/* * Command line editing and history wrapper for readline * Copyright (c) 2010, Jouni Malinen <j@w1.fi> * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "includes.h" #include <readline/readline.h> #include <readline/history.h> #include "common.h" #include "eloop.h" #include "edit.h" static void *edit_cb_ctx; static void (*edit_cmd_cb)(void *ctx, char *cmd); static void (*edit_eof_cb)(void *ctx); static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) = NULL; static char **pending_completions = NULL; static void readline_free_completions(void) { int i; if (pending_completions == NULL) return; for (i = 0; pending_completions[i]; i++) os_free(pending_completions[i]); os_free(pending_completions); pending_completions = NULL; } static char * readline_completion_func(const char *text, int state) { static int pos = 0; static size_t len = 0; if (pending_completions == NULL) { rl_attempted_completion_over = 1; return NULL; } if (state == 0) { pos = 0; len = os_strlen(text); } for (; pending_completions[pos]; pos++) { if (strncmp(pending_completions[pos], text, len) == 0) return strdup(pending_completions[pos++]); } rl_attempted_completion_over = 1; return NULL; } static char ** readline_completion(const char *text, int start, int end) { readline_free_completions(); if (edit_completion_cb) pending_completions = edit_completion_cb(edit_cb_ctx, rl_line_buffer, end); return rl_completion_matches(text, readline_completion_func); } static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx) { rl_callback_read_char(); } static void trunc_nl(char *str) { char *pos = str; while (*pos != '\0') { if (*pos == '\n') { *pos = '\0'; break; } pos++; } } static void readline_cmd_handler(char *cmd) { if (cmd && *cmd) { HIST_ENTRY *h; while (next_history()) ; h = previous_history(); if (h == NULL || os_strcmp(cmd, h->line) != 0) add_history(cmd); next_history(); } if (cmd == NULL) { edit_eof_cb(edit_cb_ctx); return; } trunc_nl(cmd); edit_cmd_cb(edit_cb_ctx, cmd); } int edit_init(void (*cmd_cb)(void *ctx, char *cmd), void (*eof_cb)(void *ctx), char ** (*completion_cb)(void *ctx, const char *cmd, int pos), void *ctx, const char *history_file, const char *ps) { edit_cb_ctx = ctx; edit_cmd_cb = cmd_cb; edit_eof_cb = eof_cb; edit_completion_cb = completion_cb; rl_attempted_completion_function = readline_completion; if (history_file) { read_history(history_file); stifle_history(100); } eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL); if (ps) { size_t blen = os_strlen(ps) + 3; char *ps2 = os_malloc(blen); if (ps2) { os_snprintf(ps2, blen, "%s> ", ps); rl_callback_handler_install(ps2, readline_cmd_handler); os_free(ps2); return 0; } } rl_callback_handler_install("> ", readline_cmd_handler); return 0; } void edit_deinit(const char *history_file, int (*filter_cb)(void *ctx, const char *cmd)) { rl_set_prompt(""); rl_replace_line("", 0); rl_redisplay(); rl_callback_handler_remove(); readline_free_completions(); eloop_unregister_read_sock(STDIN_FILENO); if (history_file) { /* Save command history, excluding lines that may contain * passwords. */ HIST_ENTRY *h; history_set_pos(0); while ((h = current_history())) { char *p = h->line; while (*p == ' ' || *p == '\t') p++; if (filter_cb && filter_cb(edit_cb_ctx, p)) { h = remove_history(where_history()); if (h) { free(h->line); free(h->data); free(h); } else next_history(); } else next_history(); } write_history(history_file); } } void edit_clear_line(void) { } void edit_redraw(void) { rl_on_new_line(); rl_redisplay(); }