]> Joshua Wise's Git repositories - tdl.git/blob - inter.c
Print 'danger warnings' based on deadline proximity.
[tdl.git] / inter.c
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
49 static 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 /*}}}*/
71 static 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. */
95 struct 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
102 static 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 /*}}}*/
113 static void cleanup_chain(struct node_chain *chain)/*{{{*/
114 {
115   /* TODO: Reclaim memory */
116   return;
117 }
118 /*}}}*/
119 static 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 /*}}}*/
143 static 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 /*}}}*/
197 static 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 :-( */
229 static int want_postponed_entries = 0;
230
231 static 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
263 char **complete_help(char *text, int index)/*{{{*/
264 {
265   char **matches;
266   matches = COMPLETION_MATCHES(text, generate_a_command_completion);
267   return matches;
268 }
269 /*}}}*/
270 char **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 /*}}}*/
279 char **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 /*}}}*/
291 char **complete_priority(char *text, int index)/*{{{*/
292 {
293   return complete_list(text, index);
294 }
295 /*}}}*/
296 char **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 /*}}}*/
304 char **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 /*}}}*/
312 char **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
320 static 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 /*}}}*/
343 static char **null_tdl_completion(char *text, int start, int end)/*{{{*/
344 {
345   return NULL;
346 }
347 /*}}}*/
348 #else
349 char **complete_help(char *text, int index)/*{{{*/
350 {
351   return NULL;
352 }/*}}}*/
353 char **complete_list(char *text, int index)/*{{{*/
354 {
355   return NULL;
356 }/*}}}*/
357 char **complete_priority(char *text, int index)/*{{{*/
358 {
359   return NULL;
360 }/*}}}*/
361 char **complete_postpone(char *text, int index)/*{{{*/
362 {
363   return NULL;
364 }
365 /*}}}*/
366 char **complete_open(char *text, int index)/*{{{*/
367 {
368   return NULL;
369 }
370 /*}}}*/
371 char **complete_done(char *text, int index)/*{{{*/
372 {
373   return NULL;
374 }
375 /*}}}*/
376 #endif
377
378 static 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 /*}}}*/
389 static 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
428 static char **argv = NULL;
429
430 static 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 /*}}}*/
490 static 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 /*}}}*/
499 static 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
517 static 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
546 static char *readline_initval = NULL;
547 static 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 /*}}}*/
556 static 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
576 static 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 /*}}}*/
607 static 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 /*}}}*/
621 static 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 /*}}}*/
631 char *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
650 void 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
673 int main (int argc, char **argv) {
674   interactive();
675   return 0;
676 }
677 #endif
678 /*}}}*/
This page took 0.055869 seconds and 4 git commands to generate.