root/src/noit_console_complete.c

Revision fc52ce1c8e051b2acc414eb64b229b43f8329b20, 7.6 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 years ago)

more complete tab completion, refs #37

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  */
5
6 #include "noit_defines.h"
7
8 #include "eventer/eventer.h"
9 #include "utils/noit_log.h"
10 #include "noit_listener.h"
11 #include "noit_console.h"
12 #include "noit_tokenizer.h"
13
14 #include "noitedit/sys.h"
15 #include "noitedit/el.h"
16 #include "noitedit/fcns.h"
17 #include "noitedit/map.h"
18
19 typedef char *NCPFunction(EditLine *, const char *, int);
20
21 char *
22 noit_console_opt_delegate(noit_console_closure_t ncct,
23                           noit_console_state_stack_t *stack,
24                           noit_console_state_t *state,
25                           int argc, char **argv,
26                           int idx) {
27   int i;
28   noit_skiplist_node *next, *curr;
29   cmd_info_t *cmd;
30
31   if(state == NULL) return NULL;
32   i = 0;
33   if(argc == 0) {
34     for(next = noit_skiplist_getlist(&state->cmds); next;
35         noit_skiplist_next(&state->cmds,&next)) {
36       cmd = next->data;
37       if(idx == i) return strdup(cmd->name);
38       i++;
39     }
40   }
41
42   cmd = noit_skiplist_find_neighbors(&state->cmds, argv[0],
43                                      NULL, &curr, &next);
44   if(cmd) {
45     if(argc != 1) {
46       if(!cmd->autocomplete) return NULL;
47       return cmd->autocomplete(ncct, stack, cmd->dstate, argc-1, argv+1, idx);
48     }
49     next = curr;
50     goto multiples;
51   }
52
53  multiples:
54   if(!next) return NULL;
55   i = 0;
56   while(next) {
57     cmd = next->data;
58     if(strncasecmp(cmd->name, argv[0], strlen(argv[0])) == 0) {
59       if(idx == i) return strdup(cmd->name);
60       i++;
61     }
62     noit_skiplist_next(&state->cmds, &next);
63   }
64   return NULL;
65 }
66
67
68 static char *
69 noitedit_completion_function(EditLine *el, const char *text, int state) {
70   noit_console_closure_t ncct;
71   const LineInfo *li;
72   char **cmds, *curstr;
73   int len, i, cnt = 32;
74
75   li = el_line(el);
76   len = li->cursor - li->buffer;
77   curstr = alloca(len + 1);
78   memcpy(curstr, li->buffer, len);
79   curstr[len] = '\0';
80
81   cmds = alloca(32 * sizeof(*cmds));
82   i = noit_tokenize(curstr, cmds, &cnt);
83
84   el_get(el, EL_USERDATA, (void *)&ncct);
85
86   if(!strlen(text)) {
87     cmds[cnt++] = "";
88   }
89   if(cnt == 32) return NULL;
90   return noit_console_opt_delegate(ncct, ncct->state_stack,
91                                    ncct->state_stack->state,
92                                    cnt, cmds, state);
93 }
94 static int
95 _edit_qsort_string_compare(i1, i2)
96         const void *i1, *i2;
97 {
98         /*LINTED const castaway*/
99         const char *s1 = ((const char **)i1)[0];
100         /*LINTED const castaway*/
101         const char *s2 = ((const char **)i2)[0];
102
103         return strcasecmp(s1, s2);
104 }
105 static char **
106 completion_matches(EditLine *el, const char *text, NCPFunction *genfunc) {
107   char **match_list = NULL, *retstr, *prevstr;
108   size_t match_list_len, max_equal, which, i;
109   int matches;
110
111   matches = 0;
112   match_list_len = 1;
113   while ((retstr = (*genfunc) (el, text, matches)) != NULL) {
114     if(matches + 1 >= match_list_len) {
115       match_list_len <<= 1;
116       match_list = realloc(match_list,
117                            match_list_len * sizeof(char *));
118     }
119     match_list[++matches] = retstr;
120   }
121
122   if(!match_list)
123     return (char **) NULL;  /* nothing found */
124
125   /* find least denominator and insert it to match_list[0] */
126   which = 2;
127   prevstr = match_list[1];
128   max_equal = strlen(prevstr);
129   for(; which <= matches; which++) {
130     for(i = 0; i < max_equal && prevstr[i] == match_list[which][i]; i++)
131       continue;
132     max_equal = i;
133   }
134
135   retstr = malloc(max_equal + 1);
136   (void) strncpy(retstr, match_list[1], max_equal);
137   retstr[max_equal] = '\0';
138   match_list[0] = retstr;
139
140   /* add NULL as last pointer to the array */
141   if(matches + 1 >= match_list_len)
142     match_list = realloc(match_list,
143                          (match_list_len + 1) * sizeof(char *));
144   match_list[matches + 1] = (char *) NULL;
145
146   return (match_list);
147 }
148
149 static void
150 noit_edit_display_match_list (EditLine *el, char **matches, int len, int max)
151 {
152   int i, idx, limit, count;
153   int screenwidth = el->el_term.t_size.h;
154
155   /*
156    * Find out how many entries can be put on one line, count
157    * with two spaces between strings.
158    */
159   limit = screenwidth / (max + 2);
160   if(limit == 0)
161     limit = 1;
162
163   /* how many lines of output */
164   count = len / limit;
165   if(count * limit < len)
166     count++;
167
168   /* Sort the items if they are not already sorted. */
169   qsort(&matches[1], (size_t)(len - 1), sizeof(char *),
170         _edit_qsort_string_compare);
171
172   idx = 1;
173   for(; count > 0; count--) {
174     for(i=0; i < limit && matches[idx]; i++, idx++)
175       el->el_std_printf(el, "%-*s  ", max, matches[idx]);
176     el->el_std_printf(el, "\r\n");
177   }
178 }
179
180 unsigned char
181 noit_edit_complete(EditLine *el, int invoking_key) {
182   static const char *rl_basic_word_break_characters = " \t\n\"\\'@$><=;|&{(";
183   static const char *rl_special_prefixes = NULL;
184   static const int   rl_completion_append_character = ' ';
185   noit_console_closure_t ncct;
186   const LineInfo *li;
187   char *temp, **matches;
188   const char *ctemp;
189   int method = '\t';
190   size_t len;
191
192   el_get(el, EL_USERDATA, (void *)&ncct);
193
194   if(el->el_state.lastcmd == ncct->noit_edit_complete_cmdnum)
195     method = '?';
196
197   /* We now look backwards for the start of a filename/variable word */
198   li = el_line(el);
199   ctemp = (const char *) li->cursor;
200   while (ctemp > li->buffer
201       && !strchr(rl_basic_word_break_characters, ctemp[-1])
202       && (!rl_special_prefixes
203       || !strchr(rl_special_prefixes, ctemp[-1]) ) )
204     ctemp--;
205
206   len = li->cursor - ctemp;
207   temp = alloca(len + 1);
208   (void) strncpy(temp, ctemp, len);
209   temp[len] = '\0';
210
211   /* these can be used by function called in completion_matches() */
212   /* or (*rl_attempted_completion_function)() */
213   ncct->rl_point = li->cursor - li->buffer;
214   ncct->rl_end = li->lastchar - li->buffer;
215
216   matches = completion_matches(el, temp, noitedit_completion_function);
217
218   if (matches) {
219     int i, retval = CC_REFRESH;
220     int matches_num, maxlen, match_len;
221
222     /*
223      * Only replace the completed string with common part of
224      * possible matches if there is possible completion.
225      */
226     if (matches[0][0] != '\0') {
227       el_deletestr(el, (int) len);
228       el_insertstr(el, matches[0]);
229     }
230
231     if (method == '?')
232       goto display_matches;
233
234     if (matches[2] == NULL && strcmp(matches[0], matches[1]) == 0) {
235       /*
236        * We found exact match. Add a space after
237        * it, unless we do filename completition and the
238        * object is a directory.
239        */
240       size_t alen = strlen(matches[0]);
241       if ((alen > 0 && (matches[0])[alen - 1] != '/')
242           && rl_completion_append_character) {
243         char buf[2];
244         buf[0] = rl_completion_append_character;
245         buf[1] = '\0';
246         el_insertstr(el, buf);
247       }
248     } else if (method == '!') {
249     display_matches:
250       /*
251        * More than one match and requested to list possible
252        * matches.
253        */
254
255       for(i=1, maxlen=0; matches[i]; i++) {
256         match_len = strlen(matches[i]);
257         if (match_len > maxlen)
258           maxlen = match_len;
259       }
260       matches_num = i - 1;
261        
262       /* newline to get on next line from command line */
263       el->el_std_printf(el, "\r\n");
264
265       noit_edit_display_match_list(el, matches, matches_num, maxlen);
266       retval = CC_REDISPLAY;
267     } else if (matches[0][0]) {
268       /*
269        * There was some common match, but the name was
270        * not complete enough. Next tab will print possible
271        * completions.
272        */
273       el_beep(el);
274     } else {
275       /* lcd is not a valid object - further specification */
276       /* is needed */
277       el_beep(el);
278       retval = CC_NORM;
279     }
280
281     /* free elements of array and the array itself */
282     for (i = 0; matches[i]; i++)
283       free(matches[i]);
284     free(matches), matches = NULL;
285
286     return (retval);
287   }
288   return (CC_NORM);
289 }
Note: See TracBrowser for help on using the browser.