~emersion/mrsh-dev

mrsh: Add optional readline/libedit support v2 PROPOSED

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.
Export patchset (mbox)
How do I use this?

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 -3
Learn more about email & git

[PATCH mrsh v2] Add optional readline/libedit support Export this patch

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
 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