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 :
22 : #ifdef READLINE
23 :
24 : #include "paripriv.h"
25 : #include "gp.h"
26 :
27 : typedef int (*RLCI)(int, int); /* rl_complete and rl_insert functions */
28 :
29 : BEGINEXTERN
30 : /* otherwise C++ compilers will choke on rl_message() prototype */
31 : #define USE_VARARGS
32 : #define PREFER_STDARG
33 : #include <readline/readline.h>
34 : #include <readline/history.h>
35 : ENDEXTERN
36 :
37 : /**************************************************************************/
38 : static pari_rl_interface pari_rl;
39 : static int did_init_matched = 0;
40 :
41 : static int
42 0 : change_state(const char *msg, ulong flag, int count)
43 : {
44 0 : int c = (GP_DATA->readline_state & flag) != 0;
45 0 : ulong state = GP_DATA->readline_state;
46 :
47 0 : switch(count)
48 : {
49 0 : default: c = 0; break; /* off */
50 0 : case -1: c = 1; break; /* on */
51 0 : case -2: c = 1 - c; /* toggle */
52 : }
53 0 : if (c)
54 0 : GP_DATA->readline_state |= flag;
55 : else {
56 0 : GP_DATA->readline_state &= ~flag;
57 0 : if (!GP_DATA->readline_state && state) GP_DATA->readline_state = 1;
58 : }
59 0 : rl_save_prompt();
60 0 : rl_message("[%s: %s] ", msg, c? "on": "off");
61 0 : c = rl_read_key();
62 0 : rl_restore_prompt();
63 0 : rl_clear_message();
64 0 : rl_stuff_char(c); return 1;
65 : }
66 :
67 : /* Wrapper around rl_complete to allow toggling insertion of arguments */
68 : static int
69 0 : pari_rl_complete(int count, int key)
70 : {
71 : int ret;
72 :
73 0 : pari_rl.back = 0;
74 0 : if (count <= 0)
75 0 : return change_state("complete args", DO_ARGS_COMPLETE, count);
76 :
77 0 : rl_begin_undo_group();
78 0 : if (rl_last_func == pari_rl_complete)
79 0 : rl_last_func = (RLCI) rl_complete; /* Make repeated TABs different */
80 0 : ret = ((RLCI)rl_complete)(count,key);
81 0 : if (pari_rl.back && (pari_rl.back <= rl_point))
82 0 : rl_point -= pari_rl.back;
83 0 : rl_end_undo_group(); return ret;
84 : }
85 :
86 : static int did_matched_insert;
87 :
88 : static int
89 0 : pari_rl_matched_insert_suspend(int count, int key)
90 : {
91 0 : ulong state = GP_DATA->readline_state;
92 : (void)count; (void)key;
93 :
94 0 : did_matched_insert = (GP_DATA->readline_state & DO_MATCHED_INSERT);
95 0 : GP_DATA->readline_state &= ~DO_MATCHED_INSERT;
96 0 : if (!GP_DATA->readline_state && state) GP_DATA->readline_state = 1;
97 0 : return 1;
98 : }
99 :
100 : static int
101 0 : pari_rl_matched_insert_restore(int count, int key)
102 : {
103 : (void)count; (void)key;
104 0 : if (did_matched_insert)
105 0 : GP_DATA->readline_state |= DO_MATCHED_INSERT;
106 0 : return 1;
107 : }
108 :
109 : static const char paropen[] = "([{";
110 : static const char parclose[] = ")]}";
111 :
112 : /* To allow insertion of () with a point in between. */
113 : static int
114 0 : pari_rl_matched_insert(int count, int key)
115 : {
116 0 : int i = 0, ret;
117 :
118 0 : if (count <= 0)
119 0 : return change_state("electric parens", DO_MATCHED_INSERT, count);
120 0 : while (paropen[i] && paropen[i] != key) i++;
121 0 : if (!paropen[i] || !(GP_DATA->readline_state & DO_MATCHED_INSERT) || GP_DATA->flags & gpd_EMACS)
122 0 : return ((RLCI)rl_insert)(count,key);
123 0 : rl_begin_undo_group();
124 0 : ((RLCI)rl_insert)(count,key);
125 0 : ret = ((RLCI)rl_insert)(count,parclose[i]);
126 0 : rl_point -= count;
127 0 : rl_end_undo_group(); return ret;
128 : }
129 :
130 : static int
131 0 : pari_rl_default_matched_insert(int count, int key)
132 : {
133 0 : if (!did_init_matched) {
134 0 : did_init_matched = 1;
135 0 : GP_DATA->readline_state |= DO_MATCHED_INSERT;
136 : }
137 0 : return pari_rl_matched_insert(count, key);
138 : }
139 :
140 : static int
141 0 : pari_rl_forward_sexp(int count, int key)
142 : {
143 0 : int deep = 0, dir = 1, move_point = 0, lfail;
144 :
145 : (void)key;
146 0 : if (count < 0)
147 : {
148 0 : count = -count; dir = -1;
149 0 : if (!rl_point) goto fail;
150 0 : rl_point--;
151 : }
152 0 : while (count || deep)
153 : {
154 0 : move_point = 1; /* Need to move point if moving left. */
155 0 : lfail = 0; /* Do not need to fail left movement yet. */
156 0 : while ( !is_keyword_char(rl_line_buffer[rl_point])
157 0 : && !strchr("\"([{}])",rl_line_buffer[rl_point])
158 0 : && !( (dir == 1)
159 : ? (rl_point >= rl_end)
160 0 : : (rl_point <= 0 && (lfail = 1))))
161 0 : rl_point += dir;
162 0 : if (lfail || !rl_line_buffer[rl_point]) goto fail;
163 :
164 0 : if (is_keyword_char(rl_line_buffer[rl_point]))
165 : {
166 0 : while ( is_keyword_char(rl_line_buffer[rl_point])
167 0 : && (!((dir == 1) ? (rl_point >= rl_end) : (rl_point <= 0 && (lfail = 1)))
168 0 : || (move_point = 0)))
169 0 : rl_point += dir;
170 0 : if (deep && lfail) goto fail;
171 0 : if (!deep) count--;
172 : }
173 0 : else if (strchr(paropen,rl_line_buffer[rl_point]))
174 : {
175 0 : if (deep == 0 && dir == -1) goto fail; /* We are already out of pars. */
176 0 : rl_point += dir;
177 0 : deep++; if (!deep) count--;
178 : }
179 0 : else if (strchr(parclose,rl_line_buffer[rl_point]))
180 : {
181 0 : if (deep == 0 && dir == 1)
182 : {
183 0 : rl_point++; goto fail; /* Get out of pars. */
184 : }
185 0 : rl_point += dir;
186 0 : deep--; if (!deep) count--;
187 : }
188 0 : else if (rl_line_buffer[rl_point] == '\"')
189 : {
190 0 : int bad = 1;
191 :
192 0 : rl_point += dir;
193 0 : while ( ((rl_line_buffer[rl_point] != '\"') || (bad = 0))
194 0 : && (!((dir == 1) ? (rl_point >= rl_end) : (rl_point <= 0))
195 0 : || (move_point = 0)) )
196 0 : rl_point += dir;
197 0 : if (bad) goto fail;
198 0 : rl_point += dir; /* Skip the other delimiter */
199 0 : if (!deep) count--;
200 : }
201 : else
202 : {
203 0 : fail: rl_ding(); return 1;
204 : }
205 : }
206 0 : if (dir != 1 && move_point) rl_point++;
207 0 : return 1;
208 : }
209 :
210 : static int
211 0 : pari_rl_backward_sexp(int count, int key)
212 : {
213 0 : return pari_rl_forward_sexp(-count, key);
214 : }
215 :
216 : static void
217 0 : rl_print_aide(char *s, int flag)
218 : {
219 0 : int p = rl_point, e = rl_end;
220 0 : FILE *save = pari_outfile;
221 :
222 0 : rl_point = 0; rl_end = 0; pari_outfile = rl_outstream;
223 0 : rl_save_prompt();
224 0 : rl_message("%s",""); /* rl_message("") ==> "zero length format" warning */
225 0 : gp_help(s, flag);
226 0 : rl_restore_prompt();
227 0 : rl_point = p; rl_end = e; pari_outfile = save;
228 0 : rl_clear_message();
229 0 : rl_refresh_line(0,0);
230 0 : }
231 :
232 : /* long help if count < 0 */
233 : static int
234 0 : rl_short_help(int count, int key)
235 : {
236 0 : int flag = h_RL;
237 0 : char *s = rl_line_buffer + rl_point;
238 :
239 : (void)key;
240 : /* func() with cursor on ')', e.g. following completion */
241 0 : if (s > rl_line_buffer && *s == ')' && s[-1] == '(') s--;
242 0 : while (s > rl_line_buffer && is_keyword_char(s[-1])) s--;
243 : /* check for '\c' */
244 0 : if (s > rl_line_buffer && s[-1] == '\\') s--;
245 :
246 0 : if (count < 0 || rl_last_func == rl_short_help) flag |= h_LONG;
247 0 : rl_print_aide(s, flag); return 0;
248 : }
249 :
250 : static int
251 0 : rl_long_help(int count, int key) { (void)count; return rl_short_help(-1,key); }
252 :
253 : static void
254 1855 : init_histfile(void)
255 : {
256 1855 : char *h = GP_DATA->histfile;
257 1855 : if (h && read_history(h)) write_history(h);
258 1855 : }
259 :
260 : /*******************************************************************/
261 : /* */
262 : /* GET LINE FROM READLINE */
263 : /* */
264 : /*******************************************************************/
265 : static int
266 0 : history_is_new(char *s)
267 : {
268 : HIST_ENTRY *e;
269 0 : if (!*s) return 0;
270 0 : if (!history_length) return 1;
271 0 : e = history_get(history_base+history_length-1);
272 : /* paranoia: e != NULL, unless readline is in a weird state */
273 0 : return e? strcmp(s, e->line): 0;
274 : }
275 :
276 : static void
277 0 : gp_add_history(char *s)
278 : {
279 0 : if (history_is_new(s)) { add_history(s); append_history(1,GP_DATA->histfile); }
280 0 : }
281 :
282 : /* Read line; returns a malloc()ed string of the user input or NULL on EOF.
283 : Increments the buffer size appropriately if needed; fix *endp if so. */
284 : static char *
285 0 : gprl_input(char **endp, int first, input_method *IM, filtre_t *F)
286 : {
287 0 : pari_sp av = avma;
288 0 : Buffer *b = F->buf;
289 0 : ulong used = *endp - b->buf;
290 0 : ulong left = b->len - used, l;
291 : const char *p;
292 : char *s, *t;
293 :
294 0 : if (first) p = IM->prompt;
295 : else {
296 0 : p = F->in_comment ? GP_DATA->prompt_comment: IM->prompt_cont;
297 0 : p = gp_format_prompt(p);
298 : }
299 0 : if (! (s = readline(p)) ) { set_avma(av); return NULL; } /* EOF */
300 0 : gp_add_history(s); /* Makes a copy */
301 0 : l = strlen(s) + 1;
302 : /* put back \n that readline stripped. This is needed for
303 : * { print("a
304 : * b"); }
305 : * and conforms with the other input methods anyway. */
306 0 : t = (char*)pari_malloc(l + 1);
307 0 : memcpy(t, s, l-1);
308 0 : pari_free(s); /* readline use malloc */
309 0 : t[l-1] = '\n';
310 0 : t[l] = 0; /* equivalent to sprintf(t,"%s\n", s) */
311 0 : if (left < l)
312 : {
313 0 : ulong incr = b->len;
314 0 : if (incr < l) incr = l;
315 0 : fix_buffer(b, b->len + incr);
316 0 : *endp = b->buf + used;
317 : }
318 0 : set_avma(av); return t;
319 : }
320 :
321 : /* request one line interactively.
322 : * Return 0: EOF
323 : * 1: got one line from readline or pari_infile */
324 : int
325 0 : get_line_from_readline(const char *prompt, const char *prompt_cont, filtre_t *F)
326 : {
327 0 : const int index = history_length;
328 : char *s;
329 : input_method IM;
330 :
331 0 : if (!GP_DATA->use_readline)
332 : {
333 0 : pari_puts(prompt); pari_flush();
334 0 : return get_line_from_file(prompt, F, pari_infile);
335 : }
336 :
337 0 : IM.prompt = prompt;
338 0 : IM.prompt_cont = prompt_cont;
339 0 : IM.getline = &gprl_input;
340 0 : IM.free = 1;
341 0 : if (! input_loop(F,&IM)) { pari_puts("\n"); return 0; }
342 :
343 0 : s = F->buf->buf;
344 0 : if (*s)
345 : {
346 0 : if (history_length > index+1)
347 : { /* Multi-line input. Remove incomplete lines */
348 0 : int i = history_length;
349 0 : while (i > index) {
350 0 : HIST_ENTRY *e = remove_history(--i);
351 0 : pari_free(e->line); pari_free(e);
352 : }
353 0 : gp_add_history(s);
354 : }
355 0 : gp_echo_and_log(prompt, s);
356 : }
357 0 : return 1;
358 : }
359 :
360 : static char**
361 0 : gp_completion(char *text, int START, int END)
362 : {
363 0 : return pari_completion(&pari_rl, text, START, END);
364 : }
365 :
366 : void
367 1855 : init_readline(void)
368 : {
369 : static int init_done = 0;
370 :
371 1855 : if (init_done) return;
372 :
373 1855 : pari_use_readline(pari_rl);
374 :
375 1855 : if (! GP_DATA->use_readline) GP_DATA->readline_state = 0;
376 1855 : init_done = 1;
377 1855 : init_histfile();
378 1855 : cb_pari_init_histfile = init_histfile;
379 1855 : cb_pari_get_line_interactive = get_line_from_readline;
380 :
381 : /* Allow conditional parsing of the ~/.inputrc file. */
382 1855 : rl_readline_name = "Pari-GP";
383 :
384 : /* added ~, ? and , */
385 1855 : rl_basic_word_break_characters = " \t\n\"\\'`@$><=;|&{(?~";
386 1855 : rl_special_prefixes = "~";
387 :
388 : /* custom completer */
389 1855 : rl_attempted_completion_function = (rl_completion_func_t*) gp_completion;
390 :
391 : /* we always want the whole list of completions under emacs */
392 1855 : if (GP_DATA->flags & gpd_EMACS) rl_completion_query_items = 0x8fff;
393 :
394 1855 : rl_add_defun("short-help", rl_short_help, -1);
395 1855 : rl_add_defun("long-help", rl_long_help, -1);
396 1855 : rl_add_defun("pari-complete", pari_rl_complete, '\t');
397 1855 : rl_add_defun("pari-matched-insert", pari_rl_default_matched_insert, -1);
398 1855 : rl_add_defun("pari-matched-insert-suspend", pari_rl_matched_insert_suspend, -1);
399 1855 : rl_add_defun("pari-matched-insert-restore", pari_rl_matched_insert_restore, -1);
400 1855 : rl_add_defun("pari-forward-sexp", pari_rl_forward_sexp, -1);
401 1855 : rl_add_defun("pari-backward-sexp", pari_rl_backward_sexp, -1);
402 :
403 1855 : rl_bind_key_in_map('h', rl_short_help, emacs_meta_keymap);
404 1855 : rl_bind_key_in_map('H', rl_long_help, emacs_meta_keymap);
405 :
406 : #define KSbind(s,f,k) rl_generic_bind(ISFUNC, (s), (char*)(f), (k))
407 :
408 1855 : KSbind("OP", rl_short_help, emacs_meta_keymap); /* f1, vt100 */
409 1855 : KSbind("[11~", rl_short_help, emacs_meta_keymap); /* f1, xterm */
410 1855 : KSbind("OP", rl_short_help, vi_movement_keymap); /* f1, vt100 */
411 1855 : KSbind("[11~", rl_short_help, vi_movement_keymap); /* f1, xterm */
412 : /* XTerm may signal start/end of paste by emitting F200/F201
413 : * TODO: check to what extent this patch has been applied */
414 : /* FIXME: For vi mode something more intelligent is needed - to switch to the
415 : insert mode - and back when restoring. */
416 1855 : KSbind("[200~", pari_rl_matched_insert_suspend, emacs_meta_keymap); /* pre-paste xterm */
417 1855 : KSbind("[200~", pari_rl_matched_insert_suspend, vi_movement_keymap); /* pre-paste xterm */
418 1855 : KSbind("[201~", pari_rl_matched_insert_restore, emacs_meta_keymap); /* post-paste xterm */
419 1855 : KSbind("[201~", pari_rl_matched_insert_restore, vi_movement_keymap); /* post-paste xterm */
420 1855 : rl_bind_key_in_map('(', pari_rl_matched_insert, emacs_standard_keymap);
421 1855 : rl_bind_key_in_map('[', pari_rl_matched_insert, emacs_standard_keymap);
422 1855 : rl_bind_key_in_map(6, pari_rl_forward_sexp, emacs_meta_keymap); /* M-C-f */
423 1855 : rl_bind_key_in_map(2, pari_rl_backward_sexp, emacs_meta_keymap); /* M-C-b */
424 : }
425 : #endif
|