]>
Commit | Line | Data |
---|---|---|
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 | |
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 | /*}}}*/ |