source: project/release/4/readline/trunk/readline-egg.c @ 32069

Last change on this file since 32069 was 32069, checked in by Alexej Magura, 6 years ago

adds versions 2.3 and 2.4

File size: 13.7 KB
Line 
1// (Readline is GPLed, so that makes this file GPLed too, because this
2// file will only run if it's linked against readline.)
3//
4// Copyright (c) 2002 Tony Garnock-Jones
5// Copyright (c) 2006 Heath Johns (paren bouncing and auto-completion code)
6// Copyright (c) 2014 Alexej Magura (added a lot of history functions and more)
7//
8// This program is free software; you can redistribute it and/or modify
9// it under the terms of the GNU General Public License as published by
10// the Free Software Foundation; either version 2 of the License, or
11// (at your option) any later version.
12//
13// This program is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16// GNU General Public License for more details.
17//
18// You should have received a copy of the GNU General Public License
19// along with this program; if not, write to the Free Software
20// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
22#include <stdlib.h>
23#include <stdio.h>
24#include <string.h>
25#include <sys/poll.h>
26#include <readline/readline.h>
27#include <readline/history.h>
28#include <stdbool.h>
29
30#if _HAVE_LIBBSD // then we can use strlcat and strlcpy
31#include <bsd/string.h>
32#define _CHK_READLINE_EGG_STRCAT(dst_str, src_str) strlcat((dst_str), (src_str), sizeof (dst_str))
33//#define _CHK_READLINE_EGG_STRCPY(dst_str, src_str) strlcpy(dst_str, src_str, sizeof dst_str)
34#define _CHK_READLINE_EGG_STRCPY(dst_str, src_str) _CHK_READLINE_EGG_STRCAT(dst_str, src_str)
35
36#define _CHK_READLINE_EGG_STRCAT_FN_USED "strlcat"
37#define _CHK_READLINE_EGG_STRCPY_FN_USED _CHK_READLINE_EGG_STRCAT_FN_USED
38#else
39#define _CHK_READLINE_EGG_STRCAT(dst_str, src_str) strncat(dst_str, src_str, (sizeof dst_str - strlen(dst_str) - 1))
40#define _CHK_READLINE_EGG_STRCPY(dst_str, src_str) _CHK_READLINE_EGG_STRCAT(dst_str, src_str)
41
42#define _CHK_READLINE_EGG_STRCAT_FN_USED "strncat"
43#define _CHK_READLINE_EGG_STRCPY_FN_USED _CHK_READLINE_EGG_STRCAT_FN_USED
44#endif
45
46#define _CHK_READLINE_EGG_INCR_ONTOK(token) \
47  do { \
48    switch ((token)) { \
49      case '(': \
50                ++balance.paren.open; \
51      break; \
52      case ')': \
53                ++balance.paren.close; \
54      break; \
55      case '[': \
56                ++balance.brace.open; \
57      break; \
58      case ']': \
59                ++balance.brace.close; \
60      break; \
61      case '"': \
62                charc % 2 == 0 || ++balance.quote; \
63      break; \
64    } \
65  } while(0)
66
67#define _CHK_READLINE_EGG_SETPROP_3(prop1, prop2, prop3, val) (prop1) = (val); (prop2) = (val); (prop3) = (val)
68
69#define _CHK_READLINE_EGG_CLEAR_SUB(sub_struct) \
70  do { \
71    (sub_struct).close = 0; \
72    (sub_struct).open = 0; \
73  } while(0)
74
75#define _CHK_READLINE_EGG_OR_3(val1, val2, val3) ((val1) || (val2) || (val3))
76#define _CHK_READLINE_EGG_NOR_3(val1, val2, val3) !(_CHK_READLINE_EGG_OR_3((val1), (val2), (val3)))
77
78#define _CHK_READLINE_EGG_IF_THEN_SET(exp, sub_struct, val) \
79  do { \
80    if ((exp)) { \
81      (sub_struct).open = (val); \
82      (sub_struct).close = (val); \
83    } \
84  } while(0)
85
86#if 0 // NOTE change this to 1 to enable debug messages
87#define _CHK_READLINE_EGG_DEBUG(format, ...) fprintf(stderr, format, __VA_ARGS__)
88#define _CHK_READLINE_EGG_DEBUG_LIT(format, ...) fprintf(stderr, format, #__VA_ARGS__)
89#else
90#define _CHK_READLINE_EGG_DEBUG(format, ...)
91#define _CHK_READLINE_EGG_DEBUG_LIT(format, ...)
92#endif
93
94struct balance_t {
95  int paren[3]; // 0 -> total, 1 -> open, 2 -> close
96  int brace[3]; // 0 -> total, 1 -> open, 2 -> close
97  int quote;
98} balance;
99
100static char *gnu_readline_buf = NULL;
101static int gnu_readline_paren_balance = 0;
102static int gnu_readline_bounce_ms = 500;
103static int gnu_history_newlines = 0;
104#if 0// NOT YET IMPLEMENTED
105static struct gnu_readline_current_paren_color_t {
106  int enabled;
107  char *with_match;
108  char *without_match;
109} paren_colors;
110#endif
111
112/* XXX readline already provides paren-bouncing:
113   (gnu-readline-parse-and-bind "set blink-matching-paren on")
114
115   XXX it however does ____NOT____ work.  built-in: (line 'a) ')
116                                                    ^ -------- ^ ; WRONG
117   ~ Alexej
118*/
119// >>>1
120inline char *
121strtail_addr(const char *string)
122{
123  return (char *)&string[strlen(string) - 1];
124}
125
126inline char *
127strend_addr(const char *string)
128{
129  return (char *)&string[strlen(string)];
130}
131
132inline char
133prev_char(char *stringp, char *string)
134{
135  char result = '\0';
136  if (stringp != string) {
137    --stringp;
138    result = *stringp;
139    ++stringp;
140  } else {
141    result = *stringp;
142  }
143  return result;
144}
145
146inline bool
147ptr_strhead(char *stringp, char *string)
148{
149  return stringp == string;
150}
151
152inline bool
153ptr_not_strhead(char *stringp, char *string)
154{
155  return !ptr_strhead(stringp, string);
156}
157// <<<1
158inline int /* working */
159quote_in_string(char *string)
160{
161  char *str_ptr = strend_addr(string);
162
163  if (str_ptr == string)
164    return 0;
165
166  do {
167    if (ptr_not_strhead(str_ptr, string) && prev_char(str_ptr, string) == '\\')
168      continue;
169
170    if (*str_ptr == '"') {
171      ++balance.quote;
172    }
173  } while (str_ptr-- != string);
174
175  if (balance.quote == 0)
176    return -1;
177  _CHK_READLINE_EGG_DEBUG("return balance.quote: %d\n", balance.quote);
178  return balance.quote % 2;
179}
180
181inline void
182clear_parbar(char token)
183{
184  int idx = 0;
185  for (; idx < 3; ++idx) {
186    if (token == '(' || token == ')')
187      balance.paren[idx] = 0;
188    if (token == '[' || token == ']')
189      balance.brace[idx] = 0;
190  }
191}
192
193inline int /* not working */
194parbar_in_string(char *string, char add_token)
195{
196  int *idxp = NULL;
197  char sub_token = '\0';
198  if (add_token == '(') {
199    idxp = balance.paren;
200    sub_token = ')';
201  } else if (add_token == '[') {
202    idxp = balance.brace;
203    sub_token = ']';
204  }
205
206  char *str_ptr = strend_addr(string);
207
208  if (str_ptr == string)
209    return 0;
210
211  do {
212    if (ptr_not_strhead(str_ptr, string) && prev_char(str_ptr, string) == '\\')
213      continue;
214
215    _CHK_READLINE_EGG_DEBUG("balance.quote: %d\n", balance.quote);
216    if (*str_ptr == add_token && balance.quote < 1) {
217      ++(*idxp); // increment total
218      ++(*(++idxp)); // move to open, and increment
219      --idxp; // move back to total
220    } else if (*str_ptr == sub_token && balance.quote < 1) {
221      --(*idxp); // deincrement total
222      ++idxp; // move to open
223      ++(*(++idxp)); // move to close and increment
224      --idxp; // move back to open
225      --idxp; // move back to total
226    }
227  } while (str_ptr-- != string);
228  return *idxp;
229}
230
231int
232highlight_paren()
233{
234  char *rl_ptr = rl_line_buffer;
235  while (rl_ptr != &rl_line_buffer[rl_point]) { ++rl_ptr; }
236  _CHK_READLINE_EGG_DEBUG("%s\n", rl_ptr);
237  return 0;
238}
239
240////\\\\//// Paren Bouncing ////\\\\////
241
242/* Returns: (if positive) the position of the matching paren
243            (if negative) the number of unmatched closing parens */
244int
245gnu_readline_skip(int pos, char open_key, char close_key)
246{
247  while (--pos > -1) {
248    if (pos > 0 && rl_line_buffer[pos - 1] == '\\') {
249      continue;
250    } else if (rl_line_buffer[pos] == open_key) {
251      return pos;
252    } else if (rl_line_buffer[pos] == close_key) {
253      pos = gnu_readline_skip(pos, open_key, close_key);
254    } else if (rl_line_buffer[pos] == '"') {
255      pos = gnu_readline_skip(pos, '"', '"');
256    }
257  }
258  return pos;
259}
260
261// Finds the matching paren (starting from just left of the cursor)
262int
263gnu_readline_find_match(char key)
264{
265  if (key == ')')
266    return gnu_readline_skip(rl_point - 1, '(', ')');
267  else if (key == ']')
268    return gnu_readline_skip(rl_point - 1, '[', ']');
269  return 0; 
270}
271
272// Delays, but returns early if key press occurs
273void
274gnu_readline_timid_delay(int ms)
275{
276  struct pollfd pfd;
277
278  pfd.fd = fileno(rl_instream);
279  pfd.events = POLLIN || POLLPRI;
280  pfd.revents = 0;
281
282  poll(&pfd, 1, ms);
283}
284
285// Bounces the cursor to the matching paren for a while
286int
287gnu_readline_paren_bounce(int count, int key)
288{
289  int insert_success;
290  int old_point;
291  int matching;
292
293  if (gnu_readline_bounce_ms == 0)
294    return 0;
295
296  // Write the just entered paren out first
297  insert_success = rl_insert(count, key);
298  if (insert_success != 0)
299    return insert_success;
300  rl_redisplay();
301
302  // Need at least two chars to bounce...
303  if (rl_point < 2) // rl_point set to next char (implicit +1)
304    return 0;
305
306  // If it's an escaped paren, don't bounce...
307  if (rl_line_buffer[rl_point - 2] == '\\')
308    return 0;
309
310  // Bounce
311  old_point = rl_point;
312  matching = gnu_readline_find_match(key);
313  if (matching < 0)
314    return 0;
315  else
316    rl_point = matching;
317  rl_redisplay();
318  gnu_readline_timid_delay(gnu_readline_bounce_ms);
319  rl_point = old_point;
320
321  return 0;
322}
323
324#if 0
325void paren_match_highlights(char *match_color, char *no_match_color)
326{
327  paren_colors.with_match = (match_color[0] == '\0'
328      ? "\x1b[36m"
329      : match_color);
330  paren_colors.without_match = (no_match_color[0] == '\0'
331      ? "\x1b[35m"
332      : no_match_color);
333}
334#endif
335
336////\\\\//// Tab Completion ////\\\\////
337
338// Prototype for callback into scm
339C_word
340gnu_readline_scm_complete(char *, int, int);
341
342// Gets called (repeatedly) when readline tries to do a completion
343// FIXME, I use _strncpy_, but I should probably use _strlcpy_ or _strncat_
344char *gnu_readline_tab_complete(const char *text, int status) {
345  C_word result;
346  char *str;
347  int len;
348  char *copied_str;
349
350  /* All of this is for older versions of chicken (< 2.3), which don't
351     reliably null-terminate strings */
352
353  // Get scheme string for possible completion via callback
354  result = gnu_readline_scm_complete((char *)text, strlen(text), status);
355
356  if (result == C_SCHEME_FALSE)
357    return NULL;
358
359  // Convert into C types
360  str = C_c_string(result);
361  len = C_num_to_int(C_i_string_length(result));
362
363  if (len == 0)
364    return NULL;
365
366  // Copy (note: the readline lib frees this copy)
367  copied_str = (char *)malloc(len + 1);
368  strncpy(copied_str, str, len); /* XXX this probably isn't the best, although it is safe
369                                    thanks to the next line, way to copy these strings. */
370  copied_str[len] = '\0';
371  return copied_str;
372}
373
374/*  grants access to the gnu_history_newlines variable.
375 *
376 *  XXX using a `define-foreign-variable' to gain access to gnu_history_newlines won't work as expected.
377 *  I don't _know_ this, but I suspect it's because it only grabs a snapshot of the variable and is not
378 *  actually binding itself _to the variable_
379 */
380int
381gnu_history_new_lines()
382{
383  return gnu_history_newlines;
384}
385
386int
387gnu_readline_append_history(char *filename)
388{
389  return append_history(gnu_history_newlines, filename);
390}
391
392#if 0
393void gnu_readline_highlight_matches()
394{
395  _CHK_READLINE_EGG_DEBUG("match-position: %d\n", find_match(')'));
396}
397#endif
398#if 0
399char *gnu_make_arrow_code()
400{};
401#endif
402
403// Set everything up
404void
405gnu_readline_init()
406{
407  using_history();
408  rl_bind_key(')', gnu_readline_paren_bounce);
409  rl_bind_key(']', gnu_readline_paren_bounce);
410#if 0
411  rl_bind_keyseq("[D", highlight_paren);
412#endif
413  rl_completion_entry_function = &gnu_readline_tab_complete;
414  rl_variable_bind("rl_catch_signals", 0);
415  rl_clear_signals();
416  rl_set_signals();
417  rl_completer_quote_characters = "\"";
418}
419
420// Called from scheme to get user input
421char *
422gnu_readline_readline(char *prompt, char *prompt2)
423{
424  char *empty_prompt;
425  int prompt_len;
426  HIST_ENTRY *entry;
427
428  if (gnu_readline_buf != NULL) {
429    free(gnu_readline_buf);
430    gnu_readline_buf = NULL;
431  }
432 
433  if (!(balance.quote || balance.paren[0] || balance.brace[0]))
434    gnu_readline_buf = readline(prompt);
435  else
436    gnu_readline_buf = readline(prompt2);
437
438  if (gnu_readline_buf != NULL && *gnu_readline_buf != '\0') {
439    entry = history_get(history_base + history_length - 1);
440    if (entry == NULL || strcmp(entry->line, gnu_readline_buf) != 0) {
441      add_history(gnu_readline_buf);
442      ++gnu_history_newlines;
443    }
444  }
445
446  if (strlen(rl_line_buffer) > 0) {
447    int adx = quote_in_string(rl_line_buffer);
448    _CHK_READLINE_EGG_DEBUG("quote_in_string: %d\n", adx);
449    int bdx = parbar_in_string(rl_line_buffer, '(');
450    int cdx = parbar_in_string(rl_line_buffer, '[');
451    balance.quote = (adx == -1 ? 0 : adx);
452    if (bdx == -1)
453      clear_parbar('(');
454    if (cdx == -1)
455      clear_parbar('[');
456  }
457  return (gnu_readline_buf);
458}
459
460void
461gnu_readline_signal_cleanup()
462{
463  balance.quote = 0;
464  free(gnu_readline_buf);
465  gnu_readline_buf = NULL;
466  rl_free_line_state();
467  rl_cleanup_after_signal();
468}
469
470char *
471gnu_history_get(int ind, int time)
472{
473  HIST_ENTRY *entry = NULL;
474 
475  entry = history_get(ind);
476
477  if (entry == NULL)
478    return NULL;
479  return (time == 0 ? entry->line : entry->timestamp);
480}
481
482char *
483gnu_history_entry(int ind, int time)
484{
485  HIST_ENTRY *entry = NULL;
486
487  if (ind == 0)
488    entry = current_history();
489  else if (ind < 0)
490    entry = previous_history();
491  else if (ind > 0)
492    entry = next_history();
493
494  if (entry == NULL)
495    return NULL;
496  return (time == 0 ? entry->line : entry->timestamp);
497}
498
499/* safely concatenates the history_list's entry strings and returns them via a pointer */
500char *
501gnu_history_list() /* may look a bit messy, but it seems to work great ;D */
502{
503  HIST_ENTRY **hist_list = history_list();
504  char result_buf[BUFSIZ];
505  char *result_buf_ptr = result_buf;
506  int idx;
507
508  *result_buf = '\0';
509
510  if (hist_list == NULL)
511    return NULL;
512  _CHK_READLINE_EGG_DEBUG("_CHK_READLINE_EGG_STRCPY: %s\n", _CHK_READLINE_EGG_STRCPY_FN_USED);
513  _CHK_READLINE_EGG_DEBUG("_CHK_READLINE_EGG_STRCAT: %s\n", _CHK_READLINE_EGG_STRCAT_FN_USED);
514  _CHK_READLINE_EGG_STRCPY(result_buf, hist_list[0]->line);
515  _CHK_READLINE_EGG_STRCAT(result_buf, "\n");
516
517  for (idx = 1; idx < history_length; ++idx) {
518    _CHK_READLINE_EGG_STRCAT(result_buf, hist_list[idx]->line);
519    _CHK_READLINE_EGG_STRCAT(result_buf, "\n");
520  }
521  return result_buf_ptr;
522}
523
524char *
525gnu_history_timeat_current()
526{
527  HIST_ENTRY *entry = NULL;
528  entry = current_history();
529
530  if (entry == NULL || entry->timestamp[0] == '\0')
531    return NULL;
532  return entry->timestamp;
533}
534
535int
536gnu_history_list_length()
537{
538  return history_length;
539}
Note: See TracBrowser for help on using the repository browser.