readline completion

This commit is contained in:
Vitor Santos Costa 2016-04-19 22:42:44 +01:00
parent 96f03e26e8
commit 3d68f0e06b

View File

@ -25,8 +25,8 @@ static char SccsId[] = "%W% %G%";
*/ */
#include "Yap.h" #include "Yap.h"
#include "Yatom.h"
#include "YapHeap.h" #include "YapHeap.h"
#include "Yatom.h"
#include "yapio.h" #include "yapio.h"
#include <stdlib.h> #include <stdlib.h>
#if HAVE_UNISTD_H #if HAVE_UNISTD_H
@ -53,8 +53,8 @@ static char SccsId[] = "%W% %G%";
#if USE_READLINE #if USE_READLINE
#include <readline/readline.h>
#include <readline/history.h> #include <readline/history.h>
#include <readline/readline.h>
static int ReadlineGetc(int); static int ReadlineGetc(int);
static int ReadlinePutc(int, int); static int ReadlinePutc(int, int);
@ -129,259 +129,362 @@ static char *atom_generator(const char *prefix, int state) {
return s; return s;
} }
static char **prolog_completion(const char *text, int start, int end) { typedef struct chain {
char **matches = NULL; struct chain *next;
char data[2];
} chain_t;
if ((start == 1 && rl_line_buffer[0] == '[') || /* [file */ static char *predicate_enumerate(const char *prefix, int state) {
(start == 2 && strncmp(rl_line_buffer, "['", 2))) CACHE_REGS
matches = rl_completion_matches((char *)text, /* for pre-4.2 */ PredEntry *p;
rl_filename_completion_function); ModEntry m0, *mod;
else AtomEntry *ap;
matches = rl_completion_matches((char *)text, atom_generator);
if (!state) {
return matches; p = NULL;
mod = &m0;
m0.NextME = CurrentModules;
if (mod->AtomOfME == AtomIDB)
mod = mod->NextME;
} else {
Term cmod;
p = LOCAL_SearchPreds;
cmod = (p->ModuleOfPred != PROLOG_MODULE ? p->ModuleOfPred : TermProlog );
mod = Yap_GetModuleEntry(cmod);
}
while (mod) {
// move to next o;
if (p)
p = p->NextPredOfModule;
while (p == NULL) {
mod = mod->NextME;
if (!mod) {
// done
LOCAL_SearchPreds = NULL;
return NULL;
}
if (mod->AtomOfME == AtomIDB)
mod = mod->NextME;
p = mod->PredForME;
}
char *c = RepAtom(ap = NameOfPred(p))->StrOfAE;
if (strlen(c) > strlen(prefix) &&
strstr(c, prefix) == c &&
!(p->PredFlags & HiddenPredFlag)) {
LOCAL_SearchPreds = p;
arity_t ar = p->ArityOfPE;
int l, r;
if (Yap_IsPrefixOp(AbsAtom(ap), &l, &r) && ar == 1) {
return c;
}
size_t sz = strlen(c);
chain_t *el = (chain_t *)malloc(sizeof(chain_t)+sz);
strncpy(LOCAL_FileNameBuf, c, YAP_FILENAME_MAX);
strncat(LOCAL_FileNameBuf, "(", YAP_FILENAME_MAX);
return LOCAL_FileNameBuf;
}
}
LOCAL_SearchPreds = NULL;
return NULL;
} }
void Yap_ReadlineFlush(int sno) { static char *predicate_generator(const char *prefix, int state) {
if (GLOBAL_Stream[sno].status & Tty_Stream_f && char *s = predicate_enumerate(prefix, state);
GLOBAL_Stream[sno].status & Output_Stream_f) {
rl_redisplay(); if (s) {
char *copy = malloc(1 + strlen(s));
if (copy) /* else pretend no completion */
strcpy(copy, s);
s = copy;
}
return s;
} }
}
bool Yap_ReadlinePrompt(StreamDesc *s) { static char **prolog_completion(const char *text, int start, int end) {
if (s->status & Tty_Stream_f) { char **matches = NULL;
s->stream_getc = ReadlineGetc;
if (GLOBAL_Stream[0].status & Tty_Stream_f && if (start == 0 && isalpha(text[0])) {
s->name == GLOBAL_Stream[0].name) int i = 0;
s->stream_putc = ReadlinePutc; while (i < end) {
return true; if (isalnum(text[i]))
i++;
else
break;
}
if (i == end) {
matches = rl_completion_matches((char *)text, predicate_generator);
}
return matches;
} else if (start == 0) {
int i = 0;
const char *p;
while (isblank(text[i++]) && i <= end)
;
p = text + i;
if ((strstr(p,"[") == p) || (strstr(p,"compile(") == p) ||
(strstr(p,"consult(") == p) || (strstr(p,"load_files(") == p) ||
(strstr(p,"reconsult(") == p) || (strstr(p,"use_module(") == p))
matches = rl_completion_matches((char *)text, /* for pre-4.2 */
rl_filename_completion_function);
return matches;
}
int i = end, ch = '\0';
while (i > start) {
ch = text[-i];
if (isalnum(text[i]))
continue;
break;
}
if (islower(ch))
return rl_completion_matches((char *)text, atom_generator);
return NULL;
} }
return false;
}
bool Yap_ReadlineOps(StreamDesc *s) { void Yap_ReadlineFlush(int sno) {
if (s->status & Tty_Stream_f) { if (GLOBAL_Stream[sno].status & Tty_Stream_f &&
if (GLOBAL_Stream[0].status & Tty_Stream_f && GLOBAL_Stream[sno].status & Output_Stream_f) {
is_same_tty(s->file, GLOBAL_Stream[0].file)) rl_redisplay();
s->stream_putc = ReadlinePutc; }
s->stream_getc = ReadlineGetc;
s->status |= Readline_Stream_f;
return true;
} }
return false;
}
static int prolog_complete(int ignore, int key) { bool Yap_ReadlinePrompt(StreamDesc * s) {
if (rl_point > 0 && rl_line_buffer[rl_point - 1] != ' ') { if (s->status & Tty_Stream_f) {
s->stream_getc = ReadlineGetc;
if (GLOBAL_Stream[0].status & Tty_Stream_f &&
s->name == GLOBAL_Stream[0].name)
s->stream_putc = ReadlinePutc;
return true;
}
return false;
}
bool Yap_ReadlineOps(StreamDesc * s) {
if (s->status & Tty_Stream_f) {
if (GLOBAL_Stream[0].status & Tty_Stream_f &&
is_same_tty(s->file, GLOBAL_Stream[0].file))
s->stream_putc = ReadlinePutc;
s->stream_getc = ReadlineGetc;
s->status |= Readline_Stream_f;
return true;
}
return false;
}
static int prolog_complete(int ignore, int key) {
if (rl_point > 0 && rl_line_buffer[rl_point - 1] != ' ') {
#if HAVE_DECL_RL_CATCH_SIGNALS_ /* actually version >= 1.2, or true readline \ #if HAVE_DECL_RL_CATCH_SIGNALS_ /* actually version >= 1.2, or true readline \
*/ */
rl_begin_undo_group(); rl_begin_undo_group();
rl_complete(ignore, key); rl_complete(ignore, key);
if (rl_point > 0 && rl_line_buffer[rl_point - 1] == ' ') { if (rl_point > 0 && rl_line_buffer[rl_point - 1] == ' ') {
rl_delete_text(rl_point - 1, rl_point); rl_delete_text(rl_point - 1, rl_point);
rl_point -= 1; rl_point -= 1;
rl_delete(-1, key); rl_delete(-1, key);
} }
rl_end_undo_group(); rl_end_undo_group();
#endif #endif
} else } else
rl_complete(ignore, key); rl_complete(ignore, key);
return 0; return 0;
} }
bool Yap_InitReadline(Term enable) { bool Yap_InitReadline(Term enable) {
// don't call readline within emacs // don't call readline within emacs
// if (getenv("ËMACS")) // if (getenv("ËMACS"))
// return; // return;
if (enable == TermFalse) if (enable == TermFalse)
return true; return true;
GLOBAL_Stream[StdInStream].u.irl.buf = NULL; GLOBAL_Stream[StdInStream].u.irl.buf = NULL;
GLOBAL_Stream[StdInStream].u.irl.ptr = NULL; GLOBAL_Stream[StdInStream].u.irl.ptr = NULL;
GLOBAL_Stream[StdInStream].status |= Readline_Stream_f; GLOBAL_Stream[StdInStream].status |= Readline_Stream_f;
#if _MSC_VER || defined(__MINGW32__) #if _MSC_VER || defined(__MINGW32__)
rl_instream = stdin; rl_instream = stdin;
#endif #endif
rl_outstream = stderr; rl_outstream = stderr;
using_history(); using_history();
const char *s = Yap_AbsoluteFile("~/.YAP.history", NULL, true); const char *s = Yap_AbsoluteFile("~/.YAP.history", NULL, true);
if (!read_history(s)) { if (read_history(s) != 0) {
FILE *f = fopen(s, "w"); FILE *f = fopen(s, "a");
if (f) { if (f) {
fclose(f); fclose(f);
read_history(s); }
} }
} rl_readline_name = "Prolog";
rl_readline_name = "Prolog"; rl_attempted_completion_function = prolog_completion;
rl_attempted_completion_function = prolog_completion;
#ifdef HAVE_RL_COMPLETION_FUNC_T #ifdef HAVE_RL_COMPLETION_FUNC_T
rl_add_defun("prolog-complete", prolog_complete, '\t'); rl_add_defun("prolog-complete", prolog_complete, '\t');
#else #else
rl_add_defun("prolog-complete", (void *)prolog_complete, '\t'); rl_add_defun("prolog-complete", (void *)prolog_complete, '\t');
#endif #endif
return Yap_ReadlineOps(GLOBAL_Stream + StdInStream); return Yap_ReadlineOps(GLOBAL_Stream + StdInStream);
}
static bool getLine(int inp, int out) {
CACHE_REGS
rl_instream = GLOBAL_Stream[inp].file;
rl_outstream = GLOBAL_Stream[out].file;
const unsigned char *myrl_line;
StreamDesc *s = GLOBAL_Stream + inp;
if (!(s->status & Tty_Stream_f))
return false;
/* window of vulnerability opened */
fflush(NULL);
LOCAL_PrologMode |= ConsoleGetcMode;
if (LOCAL_newline) { // no output so far
myrl_line = (unsigned char *)readline(LOCAL_Prompt);
} else {
myrl_line = (unsigned char *)readline(NULL);
} }
/* Do it the gnu way */
if (LOCAL_PrologMode & InterruptMode) {
Yap_external_signal(0, YAP_INT_SIGNAL);
LOCAL_PrologMode &= ~ConsoleGetcMode;
if (LOCAL_PrologMode & AbortMode) {
Yap_Error(ABORT_EVENT, TermNil, "");
LOCAL_ErrorMessage = "Abort";
return console_post_process_eof(s);
}
} else {
LOCAL_PrologMode &= ~ConsoleGetcMode;
LOCAL_newline = true;
}
strncpy(LOCAL_Prompt, RepAtom(LOCAL_AtPrompt)->StrOfAE, MAX_PROMPT);
/* window of vulnerability closed */
if (myrl_line == NULL)
return false;
if (myrl_line[0] != '\0' && myrl_line[1] != '\0') {
add_history((char *)myrl_line);
append_history(1, history_file);
}
s->u.irl.ptr = s->u.irl.buf = myrl_line;
return true;
}
static int ReadlinePutc(int sno, int ch) { static bool getLine(int inp, int out) {
CACHE_REGS CACHE_REGS
StreamDesc *s = &GLOBAL_Stream[sno]; rl_instream = GLOBAL_Stream[inp].file;
#if MAC || _MSC_VER || defined(__MINGW32__) rl_outstream = GLOBAL_Stream[out].file;
if (ch == 10) { const unsigned char *myrl_line;
putc('\n', s->file); StreamDesc *s = GLOBAL_Stream + inp;
} else
#endif
putc(ch, s->file);
console_count_output_char(ch, s);
if (ch == 10) {
Yap_ReadlineFlush(sno);
LOCAL_newline = true;
}
return ((int)ch);
}
/** if (!(s->status & Tty_Stream_f))
@brief reading from the console is complicated because we need to return false;
know whether to prompt and so on...
EOF must be handled by resetting the file. /* window of vulnerability opened */
*/ fflush(NULL);
static int ReadlineGetc(int sno) { LOCAL_PrologMode |= ConsoleGetcMode;
StreamDesc *s = &GLOBAL_Stream[sno]; if (LOCAL_newline) { // no output so far
int ch; myrl_line = (unsigned char *)readline(LOCAL_Prompt);
bool fetch = (s->u.irl.buf == NULL); } else {
myrl_line = (unsigned char *)readline(NULL);
if (!fetch || getLine(sno, StdErrStream)) {
const unsigned char *ttyptr = s->u.irl.ptr++, *myrl_line = s->u.irl.buf;
ch = *ttyptr;
if (ch == '\0') {
ch = '\n';
free((void *)myrl_line);
s->u.irl.ptr = s->u.irl.buf = NULL;
} }
} else { /* Do it the gnu way */
return EOF; if (LOCAL_PrologMode & InterruptMode) {
} Yap_external_signal(0, YAP_INT_SIGNAL);
return console_post_process_read_char(ch, s); LOCAL_PrologMode &= ~ConsoleGetcMode;
} if (LOCAL_PrologMode & AbortMode) {
Yap_Error(ABORT_EVENT, TermNil, "");
/** LOCAL_ErrorMessage = "Abort";
@brief Yap_ReadlinePeekChar peeks the next char from the return console_post_process_eof(s);
readline buffer, but does not actually grab it. }
} else {
The idea is to take advantage of the buffering. Special care must be taken LOCAL_PrologMode &= ~ConsoleGetcMode;
with EOF, though.
*/
Int Yap_ReadlinePeekChar(int sno) {
StreamDesc *s = &GLOBAL_Stream[sno];
int ch;
if (s->u.irl.buf) {
const unsigned char *ttyptr = s->u.irl.ptr;
ch = *ttyptr;
if (ch == '\0') {
ch = '\n';
}
return ch;
}
if (getLine(sno, StdErrStream)) {
CACHE_REGS
ch = s->u.irl.ptr[0];
if (ch == '\0') {
ch = '\n';
}
if (ch == '\n') {
LOCAL_newline = true; LOCAL_newline = true;
} else {
LOCAL_newline = false;
} }
} else { strncpy(LOCAL_Prompt, RepAtom(LOCAL_AtPrompt)->StrOfAE, MAX_PROMPT);
return EOF; /* window of vulnerability closed */
if (myrl_line == NULL)
return false;
if (myrl_line[0] != '\0' && myrl_line[1] != '\0') {
add_history((char *)myrl_line);
write_history(history_file);
fflush(NULL);
}
s->u.irl.ptr = s->u.irl.buf = myrl_line;
return true;
} }
return ch;
}
int Yap_ReadlineForSIGINT(void) { static int ReadlinePutc(int sno, int ch) {
CACHE_REGS CACHE_REGS
int ch; StreamDesc *s = &GLOBAL_Stream[sno];
StreamDesc *s = &GLOBAL_Stream[StdInStream]; #if MAC || _MSC_VER || defined(__MINGW32__)
const unsigned char *myrl_line = s->u.irl.buf; if (ch == 10) {
putc('\n', s->file);
} else
#endif
putc(ch, s->file);
console_count_output_char(ch, s);
if (ch == 10) {
Yap_ReadlineFlush(sno);
LOCAL_newline = true;
}
return ((int)ch);
}
if ((LOCAL_PrologMode & ConsoleGetcMode) && myrl_line != NULL) { /**
ch = myrl_line[0]; @brief reading from the console is complicated because we need to
free((void *)myrl_line); know whether to prompt and so on...
myrl_line = NULL;
return ch; EOF must be handled by resetting the file.
} else { */
myrl_line = (const unsigned char *)readline("Action (h for help): "); static int ReadlineGetc(int sno) {
if (!myrl_line) { StreamDesc *s = &GLOBAL_Stream[sno];
ch = EOF; int ch;
return ch; bool fetch = (s->u.irl.buf == NULL);
if (!fetch || getLine(sno, StdErrStream)) {
const unsigned char *ttyptr = s->u.irl.ptr++, *myrl_line = s->u.irl.buf;
ch = *ttyptr;
if (ch == '\0') {
ch = '\n';
free((void *)myrl_line);
s->u.irl.ptr = s->u.irl.buf = NULL;
}
} else { } else {
return EOF;
}
return console_post_process_read_char(ch, s);
}
/**
@brief Yap_ReadlinePeekChar peeks the next char from the
readline buffer, but does not actually grab it.
The idea is to take advantage of the buffering. Special care must be taken
with EOF, though.
*/
Int Yap_ReadlinePeekChar(int sno) {
StreamDesc *s = &GLOBAL_Stream[sno];
int ch;
if (s->u.irl.buf) {
const unsigned char *ttyptr = s->u.irl.ptr;
ch = *ttyptr;
if (ch == '\0') {
ch = '\n';
}
return ch;
}
if (getLine(sno, StdErrStream)) {
CACHE_REGS
ch = s->u.irl.ptr[0];
if (ch == '\0') {
ch = '\n';
}
if (ch == '\n') {
LOCAL_newline = true;
} else {
LOCAL_newline = false;
}
} else {
return EOF;
}
return ch;
}
int Yap_ReadlineForSIGINT(void) {
CACHE_REGS
int ch;
StreamDesc *s = &GLOBAL_Stream[StdInStream];
const unsigned char *myrl_line = s->u.irl.buf;
if ((LOCAL_PrologMode & ConsoleGetcMode) && myrl_line != NULL) {
ch = myrl_line[0]; ch = myrl_line[0];
free((void *)myrl_line); free((void *)myrl_line);
myrl_line = NULL; myrl_line = NULL;
return ch; return ch;
} else {
myrl_line = (const unsigned char *)readline("Action (h for help): ");
if (!myrl_line) {
ch = EOF;
return ch;
} else {
ch = myrl_line[0];
free((void *)myrl_line);
myrl_line = NULL;
return ch;
}
} }
} }
}
static Int has_readline(USES_REGS1) { static Int has_readline(USES_REGS1) {
#if USE_READLINE #if USE_READLINE
return true; return true;
#else #else
return false; return false;
#endif #endif
} }
void Yap_InitReadlinePreds(void) { void Yap_InitReadlinePreds(void) {
Yap_InitCPred("$has_readline", 0, has_readline, Yap_InitCPred("$has_readline", 0, has_readline,
SafePredFlag | HiddenPredFlag); SafePredFlag | HiddenPredFlag);
} }
#else #else
bool Yap_InitReadline(Term enable) { bool Yap_InitReadline(Term enable) {