Fall back on index for sort if the due date is the same, not just if both due dates...
[tdl.git] / list.c
1 /*
2    $Header: /cvs/src/tdl/list.c,v 1.20.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,2002,2003,2004,2005  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 #include <time.h>
23 #include <string.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #include <unistd.h>
27 #include "tdl.h"
28
29 struct list_options {
30   unsigned monochrome:1;
31   unsigned show_all:1;
32   unsigned show_postponed:1;
33   unsigned verbose:1;
34   unsigned set_depth:1;
35   int depth;
36 };
37
38 #define INDENT_TAB 3
39
40 /*{{{ Colour definitions */
41 #define RED     "\e[31m\e[1m"
42 #define GREEN   "\e[32m"
43 #define YELLOW  "\e[33m\e[1m"
44 #define BLUE    "\e[34m"
45 #define MAGENTA "\e[35m"
46 #define CYAN    "\e[36m"
47 #define NORMAL  "\e[0m"
48 #define DIM     "\e[37m\e[2m"
49 #define DIMCYAN "\e[36m\e[2m"
50
51 /* Table to map priority levels to colours */
52 static char *colour_table[] = {
53   NORMAL, BLUE, CYAN, NORMAL, YELLOW, RED
54 };
55
56 static char *priority_text[] = {
57   "UNKNOWN!", "verylow", "low", "normal", "high", "urgent"
58 };
59
60 /*}}}*/
61 void do_indent(int indent)/*{{{*/
62 {
63   int i;
64   for (i=0; i<indent; i++) putchar(' ');
65 }
66 /*}}}*/
67 void do_bullet_indent(int indent)/*{{{*/
68 {
69   int i;
70   int n;
71   n = indent - 2;
72   for (i=0; i<indent; i++) putchar((i == n) ? '-' : ' ');
73 }
74 /*}}}*/
75 static void print_timestamp(int timestamp, char *leader, int indent, int monochrome)/*{{{*/
76 {
77   char buffer[32];
78   time_t now, timestamp2;
79   long diff, days_ago, days_ahead;
80   
81   now = time(NULL);
82   diff = now - timestamp;
83   days_ago = (diff + ((diff > 0) ? 43200 : -43200)) / 86400;
84   timestamp2 = (time_t) timestamp;
85   strftime(buffer, sizeof(buffer), "%a %d %b %Y %H:%M", 
86            localtime(&timestamp2));
87   do_indent(indent+2);
88   if (days_ago < 0) {
89     days_ahead = - days_ago;
90     if (monochrome) {
91       printf("%s: %s (%ld day%s ahead)\n", leader, buffer, days_ago, (days_ahead == 1) ? "" : "s");
92     } else {
93       printf("%s%s:%s %s %s(%ld day%s ahead)%s\n", GREEN, leader, NORMAL, buffer, MAGENTA, days_ahead, (days_ahead == 1) ? "" : "s", NORMAL);
94     }
95   } else {
96     if (monochrome) {
97       printf("%s: %s (%ld day%s ago)\n", leader, buffer, days_ago, (days_ago == 1) ? "" : "s");
98     } else {
99       printf("%s%s:%s %s (%ld day%s ago)\n", GREEN, leader, NORMAL, buffer, days_ago, (days_ago == 1) ? "" : "s");
100     }
101   }
102 }
103 /*}}}*/
104 static void count_kids(struct links *x, int *n_kids, int *n_done_kids, int *n_open_kids)/*{{{*/
105 {
106   int nk, ndk;
107   struct node *y;
108
109   nk = ndk = 0;
110   for (y = x->next;
111        y != (struct node *) x;
112        y = y->chain.next) {
113     if (y->done) ndk++;
114     nk++;
115   }
116
117   if (n_kids) *n_kids = nk;
118   if (n_done_kids) *n_done_kids = ndk;
119   if (n_open_kids) *n_open_kids = (nk - ndk);
120   return;
121 }
122 /*}}}*/
123 static void print_details(struct node *y, int indent, int summarise_kids, const struct list_options *options, char *index_buffer, time_t now)/*{{{*/
124 {
125   int is_done;
126   int is_ignored;
127   int is_postponed;
128   int is_deferred;
129   char *p;
130   int n_kids, n_open_kids;
131   int show_state;
132   char *narrow_prefix;
133   int index_buffer_len;
134
135   is_done = (y->done > 0);
136   is_ignored = (y->done == IGNORED_TIME);
137   is_postponed = (y->arrived == POSTPONED_TIME);
138   is_deferred = (y->arrived > now);
139   if (!options->show_all && is_done) return;
140
141   do_indent(indent);
142   count_kids(&y->kids, &n_kids, NULL, &n_open_kids);
143   show_state = options->show_all || options->show_postponed;
144   index_buffer_len = strlen(index_buffer);
145   narrow_prefix = get_narrow_prefix();
146
147   if (narrow_prefix) {
148     if (options->monochrome) printf("%s%s", narrow_prefix, index_buffer_len ? "." : "");
149     else                     printf("%s%s%s%s", BLUE, narrow_prefix, index_buffer_len ? "." : "", NORMAL);
150   }
151   
152   if (options->monochrome) printf("%s", index_buffer);
153   else                     printf("%s%s%s", GREEN, index_buffer, NORMAL);
154
155   if (summarise_kids && (n_kids > 0)) {
156     if (options->monochrome) printf(" [%d/%d]", n_open_kids, n_kids);
157     else                     printf(" %s[%d/%d]%s", CYAN, n_open_kids, n_kids, NORMAL);
158   }
159
160   if (show_state && !options->verbose) {
161     if (is_ignored) {
162       if (options->monochrome) printf(" (IGNORED)");
163       else                     printf(" %s(IGNORED)%s", BLUE, NORMAL);
164     } else if (is_done) {
165       if (options->monochrome) printf(" (DONE)");
166       else                     printf(" %s(DONE)%s", CYAN, NORMAL);
167     } else if (is_postponed) {
168       if (options->monochrome) printf(" (POSTPONED)");
169       else                     printf(" %s(POSTPONED)%s", MAGENTA, NORMAL);
170     } else if (is_deferred) {
171       if (options->monochrome) printf(" (DEFERRED)");
172       else                     printf(" %s(DEFERRED)%s", MAGENTA, NORMAL);
173     }
174     printf(" : ");
175   } else {
176     printf(" ");
177   }
178
179   if (!options->monochrome) printf("%s", is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
180
181 #if 0
182   
183   if (summarise_kids && (n_kids > 0)) {
184     if (options->monochrome) {
185       printf("%s [%d/%d] %s", index_buffer, n_open_kids, n_kids,
186              (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : 
187              (show_state && !options->verbose && is_done) ? "(DONE) : " : 
188              (show_state && !options->verbose && is_done) ? "(DONE) : " : 
189              (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
190     } else {
191       printf("%s%s %s[%d/%d]%s %s%s", GREEN, index_buffer, CYAN, n_open_kids, n_kids, NORMAL,
192              (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
193              (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
194              (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
195              (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
196              is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
197     }
198   } else {
199     if (options->monochrome) {
200       printf("%s %s", index_buffer,
201              (show_state && !options->verbose && is_ignored) ? "(IGNORED) : " : 
202              (show_state && !options->verbose && is_done) ? "(DONE) : " : 
203              (show_state && !options->verbose && is_postponed) ? "(POSTPONED) : " :
204              (show_state && !options->verbose && (y->arrived > now)) ? "(DEFERRED) : " : ": ");
205     } else {
206       printf("%s%s%s %s%s", GREEN, index_buffer, NORMAL,
207              (show_state && !options->verbose && is_ignored) ? BLUE "(IGNORED) " NORMAL :
208              (show_state && !options->verbose && is_done) ? CYAN "(DONE) " NORMAL :
209              (show_state && !options->verbose && is_postponed) ? MAGENTA "(POSTPONED) " :
210              (show_state && !options->verbose && (y->arrived > now)) ? MAGENTA "(DEFERRED) " : "",
211              is_done ? CYAN : is_postponed ? MAGENTA : colour_table[y->priority]);
212     }
213   }
214 #endif
215   for (p = y->text; *p; p++) {
216     putchar(*p);
217     if (*p == '\n') {
218       do_indent(indent + 5);
219     }
220   }
221   if (!options->monochrome) printf("%s", NORMAL);
222   if (y->required_by > 0) {
223     time_t delta = y->required_by - time(NULL);
224     if (delta < 0) {
225       printf(options->monochrome ?
226                " [OVERDUE]" :
227                RED " [OVERDUE]" NORMAL);
228     } else if (delta < 24*60*60) {
229       printf(options->monochrome ?
230                "  [due <24hr]" :
231                RED " [due <24hr]" NORMAL);
232     } else if (delta < 2*24*60*60) {
233       printf(options->monochrome ?
234                "  [due <2d]" :
235                RED " [due <2d]" NORMAL);
236     } else if (delta < 3*24*60*60) {
237       printf(options->monochrome ?
238                "  [due <3d]" :
239                YELLOW " [due <3d]" NORMAL);
240     } else if (delta < 4*24*60*60) {
241       printf(options->monochrome ?
242                "  [due <4d]" :
243                YELLOW " [due <4d]" NORMAL);
244     } else if (delta < 5*24*60*60) {
245       printf(options->monochrome ?
246                "  [due <5d]" :
247                GREEN " [due <5d]" NORMAL);
248     }
249   }
250   printf("\n");
251
252   if (options->verbose) {
253     print_timestamp(y->arrived, "Arrived", indent, options->monochrome);
254     do_indent(indent + 2);
255     if (options->monochrome) {
256       printf("Priority: %s\n", priority_text[y->priority]);
257     } else {
258       printf("%sPriority: %s%s%s\n",
259           GREEN, colour_table[y->priority], priority_text[y->priority], NORMAL);
260     }
261     if (y->required_by > 0) print_timestamp(y->required_by, "Required by", indent, options->monochrome);
262     if (y->done > 0) print_timestamp(y->done, "Completed", indent, options->monochrome);
263     printf("\n");
264   }
265
266 }
267 /*}}}*/
268 /* 1 if x has lower priority than y. */
269 static int node_lessthan(struct node *x, struct node *y)/*{{{*/
270 {
271   if (x->priority > y->priority)
272     return 1;
273   if (x->priority < y->priority)
274     return 0;
275   if (x->required_by == y->required_by)
276     return (x->idx > y->idx);
277   if (y->required_by == 0)
278     return 1;
279   if (x->required_by == 0)
280     return 0;
281   return x->required_by < y->required_by;
282 }
283 /*}}}*/
284 static void sort_chain(struct links *x)/*{{{*/
285 {
286   struct links new;
287   struct node *y, *ynext, *yprev;
288   int idx;
289   
290   new.next = NULL;
291   new.prev = NULL;
292   
293   /* Stupidsort. */
294   for (y = x->next, idx = 1; y != (struct node *) x; y = ynext, ++idx) {
295     /* y is now the current node; go insert it into its rightful place in
296      * the new chain.  */
297     struct node **nextp = &(new.next);
298     struct node *ins;
299     for (ins = new.next; ins && node_lessthan(ins, y); nextp = &(ins->chain.next), ins = ins->chain.next)
300       ;
301     y->chain.next->chain.prev = y->chain.prev;
302     y->chain.prev->chain.next = y->chain.next;
303     ynext = y->chain.next;
304     y->chain.next = *nextp;
305     *nextp = y;
306     y->idx = idx;
307   }
308   
309   /* Now clean up the new links. */
310   yprev = (struct node *)x;
311   for (y = new.next; y; yprev = y, y = y->chain.next) {
312     y->chain.prev = yprev;
313   }
314   yprev->chain.next = (struct node *)x;
315   new.prev = y;
316   
317   if (new.next == NULL) {
318     x->next = (struct node *)x;
319     x->prev = (struct node *)x;
320   } else {
321     x->next = new.next;
322     x->prev = new.prev;
323   }
324 }
325 /*}}}*/
326 static void list_chain(struct links *x, int indent, int depth, const struct list_options *options, char *index_buffer, enum Priority prio, time_t now, unsigned char *hits)/*{{{*/
327 {
328   struct node *y;
329   int is_done, is_deferred, is_postponed;
330   int show_node;
331   char component_buffer[8];
332   char new_index_buffer[64];
333   
334   sort_chain(x);
335     
336   for (y = x->next;
337        y != (struct node *) x;
338        y = y->chain.next) {
339     
340     is_done = (y->done > 0);
341     is_postponed = (y->arrived == POSTPONED_TIME);
342     is_deferred = (y->arrived > now);
343     show_node = options->show_all 
344              || (options->show_postponed && !is_done)
345              || (!is_deferred && !is_postponed);
346     if (!show_node) continue;
347
348     sprintf(component_buffer, "%d", y->idx);
349     strcpy(new_index_buffer, index_buffer);
350     if (strlen(new_index_buffer) > 0) {
351       strcat(new_index_buffer, ".");
352     }
353     strcat(new_index_buffer, component_buffer);
354
355     if (y->priority >= prio) {
356       int summarise_kids = (options->set_depth && (options->depth == depth));
357       if (hits[y->iscratch]) {
358         print_details(y, indent, summarise_kids, options, new_index_buffer, now);
359       }
360     }
361
362     /* Maybe list children regardless of priority assigned to parent. */
363     if (!options->set_depth || (depth < options->depth)) {
364       list_chain(&y->kids, indent + INDENT_TAB, depth + 1, options, new_index_buffer, prio, now, hits);
365     }
366
367   }
368   return;
369 }
370 /*}}}*/
371 static void allocate_indices(struct links *x, int *idx)/*{{{*/
372 {
373   struct node *y;
374   for (y = x->next;
375        y != (struct node *) x;
376        y = y->chain.next) {
377
378     y->iscratch = *idx;
379     ++*idx;
380     allocate_indices(&y->kids, idx);
381   }
382 }
383 /*}}}*/
384 static void search_node(struct links *x, int max_errors, unsigned long *vecs, unsigned long hitvec, unsigned char *hits)/*{{{*/
385 {
386   struct node *y;
387   char *p;
388   char *token;
389   int got_hit;
390   unsigned long r0, r1, r2, r3, nr0, nr1, nr2;
391
392   for (y = x->next;
393        y != (struct node *) x;
394        y = y->chain.next) {
395
396     token = y->text;
397
398     switch (max_errors) {
399       /* optimise common cases for few errors to allow optimizer to keep bitmaps
400        * in registers */
401       case 0:/*{{{*/
402         r0 = ~0;
403         got_hit = 0;
404         for(p=token; *p; p++) {
405           int idx = (unsigned int) *(unsigned char *)p;
406           r0 = (r0<<1) | vecs[idx];
407           if (~(r0 | hitvec)) {
408             got_hit = 1;
409             break;
410           }
411         }
412         break;
413         /*}}}*/
414       case 1:/*{{{*/
415         r0 = ~0;
416         r1 = r0<<1;
417         got_hit = 0;
418         for(p=token; *p; p++) {
419           int idx = (unsigned int) *(unsigned char *)p;
420           nr0 = (r0<<1) | vecs[idx];
421           r1  = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
422           r0  = nr0;
423           if (~((r0 & r1) | hitvec)) {
424             got_hit = 1;
425             break;
426           }
427         }
428         break;
429   /*}}}*/
430       case 2:/*{{{*/
431         r0 = ~0;
432         r1 = r0<<1;
433         r2 = r1<<1;
434         got_hit = 0;
435         for(p=token; *p; p++) {
436           int idx = (unsigned int) *(unsigned char *)p;
437           nr0 =  (r0<<1) | vecs[idx];
438           nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
439           r2  = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
440           r0  = nr0;
441           r1  = nr1;
442           if (~((r0 & r1& r2) | hitvec)) {
443             got_hit = 1;
444             break;
445           }
446         }
447         break;
448   /*}}}*/
449       case 3:/*{{{*/
450         r0 = ~0;
451         r1 = r0<<1;
452         r2 = r1<<1;
453         r3 = r2<<1;
454         got_hit = 0;
455         for(p=token; *p; p++) {
456           int idx = (unsigned int) *(unsigned char *)p;
457           nr0 =  (r0<<1) | vecs[idx];
458           nr1 = ((r1<<1) | vecs[idx]) & ((r0 & nr0) << 1) & r0;
459           nr2 = ((r2<<1) | vecs[idx]) & ((r1 & nr1) << 1) & r1;
460           r3  = ((r3<<1) | vecs[idx]) & ((r2 & nr2) << 1) & r2;
461           r0  = nr0;
462           r1  = nr1;
463           r2  = nr2;
464           if (~((r0 & r1 & r2 & r3) | hitvec)) {
465             got_hit = 1;
466             break;
467           }
468         }
469         break;
470         /*}}}*/
471       default:
472         assert(0); /* not allowed */
473         break;
474     }
475     if (got_hit) {
476       hits[y->iscratch] = 1;
477     }
478     search_node(&y->kids, max_errors, vecs, hitvec, hits);
479   }
480 }
481 /*}}}*/
482 static void merge_search_condition(unsigned char *hits, int n_nodes, char *cond)/*{{{*/
483 {
484   /* See "Fast text searching with errors, Sun Wu and Udi Manber, TR 91-11,
485      University of Arizona.  I have been informed that this algorithm is NOT
486      patented.  This implementation of it is entirely the work of Richard P.
487      Curnow - I haven't looked at any related source (webglimpse, agrep etc) in
488      writing this.
489   */
490
491   int max_errors;
492   char *slash;
493   char *substring;
494   unsigned long a[256];
495   unsigned long hit;
496   int len, i;
497   char *p;
498   unsigned char *hit0;
499
500   slash = strchr(cond, '/');
501   if (!slash) {
502     max_errors = 0;
503     substring = cond;
504   } else {
505     substring = new_string(cond);
506     substring[slash-cond] = '\0';
507     max_errors = atoi(slash+1);
508     if (max_errors > 3) {
509       fprintf(stderr, "Can only match with up to 3 errors, ignoring patterh <%s>\n", cond);
510       goto get_out;
511     }
512   }
513
514   len = strlen(substring);
515   if (len < 1 || len > 31) {
516     fprintf(stderr, "Pattern must be between 1 and 31 characters\n");
517     goto get_out;
518   }
519   
520   /* Set array 'a' to all -1 values */
521   memset(a, 0xff, 256 * sizeof(unsigned long));
522   for (p=substring, i=0; *p; p++, i++) {
523     unsigned char pc;
524     pc = *(unsigned char *) p;
525     a[(unsigned int) pc] &= ~(1UL << i);
526     /* Make search case insensitive */
527     if (isupper(pc)) {
528       a[tolower((unsigned int) pc)] &= ~(1UL << i);
529     }
530     if (islower(pc)) {
531       a[toupper((unsigned int) pc)] &= ~(1UL << i);
532     }
533   }
534   hit = ~(1UL << (len-1));
535
536   hit0 = new_array(unsigned char, n_nodes);
537   memset(hit0, 0, n_nodes);
538   
539   /* Now scan each node against this match criterion */
540   search_node(&top, max_errors, a, hit, hit0);
541   for (i=0; i<n_nodes; i++) {
542     hits[i] &= hit0[i];
543   }
544   free(hit0);
545
546 get_out:
547   if (substring != cond) {
548     free(substring);
549   }
550   return;
551 }
552 /*}}}*/
553 int process_list(char **x)/*{{{*/
554 {
555   struct list_options options;
556   int options_done = 0;
557   int any_paths = 0;
558   char index_buffer[256];
559   char *y;
560   enum Priority prio = PRI_NORMAL, prio_to_use, node_prio;
561   int prio_set = 0;
562   time_t now = time(NULL);
563   
564   unsigned char *hits;
565   int node_index, n_nodes;
566
567   options.monochrome = 0;
568   options.show_all = 0;
569   options.show_postponed = 0;
570   options.verbose = 0;
571   options.set_depth = 0;
572
573   if ( (getenv("TDL_LIST_MONOCHROME") != NULL) ||
574        (isatty(STDOUT_FILENO) == 0) ) {
575     options.monochrome = 1;
576   }
577   
578   /* Initialisation to support searching */
579   node_index = 0;
580   allocate_indices(&top, &node_index);
581   n_nodes = node_index;
582
583   hits = n_nodes ? new_array(unsigned char, n_nodes) : NULL;
584
585   /* all nodes match until proven otherwise */
586   memset(hits, 1, n_nodes);
587   
588   while ((y = *x) != 0) {
589     /* An argument starting '1' or '+1' or '+-1' (or '-1' after '--') is
590      * treated as the path of the top node to show */
591     if (isdigit(y[0]) ||
592         (y[0] == '.') ||
593         (options_done && (y[0] == '-') && isdigit(y[1])) ||
594         ((y[0] == '+') &&
595          (isdigit(y[1]) ||
596           ((y[1] == '-' && isdigit(y[2])))))) {
597       
598       struct node *n = lookup_node(y, 0, NULL);
599       int summarise_kids;
600
601       if (!n) return -1;
602       
603       any_paths = 1;
604       index_buffer[0] = '\0';
605       strcat(index_buffer, y);
606       summarise_kids = (options.set_depth && (options.depth==0));
607       if (hits[n->iscratch]) {
608         print_details(n, 0, summarise_kids, &options, index_buffer, now);
609       }
610       if (!options.set_depth || (options.depth > 0)) {
611         node_prio = n->priority;
612
613         /* If the priority has been set on the cmd line, always use that.
614          * Otherwise, use the priority from the specified node, _except_ when
615          * that is higher than normal, in which case use normal. */
616         prio_to_use = (prio_set) ? prio : ((node_prio > prio) ? prio : node_prio);
617         list_chain(&n->kids, INDENT_TAB, 0, &options, index_buffer, prio_to_use, now, hits);
618       }
619     } else if ((y[0] == '-') && (y[1] == '-')) {
620       options_done = 1;
621     } else if (y[0] == '-') {
622       while (*++y) {
623         switch (*y) {
624           case 'v':
625             options.verbose = 1;
626             break;
627           case 'a':
628             options.show_all = 1;
629             break;
630           case 'm':
631             options.monochrome = 1;
632             break;
633           case 'p':
634             options.show_postponed = 1;
635             break;
636           case '1': case '2': case '3':
637           case '4': case '5': case '6': 
638           case '7': case '8': case '9':
639             options.set_depth = 1;
640             options.depth = (*y) - '1';
641             break;
642           default:
643             fprintf(stderr, "Unrecognized option : -%c\n", *y);
644             break;
645         }
646       }
647     } else if (y[0] == '/') {
648       /* search expression */
649       merge_search_condition(hits, n_nodes, y+1);
650        
651     } else {
652       int error;
653       prio = parse_priority(y, &error);
654       if (error < 0) return error;
655       prio_set = 1;
656     }
657
658     x++;
659   }
660   
661   if (!any_paths) {
662     struct node *narrow_top = get_narrow_top();
663     if (narrow_top) {
664       index_buffer[0] = 0;
665       if (hits[narrow_top->iscratch]) {
666         int summarise_kids = (options.set_depth && (options.depth==0));
667         print_details(narrow_top, 0, summarise_kids, &options, index_buffer, now);
668       }
669       if (!options.set_depth || (options.depth > 0)) {
670         list_chain(&narrow_top->kids, 0, 1, &options, index_buffer, prio, now, hits);
671       }
672     } else {
673       index_buffer[0] = 0;
674       list_chain(&top, 0, 0, &options, index_buffer, prio, now, hits);
675     }
676   }
677
678   if (hits) free(hits);
679   
680   return 0;
681 }
682 /*}}}*/
This page took 0.047555 seconds and 4 git commands to generate.