1 /* pk-repl.c - A REPL ui for poke.  */
2 
3 /* Copyright (C) 2019, 2020, 2021 Jose E. Marchesi */
4 
5 /* This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include <signal.h>
22 #include <pthread.h>
23 #include <unistd.h>
24 #include <setjmp.h>
25 #include <stdlib.h>
26 #include "readline.h"
27 #if defined HAVE_READLINE_HISTORY_H
28 # include <readline/history.h>
29 #endif
30 #include "xalloc.h"
31 #include "xstrndup.h"
32 
33 #include "poke.h"
34 #include "pk-cmd.h"
35 #if HAVE_HSERVER
36 #  include "pk-hserver.h"
37 #endif
38 #include "pk-utils.h"
39 #include "pk-map.h"
40 
41 /* The thread that contains the non-local entry point for reentering
42    the REPL.  */
43 static pthread_t volatile ctrlc_thread;
44 /* The non-local entry point for reentering the REPL.  */
45 static sigjmp_buf /*volatile*/ ctrlc_buf;
46 /* When nonzero, ctrlc_thread and ctrlc_buf contain valid values.  */
47 static int volatile ctrlc_buf_valid;
48 
49 /* This function is called repeatedly by the readline library, when
50    generating potential command line completions.
51 
52    TEXT is the partial word to be completed.  STATE is zero the first
53    time the function is called and a positive non-zero integer for
54    each subsequent call.
55 
56    On each call, the function returns a potential completion.  It
57    returns NULL to indicate that there are no more possibilities left. */
58 static char *
poke_completion_function(const char * text,int state)59 poke_completion_function (const char *text, int state)
60 {
61   /* First try to complete with "normal" commands.  */
62   char *function_name = pk_completion_function (poke_compiler,
63                                                 text, state);
64 
65   /* Then try with dot-commands. */
66   if (function_name == NULL)
67     function_name = pk_cmd_get_next_match (text, strlen (text));
68 
69   return function_name;
70 }
71 
72 /*  A trivial completion function.  No completions are possible.  */
73 static char *
null_completion_function(const char * x,int state)74 null_completion_function (const char *x, int state)
75 {
76   return NULL;
77 }
78 
79 char * doc_completion_function (const char *x, int state);
80 
81 #define SPACE_SUBSTITUTE  '/'
82 
83 /* Display the list of matches, replacing SPACE_SUBSTITUTE with
84    a space.  */
85 static void
space_substitute_display_matches(char ** matches,int num_matches,int max_length)86 space_substitute_display_matches (char **matches, int num_matches,
87                                 int max_length)
88 {
89   for (int i = 0; i < num_matches + 1; ++i)
90     {
91       for (char *m = matches[i]; *m; ++m)
92         {
93           if (*m == SPACE_SUBSTITUTE)
94             *m = ' ';
95         }
96     }
97 
98   rl_display_match_list (matches, num_matches, max_length);
99 }
100 
101 /* Display the rl_line_buffer substituting
102 SPACE_SUBSTITUTE with a space.  */
103 static void
space_substitute_redisplay(void)104 space_substitute_redisplay (void)
105 {
106   /* Take a copy of the line_buffer.  */
107   char *olb = xstrdup (rl_line_buffer);
108 
109   for (char *x = rl_line_buffer; *x ; x++)
110     {
111       if (*x == SPACE_SUBSTITUTE)
112         *x = ' ';
113     }
114 
115   rl_redisplay ();
116 
117   /* restore the line_buffer to its original state.  */
118   strcpy (rl_line_buffer, olb);
119   free (olb);
120 }
121 
122 /* Readline's getc callback.
123    Use this function to update the completer which
124    should be used.
125 */
126 static int
poke_getc(FILE * stream)127 poke_getc (FILE *stream)
128 {
129   char *line_to_point = xstrndup (rl_line_buffer, rl_point ? rl_point - 1 : 0);
130   char *tok = strtok (line_to_point, "\t ");
131   const struct pk_cmd *cmd = pk_cmd_find (tok);
132 
133   free (line_to_point);
134 
135   if (cmd == NULL)
136     rl_completion_entry_function = poke_completion_function;
137 
138    if (rl_completion_entry_function == poke_completion_function)
139      {
140        if (cmd)
141          {
142            if (cmd->completer)
143              rl_completion_entry_function = cmd->completer;
144            else
145              rl_completion_entry_function = null_completion_function;
146          }
147      }
148 
149   int c =  rl_getc (stream);
150 
151   /* Due to readline's apparent inability to change the word break
152      character in the middle of a line, we have to do some underhand
153      skullduggery here.  Spaces are substituted with SPACE_SUBSTITUTE,
154      and then substituted back again in various callback functions.  */
155   if (rl_completion_entry_function == doc_completion_function)
156     {
157       rl_completion_display_matches_hook = space_substitute_display_matches;
158       rl_redisplay_function = space_substitute_redisplay;
159 
160       if (c == ' ')
161         c = SPACE_SUBSTITUTE;
162     }
163   else
164     {
165       rl_completion_display_matches_hook = NULL;
166       rl_redisplay_function = rl_redisplay;
167     }
168 
169   return c;
170 }
171 
172 
173 static void
banner(void)174 banner (void)
175 {
176   if (!poke_quiet_p)
177     {
178       pk_print_version (1 /* hand_p */);
179       pk_puts ("\n");
180 
181 #if HAVE_HSERVER
182       if (poke_hserver_p)
183         {
184           pk_printf ("hserver listening in port %d.\n",
185                      pk_hserver_port ());
186           pk_puts ("\n");
187         }
188 #endif
189 
190 #if HAVE_HSERVER
191       if (poke_hserver_p)
192         {
193           char *help_hyperlink
194             = pk_hserver_make_hyperlink ('e', ".help");
195 
196           pk_puts (_("For help, type \""));
197           pk_term_hyperlink (help_hyperlink, NULL);
198           pk_puts (".help");
199           pk_term_end_hyperlink ();
200           pk_puts ("\".\n");
201           free (help_hyperlink);
202         }
203       else
204 #endif
205       pk_puts (_("For help, type \".help\".\n"));
206       pk_puts (_("Type \".exit\" to leave the program.\n"));
207     }
208 
209 }
210 
211 static _GL_ASYNC_SAFE void
poke_sigint_handler(int sig)212 poke_sigint_handler (int sig)
213 {
214   if (ctrlc_buf_valid)
215     {
216       /* Abort the current command, and return to the REPL.  */
217       if (!pthread_equal (pthread_self (), ctrlc_thread))
218         {
219           /* We are not in the thread that established the jmp_buf for
220              returning to the REPL.  Redirect the signal to that thread,
221              and decline responsibility for future deliveries of this
222              signal.  */
223           sigset_t mask;
224           if (sigemptyset (&mask) == 0
225               && sigaddset (&mask, SIGINT) == 0)
226             pthread_sigmask (SIG_BLOCK, &mask, NULL);
227 
228           pthread_kill (ctrlc_thread, SIGINT);
229         }
230       else
231         {
232           /* We are in the thread that established the jmp_buf for returning
233              to the REPL.  Put the readline library into a sane state, then
234              unwind the stack.  */
235           rl_free_line_state ();
236           rl_cleanup_after_signal ();
237           rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
238           printf ("\n");
239           siglongjmp (ctrlc_buf, 1);
240         }
241     }
242   else
243     {
244       /* The default behaviour for SIGINT is to terminate the process.
245          Let's do that here.  */
246       struct sigaction sa;
247       sa.sa_handler = SIG_DFL;
248       sa.sa_flags = 0;
249       sigemptyset (&sa.sa_mask);
250       sigaction (SIGINT, &sa, NULL);
251       raise (SIGINT);
252     }
253 }
254 
255 /* Return a copy of TEXT, with every instance of the space character
256    prepended with the backslash character.   The caller is responsible
257    for freeing the result.  */
258 static char *
escape_metacharacters(char * text,int match_type,char * qp)259 escape_metacharacters (char *text, int match_type, char *qp)
260 {
261   char *p = text;
262   char *r = xmalloc (strlen (text) * 2 + 1);
263   char *s = r;
264 
265   while (*p)
266     {
267       char c = *p++;
268       if (c == ' ')
269         *r++ = '\\';
270       *r++ = c;
271     }
272   *r = '\0';
273 
274   return s;
275 }
276 
277 static char *
pk_prompt(void)278 pk_prompt (void)
279 {
280   char *prompt = "";
281 
282   if (poke_prompt_maps_p)
283     {
284       pk_ios cur_ios;
285 
286       cur_ios = pk_ios_cur (poke_compiler);
287       if (cur_ios)
288         {
289           pk_map maps
290             = pk_map_get_maps (pk_ios_get_id (cur_ios));
291 
292           if (maps)
293             {
294               pk_map map;
295 
296               prompt = pk_str_concat (prompt, "[", NULL);
297               for (map = maps; map; map = PK_MAP_CHAIN (map))
298                 prompt = pk_str_concat (prompt, PK_MAP_NAME (map),
299                                         PK_MAP_CHAIN (map) != NULL ? "," : NULL,
300                                         NULL);
301               prompt = pk_str_concat (prompt, "]", NULL);
302             }
303         }
304     }
305 
306   prompt = pk_str_concat (prompt, "(poke) ", NULL);
307   return prompt;
308 }
309 
310 void
pk_repl(void)311 pk_repl (void)
312 {
313   banner ();
314 
315   /* Arrange for the current line to be cancelled on SIGINT.
316      Since some library code is also interested in SIGINT
317      (GNU libtextstyle, via gnulib module fatal-signal), it is better
318      to install it once, rather than to install and uninstall it once
319      in each round of the REP loop below.  */
320   ctrlc_buf_valid = 0;
321   struct sigaction sa;
322   sa.sa_handler = poke_sigint_handler;
323   sa.sa_flags = 0;
324   sigemptyset (&sa.sa_mask);
325   sigaction (SIGINT, &sa, NULL);
326 
327 #if defined HAVE_READLINE_HISTORY_H
328   char *poke_history = NULL;
329   /* Load the user's history file ~/.poke_history, if it exists
330      in the HOME directory.  */
331   char *homedir = getenv ("HOME");
332 
333   if (homedir != NULL)
334     {
335       if (asprintf (&poke_history, "%s/.poke_history", homedir) != -1)
336         {
337           if (access (poke_history, R_OK) == 0)
338             read_history (poke_history);
339         }
340       else
341         poke_history = NULL;
342     }
343 #endif
344   rl_getc_function = poke_getc;
345   rl_completer_quote_characters = "\"";
346   rl_filename_quote_characters = " ";
347   rl_filename_quoting_function = escape_metacharacters;
348 
349   ctrlc_thread = pthread_self ();
350   sigsetjmp (ctrlc_buf, 1);
351   ctrlc_buf_valid = 1;
352 
353   while (!poke_exit_p)
354     {
355       char *prompt;
356       char *line;
357 
358       pk_term_flush ();
359       rl_completion_entry_function = poke_completion_function;
360       prompt = pk_prompt ();
361       line = readline (prompt);
362       free (prompt);
363       if (line == NULL)
364         {
365           /* EOF in stdin (probably Ctrl-D).  */
366           pk_puts ("\n");
367           break;
368         }
369 
370       if (rl_completion_entry_function == doc_completion_function)
371         {
372           for (char *s = line; *s; ++s)
373             {
374               if (*s == SPACE_SUBSTITUTE)
375               *s = ' ';
376             }
377         }
378 
379       /* Ignore empty lines.  */
380       if (*line != '\0')
381         {
382 #if defined HAVE_READLINE_HISTORY_H
383           add_history (line);
384 #endif
385 
386           pk_cmd_exec (line);
387         }
388       free (line);
389     }
390 #if defined HAVE_READLINE_HISTORY_H
391   if (poke_history) {
392     write_history (poke_history);
393     free (poke_history);
394   }
395 #endif
396 
397   ctrlc_buf_valid = 0;
398 }
399 
400 static int saved_point;
401 static int saved_end;
402 
403 void
pk_repl_display_begin(void)404 pk_repl_display_begin (void)
405 {
406   saved_point = rl_point;
407   saved_end = rl_end;
408   rl_point = rl_end = 0;
409 
410   rl_save_prompt ();
411   rl_clear_message ();
412 
413   pk_puts (rl_prompt);
414 }
415 
416 void
pk_repl_display_end(void)417 pk_repl_display_end (void)
418 {
419   pk_term_flush ();
420   rl_restore_prompt ();
421   rl_point = saved_point;
422   rl_end = saved_end;
423   rl_forced_update_display ();
424 }
425 
426 void
pk_repl_insert(const char * str)427 pk_repl_insert (const char *str)
428 {
429   rl_insert_text (str);
430   rl_redisplay ();
431 }
432