Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

571 lines (501 sloc) 16.193 kb
/*
repl-readline.c: Julia REPL with readline support.
Copyright (C) 2009-2011, Jeff Bezanson, Stefan Karpinski, Viral B. Shah.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "repl.h"
extern int asprintf(char **strp, const char *fmt, ...);
#include <readline/readline.h>
#include <readline/history.h>
char jl_prompt_color[] = "\001\033[1m\033[32m\002julia> \001\033[0m\033[1m\002";
char prompt_plain[] = "julia> ";
char *prompt_string = prompt_plain;
int prompt_length;
int disable_history;
static char *history_file = NULL;
static jl_value_t *rl_ast = NULL;
DLLEXPORT void jl_enable_color(void)
{
prompt_string = jl_prompt_color;
}
// yes, readline uses inconsistent indexing internally.
#define history_rem(n) remove_history(n-history_base)
static void init_history(void) {
using_history();
char *home = getenv("HOME");
if (!home) return;
if (disable_history) return;
asprintf(&history_file, "%s/.julia_history", home);
struct stat stat_info;
if (!stat(history_file, &stat_info)) {
read_history(history_file);
for (;;) {
HIST_ENTRY *entry = history_get(history_base);
if (entry && isspace(entry->line[0]))
free_history_entry(history_rem(history_base));
else break;
}
int i, j, k;
for (i=1 ;; i++) {
HIST_ENTRY *first = history_get(i);
if (!first) break;
int length = strlen(first->line)+1;
for (j = i+1 ;; j++) {
HIST_ENTRY *child = history_get(j);
if (!child || !isspace(child->line[0])) break;
length += strlen(child->line)+1;
}
if (j == i+1) continue;
first->line = (char*)realloc(first->line, length);
char *p = strchr(first->line, '\0');
for (k = i+1; k < j; k++) {
*p = '\n';
p = stpcpy(p+1, history_get(i+1)->line);
free_history_entry(history_rem(i+1));
}
}
} else if (errno == ENOENT) {
write_history(history_file);
} else {
ios_printf(ios_stderr, "history file error: %s\n", strerror(errno));
exit(1);
}
}
static int last_hist_is_temp = 0;
static int last_hist_offset = -1;
static void add_history_temporary(char *input) {
if (!input || !*input) return;
if (last_hist_is_temp) {
history_rem(history_length);
last_hist_is_temp = 0;
}
last_hist_offset = -1;
add_history(input);
last_hist_is_temp = 1;
}
static void add_history_permanent(char *input) {
if (!input || !*input) return;
if (last_hist_is_temp) {
history_rem(history_length);
last_hist_is_temp = 0;
}
last_hist_offset = -1;
HIST_ENTRY *entry = history_get(history_length);
if (entry && !strcmp(input, entry->line)) return;
last_hist_offset = where_history();
add_history(input);
if (history_file)
append_history(1, history_file);
}
static int line_start(int point) {
if (!point) return 0;
int i = point-1;
for (; i; i--) if (rl_line_buffer[i] == '\n') return i+1;
return rl_line_buffer[i] == '\n' ? 1 : 0;
}
static int line_end(int point) {
char *nl = strchr(rl_line_buffer + point, '\n');
if (!nl) return rl_end;
return nl - rl_line_buffer;
}
static int strip_initial_spaces = 0;
static int spaces_suppressed = 0;
static void reset_indent(void) {
strip_initial_spaces = 0;
spaces_suppressed = 0;
}
// TODO: is it appropriate to call this on the int values readline uses?
static int jl_word_char(uint32_t wc)
{
return strchr(rl_completer_word_break_characters, wc) == NULL;
}
static int newline_callback(int count, int key) {
if (!rl_point) return 0;
spaces_suppressed = 0;
rl_insert_text("\n");
int i;
for (i = 0; i < prompt_length; i++)
rl_insert_text(" ");
return 0;
}
static int return_callback(int count, int key) {
static int consecutive_returns = 0;
if (rl_point > prompt_length && rl_point == rl_end &&
rl_line_buffer[rl_point-prompt_length-1] == '\n')
consecutive_returns++;
else
consecutive_returns = 0;
add_history_temporary(rl_line_buffer);
rl_ast = jl_parse_input_line(rl_line_buffer);
rl_done = !rl_ast || !jl_is_expr(rl_ast) ||
(((jl_expr_t*)rl_ast)->head != jl_continue_sym) ||
consecutive_returns > 1;
if (!rl_done) {
newline_callback(count, key);
} else {
reset_indent();
rl_point = rl_end;
rl_redisplay();
}
return 0;
}
static int suppress_space(void) {
int i;
for (i = line_start(rl_point); i < rl_point; i++)
if (rl_line_buffer[i] != ' ') return 0;
if (spaces_suppressed < strip_initial_spaces) return 1;
return 0;
}
static int space_callback(int count, int key) {
if (!rl_point) strip_initial_spaces++;
else if (suppress_space()) spaces_suppressed++;
else rl_insert_text(" ");
return 0;
}
static int tab_callback(int count, int key) {
if (!rl_point) {
strip_initial_spaces += tab_width;
return 0;
}
int i;
for (i = line_start(rl_point); i < rl_point; i++) {
if (rl_line_buffer[i] != ' ') {
// do tab completion
i = rl_point;
rl_complete_internal('!');
if (i < rl_point && rl_line_buffer[rl_point-1] == ' ') {
rl_delete_text(rl_point-1, rl_point);
rl_point = rl_point-1;
}
return 0;
}
}
// indent to next tab stop
if (suppress_space()) {
spaces_suppressed += tab_width;
} else {
i = line_start(rl_point) + prompt_length;
do { rl_insert_text(" "); } while ((rl_point - i) % tab_width);
}
return 0;
}
static int line_start_callback(int count, int key) {
reset_indent();
int start = line_start(rl_point);
int flush_left = rl_point == 0 || rl_point == start + prompt_length;
rl_point = flush_left ? 0 : (!start ? start : start + prompt_length);
return 0;
}
static int line_end_callback(int count, int key) {
reset_indent();
int end = line_end(rl_point);
int flush_right = rl_point == end;
rl_point = flush_right ? rl_end : end;
return 0;
}
static int line_kill_callback(int count, int key) {
reset_indent();
int end = line_end(rl_point);
int flush_right = rl_point == end;
int kill = flush_right ? end + prompt_length + 1 : end;
if (kill > rl_end) kill = rl_end;
rl_kill_text(rl_point, kill);
return 0;
}
static int backspace_callback(int count, int key) {
reset_indent();
if (!rl_point) return 0;
int i = line_start(rl_point), j = rl_point, k;
if (!i || rl_point <= i + prompt_length) goto backspace;
for (k = i; k < rl_point; k++)
if (rl_line_buffer[k] != ' ') goto backspace;
//unindent:
k = i + prompt_length;
do { rl_point--; } while ((rl_point - k) % tab_width);
goto finish;
backspace:
do {
rl_point = (i == 0 || rl_point-i > prompt_length) ? rl_point-1 : i-1;
} while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point]) && rl_point > i-1);
finish:
rl_delete_text(rl_point, j);
return 0;
}
static int delete_callback(int count, int key) {
reset_indent();
int j = rl_point;
do {
j += (rl_line_buffer[j] == '\n') ? prompt_length+1 : 1;
} while (locale_is_utf8 && !isutf(rl_line_buffer[j]));
if (rl_end < j) j = rl_end;
rl_delete_text(rl_point, j);
return 0;
}
static int left_callback(int count, int key) {
reset_indent();
if (rl_point > 0) {
int i = line_start(rl_point);
do {
rl_point = (i == 0 || rl_point-i > prompt_length) ? rl_point-1 : i-1;
} while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point]) && rl_point > i-1);
}
return 0;
}
static int right_callback(int count, int key) {
reset_indent();
do {
rl_point += (rl_line_buffer[rl_point] == '\n') ? prompt_length+1 : 1;
} while (locale_is_utf8 && !isutf(rl_line_buffer[rl_point]));
if (rl_end < rl_point) rl_point = rl_end;
return 0;
}
static int up_callback(int count, int key) {
reset_indent();
int i = line_start(rl_point);
if (i > 0) {
int j = line_start(i-1);
if (j == 0) rl_point -= prompt_length;
rl_point += j - i;
if (rl_point >= i) rl_point = i - 1;
} else {
last_hist_offset = -1;
rl_get_previous_history(count, key);
rl_point = line_end(0);
}
return 0;
}
static int down_callback(int count, int key) {
reset_indent();
int j = line_end(rl_point);
if (j < rl_end) {
int i = line_start(rl_point);
if (i == 0) rl_point += prompt_length;
rl_point += j - i + 1;
int k = line_end(j+1);
if (rl_point > k) rl_point = k;
return 0;
} else {
if (last_hist_offset >= 0) {
history_set_pos(last_hist_offset);
last_hist_offset = -1;
}
return rl_get_next_history(count, key);
}
}
static int callback_en=0;
void jl_input_line_callback(char *input)
{
int end=0, doprint=1;
if (!input || ios_eof(ios_stdin)) {
end = 1;
rl_ast = NULL;
} else if (!rl_ast) {
// In vi mode, it's possible for this function to be called w/o a
// previous call to return_callback.
rl_ast = jl_parse_input_line(rl_line_buffer);
}
if (rl_ast != NULL) {
doprint = !ends_with_semicolon(input);
add_history_permanent(input);
ios_putc('\n', ios_stdout);
free(input);
}
callback_en = 0;
rl_callback_handler_remove();
handle_input(rl_ast, end, doprint);
rl_ast = NULL;
}
char *read_expr(char *prompt)
{
rl_ast = NULL;
return readline(prompt);
}
static int common_prefix(const char *s1, const char *s2)
{
int i = 0;
while (s1[i] && s2[i] && s1[i] == s2[i])
i++;
return i;
}
static void symtab_search(jl_sym_t *tree, int *pcount, ios_t *result,
jl_module_t *module, const char *str,
const char *prefix, int plen)
{
do {
if (common_prefix(prefix, tree->name) == plen &&
jl_boundp(module, tree)) {
ios_puts(str, result);
ios_puts(tree->name + plen, result);
ios_putc('\n', result);
(*pcount)++;
}
if (tree->left)
symtab_search(tree->left, pcount, result, module, str, prefix, plen);
tree = tree->right;
} while (tree != NULL);
}
static jl_module_t *
find_submodule_named(jl_module_t *module, const char *name)
{
jl_sym_t *s = jl_symbol_lookup(name);
if (!s) return NULL;
jl_binding_t *b = jl_get_binding(module, s);
if (!b) return NULL;
return (jl_is_module(b->value)) ? (jl_module_t *)b->value : NULL;
}
static int symtab_get_matches(jl_sym_t *tree, const char *str, char **answer)
{
int x, plen, count=0;
ios_t ans;
// given str "X.Y.a", set module := X.Y and name := "a"
jl_module_t *module = jl_current_module;
char *name = NULL, *strcopy = strdup(str);
for (char *s=strcopy, *t, *r; (t=strtok_r(s, ".", &r)); s=NULL) {
if (name) {
module = find_submodule_named(module, name);
if (!module) goto symtab_get_matches_exit;
}
name = t;
}
plen = strlen(name);
while (tree != NULL) {
x = common_prefix(name, tree->name);
if (x == plen) {
ios_mem(&ans, 0);
symtab_search(tree, &count, &ans, module, str, name, plen);
size_t nb;
*answer = ios_takebuf(&ans, &nb);
break;
}
else {
x = strcmp(name, tree->name);
if (x < 0)
tree = tree->left;
else
tree = tree->right;
}
}
symtab_get_matches_exit:
free(strcopy);
return count;
}
int tab_complete(const char *line, char **answer, int *plen)
{
int len = *plen;
while (len>=0) {
if (!jl_word_char(line[len])) {
break;
}
len--;
}
len++;
*plen = len;
return symtab_get_matches(jl_get_root_symbol(), &line[len], answer);
}
static char *strtok_saveptr;
static char *do_completions(const char *ch, int c)
{
static char *completions = NULL;
char *ptr;
int len, cnt;
if (c == 0) {
// first time
if (completions)
free(completions);
completions = NULL;
len = strlen(ch);
if (len > 0)
len--;
cnt = tab_complete(ch, &completions, &len);
if (cnt == 0)
return NULL;
ptr = strtok_r(completions, "\n", &strtok_saveptr);
}
else {
ptr = strtok_r(NULL, "\n", &strtok_saveptr);
}
return ptr ? strdup(ptr) : NULL;
}
static char **julia_completion(const char *text, int start, int end)
{
return rl_completion_matches(text, do_completions);
}
void sigtstp_handler(int arg)
{
rl_cleanup_after_signal();
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGTSTP);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
signal(SIGTSTP, SIG_DFL);
raise(SIGTSTP);
signal(SIGTSTP, sigtstp_handler);
}
void sigcont_handler(int arg)
{
rl_reset_after_signal();
if (callback_en)
rl_forced_update_display();
}
static void init_rl(void)
{
rl_readline_name = "julia";
rl_attempted_completion_function = julia_completion;
Keymap keymaps[] = {emacs_standard_keymap, vi_insertion_keymap};
int i;
for (i = 0; i < sizeof(keymaps)/sizeof(keymaps[0]); i++) {
rl_bind_key_in_map(' ', space_callback, keymaps[i]);
rl_bind_key_in_map('\t', tab_callback, keymaps[i]);
rl_bind_key_in_map('\r', return_callback, keymaps[i]);
rl_bind_key_in_map('\n', newline_callback, keymaps[i]);
rl_bind_key_in_map('\v', line_kill_callback, keymaps[i]);
rl_bind_key_in_map('\b', backspace_callback, keymaps[i]);
rl_bind_key_in_map('\001', line_start_callback, keymaps[i]);
rl_bind_key_in_map('\005', line_end_callback, keymaps[i]);
rl_bind_key_in_map('\002', left_callback, keymaps[i]);
rl_bind_key_in_map('\006', right_callback, keymaps[i]);
rl_bind_keyseq_in_map("\e[A", up_callback, keymaps[i]);
rl_bind_keyseq_in_map("\e[B", down_callback, keymaps[i]);
rl_bind_keyseq_in_map("\e[D", left_callback, keymaps[i]);
rl_bind_keyseq_in_map("\e[C", right_callback, keymaps[i]);
rl_bind_keyseq_in_map("\\C-d", delete_callback, keymaps[i]);
}
signal(SIGTSTP, sigtstp_handler);
signal(SIGCONT, sigcont_handler);
}
void init_repl_environment(int argc, char *argv[])
{
disable_history = 0;
for (int i = 0; i < argc; i++)
{
if (!strcmp(argv[i], "--no-history"))
{
disable_history = 1;
break;
}
}
prompt_length = strlen(prompt_plain);
rl_catch_signals = 0;
init_history();
rl_startup_hook = (Function*)init_rl;
}
void repl_callback_enable()
{
callback_en = 1;
rl_callback_handler_install(prompt_string, jl_input_line_callback);
}
void jl_stdin_callback(void)
{
if (callback_en)
rl_callback_read_char();
}
Jump to Line
Something went wrong with that request. Please try again.