Drew DeVault: 1 Add optional readline/libedit support 7 files changed, 111 insertions(+), 14 deletions(-)
Just added a new mrsh_parser_continuation_line function that checks if the input ends on a continuation line. I think it should be enough to call it after parsing and buffer the line in case it returns true.
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~emersion/mrsh-dev/patches/4728/mbox | git am -3Learn more about email & git
This adds support for line editing, history, etc, either with GNU readline or BSD libedit. --- Differences from the last patch: - The interactive interface has been somewhat generalized to support multiple frontends, and the readline code has accordingly been moved into its own file. - History can now be turned off at runtime and is written after every command, instead of all at the end. Future directions: - Completion
Thanks for this patch! This is something that we'll need sooner or later anyway for interactive shells built on top of mrsh. The main issue with this patch is continuation lines. Here are some problematic commands: echo a \ b c or echo "a b c" or cat <<EOF a b c EOF We probably need a way for the parser to tell "I need more lines". That way, the shell could buffer the current line, read the next one and parse again the input.
basic.c | 17 +++++++++++++++++ include/mrsh.h | 11 +++++++++++ include/mrsh/parser.h | 1 + main.c | 31 +++++++++++++++++++++---------- meson.build | 18 ++++++++++++++---- parser/parser.c | 4 ++++ readline.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 basic.c create mode 100644 include/mrsh.h create mode 100644 readline.c diff --git a/basic.c b/basic.c new file mode 100644 index 0000000..de25eed --- /dev/null @@ -0,0 +1,17 @@ +/* Basic, strictly POSIX interactive line interface */ +#include <stdio.h> +#include <stdlib.h> +#include <mrsh/shell.h> +#include "mrsh.h" + +FILE *interactive_init(FILE *input, struct mrsh_state *state) { + return input; +} + +FILE *interactive_next(FILE *input, struct mrsh_state *state) { + char *ps1 = get_ps1(state); + fprintf(stderr, "%s", ps1); + fflush(stderr); + free(ps1); + return input; +} diff --git a/include/mrsh.h b/include/mrsh.h new file mode 100644 index 0000000..fa6a01d --- /dev/null +++ b/include/mrsh.h @@ -0,0 +1,11 @@ +#ifndef _MRSH_H +#define _MRSH_H + +// From main.c +char *get_ps1(struct mrsh_state *state); + +// From frontend drivers +FILE *interactive_init(FILE *input, struct mrsh_state *state); +FILE *interactive_next(FILE *input, struct mrsh_state *state); + +#endif diff --git a/include/mrsh/parser.h b/include/mrsh/parser.h index 695d7a4..33f6edc 100644 --- a/include/mrsh/parser.h +++ b/include/mrsh/parser.h @@ -15,6 +15,7 @@ void mrsh_parser_destroy(struct mrsh_parser *state); struct mrsh_program *mrsh_parse_program(struct mrsh_parser *state); struct mrsh_program *mrsh_parse_line(struct mrsh_parser *state); struct mrsh_word *mrsh_parse_word(struct mrsh_parser *state); +void mrsh_parser_set_file(struct mrsh_parser *parser, FILE *f); bool mrsh_parser_eof(struct mrsh_parser *state); void mrsh_parser_set_alias(struct mrsh_parser *state, mrsh_parser_alias_func_t alias, void *user_data); diff --git a/main.c b/main.c index cf8a049..11e6acc 100644 --- a/main.c +++ b/main.c @@ -6,9 +6,11 @@ #include <mrsh/parser.h> #include <mrsh/shell.h> #include <stdlib.h> +#include <stdio.h> #include <string.h> #include <unistd.h> #include "builtin.h" +#include "mrsh.h" char *expand_ps1(struct mrsh_state *state, const char *ps1) { struct mrsh_parser *parser = @@ -24,18 +26,16 @@ char *expand_ps1(struct mrsh_state *state, const char *ps1) { return mrsh_word_str(word); } -static void print_ps1(struct mrsh_state *state) { +char *get_ps1(struct mrsh_state *state) { const char *ps1 = mrsh_hashtable_get(&state->variables, "PS1"); if (ps1 != NULL) { - char *expanded_ps1 = expand_ps1(state, ps1); // TODO: Replace ! with next history ID - fprintf(stderr, "%s", expanded_ps1); - free(expanded_ps1); + return expand_ps1(state, ps1); } else { - fprintf(stderr, "%s", getuid() ? "$ " : "# "); + char *p = malloc(3); + sprintf(p, "%s", getuid() ? "$ " : "# "); + return p; } - - fflush(stderr); } static void source_profile(struct mrsh_state *state) { @@ -91,14 +91,24 @@ int main(int argc, char *argv[]) { source_profile(&state); // TODO: set PWD + + FILE *input = state.input; + if (state.interactive) { + input = interactive_init(input, &state); + } - struct mrsh_parser *parser = mrsh_parser_create(state.input); + struct mrsh_parser *parser = mrsh_parser_create(input); mrsh_parser_set_alias(parser, get_alias, &state); while (state.exit == -1) { + struct mrsh_program *prog; if (state.interactive) { - print_ps1(&state); + FILE *_input = interactive_next(input, &state); + if (_input != input) { + mrsh_parser_set_file(parser, input); + input = _input; + } } - struct mrsh_program *prog = mrsh_parse_line(parser); + prog = mrsh_parse_line(parser); if (prog == NULL) { struct mrsh_position err_pos; const char *err_msg = mrsh_parser_error(parser, &err_pos); @@ -129,5 +139,6 @@ int main(int argc, char *argv[]) { mrsh_parser_destroy(parser); mrsh_state_finish(&state); fclose(state.input); + return state.exit; } diff --git a/meson.build b/meson.build index 66b5d2c..3283d16 100644 --- a/meson.build +++ b/meson.build @@ -17,6 +17,7 @@ add_project_arguments('-Wno-missing-field-initializers', language: 'c') cc = meson.get_compiler('c') +readline = cc.find_library('readline', required: false) rt = cc.find_library('rt') mrsh_inc = include_directories('include') @@ -83,12 +84,21 @@ mrsh = declare_dependency( include_directories: mrsh_inc, ) +shell_deps = [mrsh_static] +shell_files = [ + 'main.c' +] +if readline.found() + shell_deps += [readline] + shell_files += ['readline.c'] +else + shell_files += ['basic.c'] +endif + executable( 'mrsh', - files([ - 'main.c', - ]), - dependencies: [mrsh_static], + files(shell_files), + dependencies: shell_deps, install: true, ) diff --git a/parser/parser.c b/parser/parser.c index a091af6..409d156 100644 --- a/parser/parser.c +++ b/parser/parser.c @@ -71,6 +71,10 @@ struct mrsh_parser *mrsh_parser_create_from_buffer(const char *buf, size_t len) return state; } +void mrsh_parser_set_file(struct mrsh_parser *parser, FILE *f) { + parser->f = f; +} + void mrsh_parser_destroy(struct mrsh_parser *state) { if (state == NULL) { return; diff --git a/readline.c b/readline.c new file mode 100644 index 0000000..e605e36 --- /dev/null +++ b/readline.c @@ -0,0 +1,43 @@ +/* readline/libedit interactive line interface */ +#define _POSIX_C_SOURCE 200809L +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <readline/history.h> +#include <readline/readline.h> +#include <mrsh/shell.h> +#include <mrsh/parser.h> +#include "mrsh.h" + +static const size_t rl_buf_size = 4096; + +static const char *get_history_path() { + static char history_path[PATH_MAX + 1]; + snprintf(history_path, sizeof(history_path), + "%s/.mrsh_history", getenv("HOME")); + return history_path; +} + +FILE *interactive_init(FILE *input, struct mrsh_state *state) { + input = fmemopen(NULL, rl_buf_size, "w+"); + rl_initialize(); + read_history(get_history_path()); + return input; +} + +FILE *interactive_next(FILE *input, struct mrsh_state *state) { + char *ps1 = get_ps1(state); + char *line = readline(ps1); + free(ps1); + if (line != NULL) { + fclose(input); + input = fmemopen(NULL, rl_buf_size, "w+"); + fprintf(input, "%s\n", line); + fseek(input, 0, SEEK_SET); + if (!(state->options & MRSH_OPT_NOLOG)) { + add_history(line); + write_history(get_history_path()); + } + } + return input; +} -- 2.19.0