Line data Source code
1 : /* Copyright (C) 2000 The PARI group.
2 :
3 : This file is part of the PARI/GP package.
4 :
5 : PARI/GP is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 2 of the License, or (at your option) any later
8 : version. It is distributed in the hope that it will be useful, but WITHOUT
9 : ANY WARRANTY WHATSOEVER.
10 :
11 : Check the License for details. You should have received a copy of it, along
12 : with the package; see the file 'COPYING'. If not, write to the Free Software
13 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */
14 :
15 : /*******************************************************************/
16 : /* */
17 : /* INTERFACE TO READLINE COMPLETION */
18 : /* */
19 : /*******************************************************************/
20 : #include "pari.h"
21 : #include "paripriv.h"
22 :
23 : /**************************************************************************/
24 : static entree *current_ep = NULL;
25 :
26 : /* do we add () at the end of completed word? (is it a function?) */
27 : static int
28 0 : add_paren(pari_rl_interface *rl, int end)
29 : {
30 : entree *ep;
31 : const char *s;
32 :
33 0 : if (end < 0 || (*rl->line_buffer)[end] == '(')
34 0 : return 0; /* not from command_generator or already there */
35 0 : ep = do_alias(current_ep); /* current_ep set in command_generator */
36 0 : if (EpVALENCE(ep) < EpNEW)
37 : { /* is it a constant masked as a function (e.g Pi)? */
38 0 : s = ep->help; if (!s) return 1;
39 0 : while (is_keyword_char(*s)) s++;
40 0 : return (*s != '=');
41 : }
42 0 : switch(EpVALENCE(ep))
43 : {
44 0 : case EpVAR: return typ((GEN)ep->value) == t_CLOSURE;
45 0 : case EpINSTALL: return 1;
46 : }
47 0 : return 0;
48 : }
49 :
50 : static void
51 0 : match_concat(char **matches, const char *s)
52 : {
53 0 : pari_realloc_ip((void**)matches, strlen(matches[0])+strlen(s)+1);
54 0 : strcat(matches[0],s);
55 0 : }
56 :
57 : #define add_comma(x) (x==-2) /* from default_generator */
58 :
59 : /* a single match, possibly modify matches[0] in place */
60 : static void
61 0 : treat_single(pari_rl_interface *rl, int code, char **matches)
62 : {
63 0 : if (add_paren(rl, code))
64 : {
65 0 : match_concat(matches,"()");
66 0 : rl->back = 1;
67 0 : if (*rl->point == *rl->end)
68 0 : *rl->completion_append_character = '\0'; /* Do not append space. */
69 : }
70 0 : else if (add_comma(code))
71 0 : match_concat(matches,",");
72 0 : }
73 : #undef add_comma
74 :
75 : static char **
76 0 : matches_for_emacs(const char *text, char **matches)
77 : {
78 0 : if (!matches) printf("@");
79 : else
80 : {
81 : int i;
82 0 : printf("%s@", matches[0] + strlen(text));
83 0 : if (matches[1]) print_fun_list(matches+1,0);
84 :
85 : /* we don't want readline to do anything, but insert some junk
86 : * which will be erased by emacs.
87 : */
88 0 : for (i=0; matches[i]; i++) pari_free(matches[i]);
89 0 : pari_free(matches);
90 : }
91 0 : matches = (char **) pari_malloc(2*sizeof(char *));
92 0 : matches[0] = (char*)pari_malloc(2); sprintf(matches[0],"_");
93 0 : matches[1] = NULL; printf("@E_N_D"); pari_flush();
94 0 : return matches;
95 : }
96 :
97 : /* Attempt to complete on the contents of TEXT. 'code' is used to
98 : * differentiate between callers when a single match is found.
99 : * Return the array of matches, NULL if there are none. */
100 : static char **
101 0 : get_matches(pari_rl_interface *rl, int code, const char *text, char *(*f)(const char*, int))
102 : {
103 0 : char **matches = rl->completion_matches(text, f);
104 0 : if (matches && !matches[1]) treat_single(rl, code, matches);
105 0 : if (GP_DATA->flags & gpd_EMACS) matches = matches_for_emacs(text,matches);
106 0 : return matches;
107 : }
108 :
109 : static char *
110 0 : add_prefix(const char *name, const char *text, long junk)
111 : {
112 0 : char *s = strncpy((char*)pari_malloc(strlen(name)+1+junk),text,junk);
113 0 : strcpy(s+junk,name); return s;
114 : }
115 : static void
116 0 : init_prefix(const char *text, int *len, int *junk, char **TEXT)
117 : {
118 0 : long l = strlen(text), j = l-1;
119 0 : while (j >= 0 && is_keyword_char(text[j])) j--;
120 0 : if (j >= 7 && text[j] == '-' && !strncmp(text+(j-7),"refcard",7)) j -= 8;
121 0 : j++;
122 0 : *TEXT = (char*)text + j;
123 0 : *junk = j;
124 0 : *len = l - j;
125 0 : }
126 :
127 : static int
128 0 : is_internal(entree *ep) { return *ep->name == '_'; }
129 :
130 : /* Generator function for command completion. STATE lets us know whether
131 : * to start from scratch; without any state (i.e. STATE == 0), then we
132 : * start at the top of the list. */
133 : static char *
134 0 : hashtable_generator(const char *text, int state, entree **hash)
135 : {
136 : static int hashpos, len, junk;
137 : static entree* ep;
138 : static char *TEXT;
139 :
140 : /* If this is a new word to complete, initialize now:
141 : * + indexes hashpos (GP hash list) and n (keywords specific to long help).
142 : * + file completion and keyword completion use different word boundaries,
143 : * have TEXT point to the keyword start.
144 : * + save the length of TEXT for efficiency.
145 : */
146 0 : if (!state)
147 : {
148 0 : hashpos = 0; ep = hash[hashpos];
149 0 : init_prefix(text, &len, &junk, &TEXT);
150 : }
151 :
152 : /* Return the next name which partially matches from the command list. */
153 : for(;;)
154 0 : if (!ep)
155 : {
156 0 : if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
157 0 : ep = hash[hashpos];
158 : }
159 0 : else if (is_internal(ep) || strncmp(ep->name,TEXT,len))
160 0 : ep = ep->next;
161 : else
162 : break;
163 0 : current_ep = ep; ep = ep->next;
164 0 : return add_prefix(current_ep->name,text,junk);
165 : }
166 : /* Generator function for member completion. STATE lets us know whether
167 : * to start from scratch; without any state (i.e. STATE == 0), then we
168 : * start at the top of the list. */
169 : static char *
170 0 : member_generator(const char *text, int state)
171 : {
172 : static int hashpos, len, junk;
173 : static entree* ep;
174 : static char *TEXT;
175 0 : entree **hash=functions_hash;
176 :
177 : /* If this is a new word to complete, initialize now:
178 : * + indexes hashpos (GP hash list) and n (keywords specific to long help).
179 : * + file completion and keyword completion use different word boundaries,
180 : * have TEXT point to the keyword start.
181 : * + save the length of TEXT for efficiency.
182 : */
183 0 : if (!state)
184 : {
185 0 : hashpos = 0; ep = hash[hashpos];
186 0 : init_prefix(text, &len, &junk, &TEXT);
187 : }
188 :
189 : /* Return the next name which partially matches from the command list. */
190 : for(;;)
191 0 : if (!ep)
192 : {
193 0 : if (++hashpos >= functions_tblsz) return NULL; /* no names matched */
194 0 : ep = hash[hashpos];
195 : }
196 0 : else if (ep->name[0]=='_' && ep->name[1]=='.'
197 0 : && !strncmp(ep->name+2,TEXT,len))
198 : break;
199 : else
200 0 : ep = ep->next;
201 0 : current_ep = ep; ep = ep->next;
202 0 : return add_prefix(current_ep->name+2,text,junk);
203 : }
204 : static char *
205 0 : command_generator(const char *text, int state)
206 0 : { return hashtable_generator(text,state, functions_hash); }
207 : static char *
208 0 : default_generator(const char *text,int state)
209 0 : { return hashtable_generator(text,state, defaults_hash); }
210 :
211 : static char *
212 0 : ext_help_generator(const char *text, int state)
213 : {
214 : static int len, junk, n, def, key;
215 : static char *TEXT;
216 0 : if (!state) {
217 0 : n = 0;
218 0 : def = key = 1;
219 0 : init_prefix(text, &len, &junk, &TEXT);
220 : }
221 0 : if (def)
222 : {
223 0 : char *s = default_generator(TEXT, state);
224 0 : if (s) return add_prefix(s, text, junk);
225 0 : def = 0;
226 : }
227 0 : if (key)
228 : {
229 0 : const char **L = gphelp_keyword_list();
230 0 : for ( ; L[n]; n++)
231 0 : if (!strncmp(L[n],TEXT,len))
232 0 : return add_prefix(L[n++], text, junk);
233 0 : key = 0; state = 0;
234 : }
235 0 : return command_generator(text, state);
236 : }
237 :
238 : /* add a space between \<char> and following text. Attempting completion now
239 : * would delete char. Hitting <TAB> again will complete properly */
240 : static char **
241 0 : add_space(pari_rl_interface *rl, int start)
242 : {
243 : char **m;
244 0 : int p = *rl->point + 1;
245 0 : *rl->point = start + 2;
246 0 : rl->insert(1, ' '); *rl->point = p;
247 : /*better: fake an empty completion, but don't append ' ' after it! */
248 0 : *rl->completion_append_character = '\0';
249 0 : m = (char**)pari_malloc(2 * sizeof(char*));
250 0 : m[0] = (char*)pari_malloc(1); *(m[0]) = 0;
251 0 : m[1] = NULL; return m;
252 : }
253 :
254 : char **
255 0 : pari_completion(pari_rl_interface *rl, char *text, int START, int END)
256 : {
257 0 : int i, first=0, start=START;
258 0 : char *line = *rl->line_buffer;
259 :
260 0 : *rl->completion_append_character = ' ';
261 0 : current_ep = NULL;
262 0 : while (line[first] && isspace((unsigned char)line[first])) first++;
263 0 : if (line[first] == '?')
264 : {
265 0 : if (line[first+1] == '?')
266 0 : return get_matches(rl, -1, text, ext_help_generator);
267 0 : return get_matches(rl, -1, text, command_generator);
268 : }
269 :
270 : /* If the line does not begin by a backslash, then it is:
271 : * . an old command ( if preceded by "whatnow(" ).
272 : * . a default ( if preceded by "default(" ).
273 : * . a member function ( if preceded by "." + keyword_chars )
274 : * . a file name (in current directory) ( if preceded by 'read' or 'writexx' )
275 : * . a command */
276 0 : if (start >=1 && line[start] != '~') start--;
277 0 : while (start && is_keyword_char(line[start])) start--;
278 0 : if (line[start] == '~')
279 : {
280 : char *(*f)(const char*, int);
281 0 : f = rl->username_completion_function;
282 0 : for(i=start+1;i<=END;i++)
283 0 : if (line[i] == '/') { f = rl->filename_completion_function; break; }
284 0 : return get_matches(rl, -1, text, f);
285 : }
286 0 : if (line[first] == '\\')
287 : {
288 0 : if (first == start) return add_space(rl, start);
289 0 : return get_matches(rl, -1, text, rl->filename_completion_function);
290 : }
291 :
292 0 : while (start && line[start] != '('
293 0 : && line[start] != ',') start--;
294 0 : if (line[start] == '(' && start)
295 : {
296 : int iend, j,k;
297 : entree *ep;
298 : char buf[200];
299 :
300 0 : i = start;
301 :
302 0 : while (i && isspace((unsigned char)line[i-1])) i--;
303 0 : iend = i;
304 0 : while (i && is_keyword_char(line[i-1])) i--;
305 :
306 0 : if (strncmp(line + i,"default",7) == 0)
307 0 : return get_matches(rl, -2, text, default_generator);
308 0 : if ( strncmp(line + i,"read",4) == 0
309 0 : || strncmp(line + i,"write",5) == 0)
310 0 : return get_matches(rl, -1, text, rl->filename_completion_function);
311 :
312 0 : j = start + 1;
313 0 : while (j <= END && isspace((unsigned char)line[j])) j++;
314 0 : k = END;
315 0 : while (k > j && isspace((unsigned char)line[k])) k--;
316 : /* If we are in empty parens, insert the default arguments */
317 0 : if ((GP_DATA->readline_state & DO_ARGS_COMPLETE) && k == j
318 0 : && (line[j] == ')' || !line[j])
319 0 : && (iend - i < (long)sizeof(buf))
320 0 : && ( strncpy(buf, line + i, iend - i),
321 0 : buf[iend - i] = 0, 1)
322 0 : && (ep = is_entry(buf)) && ep->help)
323 : {
324 0 : const char *s = ep->help;
325 0 : while (is_keyword_char(*s)) s++;
326 0 : if (*s++ == '(')
327 : { /* function call: insert arguments */
328 0 : const char *e = s;
329 0 : while (*e && *e != ')' && *e != '(') e++;
330 0 : if (*e == ')')
331 : { /* we just skipped over the arguments in short help text */
332 0 : char *str = strncpy((char*)pari_malloc(e-s + 1), s, e-s);
333 0 : char **ret = (char**)pari_malloc(sizeof(char*)*2);
334 0 : str[e-s] = 0;
335 0 : ret[0] = str; ret[1] = NULL;
336 0 : if (GP_DATA->flags & gpd_EMACS) ret = matches_for_emacs("",ret);
337 0 : return ret;
338 : }
339 : }
340 : }
341 : }
342 0 : for(i = END-1; i >= start; i--)
343 0 : if (!is_keyword_char(line[i]))
344 : {
345 0 : if (line[i] == '.')
346 0 : return get_matches(rl, -1, text, member_generator);
347 0 : break;
348 : }
349 0 : return get_matches(rl, END, text, command_generator);
350 : }
351 :
352 : static char *
353 0 : pari_completion_word(char *line, long end)
354 : {
355 0 : char *s = line + end, *found_quote = NULL;
356 : long i;
357 0 : *s = 0; /* truncate at cursor position */
358 0 : for (i=0; i < end; i++)
359 : { /* first look for unclosed string */
360 0 : switch(line[i])
361 : {
362 0 : case '"':
363 0 : found_quote = found_quote? NULL: line + i;
364 0 : break;
365 0 : case '\\': i++; break;
366 : }
367 : }
368 0 : if (found_quote) return found_quote + 1; /* return next char after quote */
369 : /* else find beginning of word */
370 0 : while (s > line && is_keyword_char(s[-1])) s--;
371 0 : return s;
372 : }
373 :
374 : char **
375 0 : pari_completion_matches(pari_rl_interface *rl, const char *s, long pos, long *wordpos)
376 : {
377 : char *text, *b;
378 : long w;
379 :
380 0 : if (*rl->line_buffer) pari_free(*rl->line_buffer);
381 0 : *rl->line_buffer = b = pari_strdup(s);
382 0 : text = pari_completion_word(b, pos);
383 0 : w = text - b; if (wordpos) *wordpos = w;
384 : /* text = start of expression we complete */
385 0 : *rl->end = strlen(b)-1;
386 0 : *rl->point = pos;
387 0 : return pari_completion(rl, text, w, pos);
388 : }
|