Page MenuHomeFreeBSD

D36585.id110583.diff
No OneTemporary

D36585.id110583.diff

diff --git a/stand/common/interp.c b/stand/common/interp.c
--- a/stand/common/interp.c
+++ b/stand/common/interp.c
@@ -37,20 +37,65 @@
#include <string.h>
#include "bootstrap.h"
+#include "prompt_bindings.h"
+#include "prompt_editing.h"
+
#define MAXARGS 20 /* maximum number of arguments allowed */
+struct prompt_buffer prompt_prompt = { 0 };
const char * volatile interp_identifier;
+/*
+ * Hardcoded key bindings to avoid adding a new file, can be overridden
+ * with the "keybind" simp command, or the "keybind.register" Lua function.
+ *
+ * Since some consoles might not encode modifiers and special keys correctly
+ * some actions are assigned to multiple keys, just in case a console
+ * can only do modifiers OR special keys.
+ */
+
+struct {
+ char *stroke;
+ char *action;
+} default_keybinds[] = {
+ {"BS", "delete-backward-char"},
+ {"<delete>", "delete-forward-char"},
+ {"<left>", "backward-char"},
+ {"<right>", "forward-char"},
+
+ {"M-<left>", "backward-word"},
+ {"M-<right>", "forward-word"},
+
+ {"M-b", "backward-word"},
+ {"M-f", "forward-word"},
+
+ {"<up>", "previous-history-element"},
+ {"<down>", "next-history-element"},
+
+ {"TAB", "smart-complete"},
+
+ {"<home>", "move-beginning-of-line"},
+ {"<end>", "move-end-of-line"},
+
+ {"C-a", "move-beginning-of-line"},
+ {"C-e", "move-end-of-line"},
+
+ {"C-y", "yank"},
+ {"C-k", "kill-line"},
+ {"M-d", "kill-word"},
+ {"M-<delete>", "backward-kill-word"},
+
+ {NULL, NULL}
+};
+
/*
* Interactive mode
*/
void
interact(void)
{
- static char input[256]; /* big enough? */
-
TSENTER();
-
+
/*
* Because interp_identifier is volatile, it cannot be optimized out by
* the compiler as it's considered an externally observable event. This
@@ -67,7 +112,15 @@
* Before interacting, we might want to autoboot.
*/
autoboot_maybe();
-
+
+ /*
+ * Setup sane defaults for key bindings (anything unrecognized is ignored)
+ */
+
+ for (int i = 0; default_keybinds[i].stroke != NULL; i++) {
+ prompt_add_stroke_action_binding(default_keybinds[i].stroke, default_keybinds[i].action);
+ }
+
/*
* Not autobooting, go manual
*/
@@ -76,12 +129,31 @@
setenv("prompt", "${interpret}", 1);
if (getenv("interpret") == NULL)
setenv("interpret", "OK", 1);
-
+
+ prompt_init();
+
for (;;) {
- input[0] = '\0';
+ prompt_reset();
interp_emit_prompt();
- ngets(input, sizeof(input));
- interp_run(input);
+
+ for (;;) {
+ char n = prompt_on_input(getchar());
+
+ if (n == 0xd)
+ {
+ break;
+ }
+ else if (n != 0)
+ {
+ prompt_rawinput(n);
+ }
+ }
+
+ char *line = prompt_getline();
+
+ printf("\r\n");
+
+ interp_run(line);
}
}
diff --git a/stand/common/interp_lua.c b/stand/common/interp_lua.c
--- a/stand/common/interp_lua.c
+++ b/stand/common/interp_lua.c
@@ -44,6 +44,9 @@
#include <lfs.h>
#include <lutils.h>
+#include "prompt_bindings.h"
+#include "prompt_editing.h"
+
struct interp_lua_softc {
lua_State *luap;
};
@@ -75,6 +78,335 @@
return realloc(ptr, nsize);
}
+/*
+ * Appended onto each keybind created from Lua, callback_ref
+ * is just the registry index of a callback
+ */
+
+struct lua_keybind {
+ int callback_ref;
+};
+
+static void
+lua_keybind_handler(void *data)
+{
+ struct lua_keybind *bind = data;
+ lua_State *L = lua_softc.luap;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, bind->callback_ref);
+
+ lua_pcall(L, 0, 0, 0);
+}
+
+static void
+delete_bind(lua_State *L, struct prompt_keybind *bind)
+{
+ /* Delete a binding, unref-ing along the way if Lua owns it */
+
+ if (bind->action == lua_keybind_handler) {
+ void *data = sizeof(struct prompt_keybind) + (void*)bind;
+ struct lua_keybind *luabind = data;
+
+ luaL_unref(L, LUA_REGISTRYINDEX, luabind->callback_ref);
+ }
+
+ prompt_remove_binding(bind);
+}
+
+static int
+lua_keybind_register(lua_State *L)
+{
+ int argc = lua_gettop(L);
+
+ if (argc != 2) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ if (!lua_isstring(L, -2)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ const char *stroke = lua_tostring(L, -2);
+ struct prompt_input input = prompt_parse_stroke(stroke);
+
+ if (input.key == 0) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ struct prompt_keybind *existing = prompt_find_binding(input.mods, input.key);
+
+ if (existing != NULL) {
+ /*
+ * Delete a existing binding to prevent a leak
+ */
+
+ delete_bind(L, existing);
+ }
+
+ struct prompt_keybind *bind = NULL;
+
+ if (lua_islightuserdata(L, -1)) {
+ /*
+ * keybind.register(..., lightuserdata)
+ * means we've been called with a predefined action
+ * from the keybind.actions table.
+ * Instead of wrapping a Lua function, just call to the
+ * predefined action directly.
+ */
+
+ struct prompt_predefined_action *predef = lua_touserdata(L, -1);
+
+ bind = prompt_add_binding(input.mods, input.key, predef->action);
+ } else {
+ struct prompt_keybind *bind = prompt_add_binding_raw(4, input.mods, input.key, lua_keybind_handler);
+ struct lua_keybind *luabind = sizeof(struct prompt_keybind) + (void*)bind;
+
+ luabind->callback_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+ }
+
+ /*
+ * Return the binding as lightuserdata, so it can be passed to
+ * keybind.delete to be removed
+ */
+
+ lua_pushlightuserdata(L, bind);
+ return 1;
+}
+
+static int
+lua_keybind_delete(lua_State *L)
+{
+ int argc = lua_gettop(L);
+
+ if (argc != 1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ if (!lua_islightuserdata(L, -1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ struct prompt_keybind *bind = (struct prompt_keybind*)lua_touserdata(L, -1);
+
+ delete_bind(L, bind);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int
+lua_keybind_find(lua_State *L)
+{
+ /*
+ * Find a binding by name so it can be deleted
+ */
+
+ int argc = lua_gettop(L);
+
+ if (argc != 1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ if (!lua_isstring(L, -1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ const char *stroke = lua_tostring(L, -1);
+ struct prompt_input input = prompt_parse_stroke(stroke);
+
+ if (input.key == 0) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ struct prompt_keybind *bind = prompt_find_binding(input.mods, input.key);
+
+ if (bind == NULL) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_pushlightuserdata(L, bind);
+ return 1;
+}
+
+static int
+lua_keybind_list(lua_State *L)
+{
+ /*
+ * Get a list of all bound keys
+ */
+
+ int argc = lua_gettop(L);
+
+ if (argc != 0) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ struct prompt_keybind *bind = prompt_first_binding();
+ int i = 1;
+
+ while (bind != NULL) {
+ char buf[20] = { 0 };
+ prompt_stroke_to_string(buf, sizeof(buf), bind->target);
+
+ lua_pushstring(L, buf);
+ lua_rawseti(L, -2, i++);
+
+ bind = prompt_next_binding(bind);
+ }
+
+ return 1;
+}
+
+static const struct luaL_Reg keybindlib[] = {
+ {"register", lua_keybind_register},
+ {"delete", lua_keybind_delete},
+ {"find", lua_keybind_find},
+ {"list", lua_keybind_list},
+ {NULL, NULL}
+};
+
+int
+luaopen_keybind(lua_State *L)
+{
+ luaL_newlib(L, keybindlib);
+
+ lua_newtable(L);
+
+ /*
+ * Build keybind.actions out of the list of predefined actions
+ */
+
+ struct prompt_predefined_action **ppa;
+
+ SET_FOREACH(ppa, Xpredef_action_set) {
+ struct prompt_predefined_action *a = *ppa;
+
+ lua_pushlightuserdata(L, a);
+ lua_setfield(L, -2, a->name);
+ }
+
+ lua_setfield(L, -2, "actions");
+
+ return 1;
+}
+
+static int
+lua_history_add(lua_State *L)
+{
+ /*
+ * Artificially add a line the the prompt history
+ */
+
+ int argc = lua_gettop(L);
+
+ if (argc != 1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ if (!lua_isstring(L, -1)) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ const char *entry = lua_tostring(L, -1);
+
+ prompt_history_add(entry, strlen(entry));
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int
+lua_history_remove(lua_State *L)
+{
+ /*
+ * Remove a line from the history, identified by index
+ */
+
+ int argc = lua_gettop(L);
+
+ if (argc != 1) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ int index = (int)lua_tonumber(L, -1);
+
+ struct prompt_history_entry *entry = prompt_history_first();
+ int i = 0;
+
+ while (entry != NULL) {
+ if (i++ == index) {
+ prompt_history_remove(entry);
+
+ lua_pushboolean(L, 1);
+ return 1;
+ }
+
+ entry = prompt_history_next(entry);
+ }
+
+ lua_pushnil(L);
+ return 1;
+}
+
+static int
+lua_history_list(lua_State *L)
+{
+ /*
+ * Get a list of all lines in the history
+ */
+
+ int argc = lua_gettop(L);
+
+ if (argc != 0) {
+ lua_pushnil(L);
+ return 1;
+ }
+
+ lua_newtable(L);
+
+ struct prompt_history_entry *entry = prompt_history_first();
+ int i = 1;
+
+ while (entry != NULL) {
+ lua_pushstring(L, entry->line);
+ lua_rawseti(L, -2, i++);
+
+ entry = prompt_history_next(entry);
+ }
+
+ return 1;
+}
+
+static const struct luaL_Reg historylib[] = {
+ {"add", lua_history_add},
+ {"remove", lua_history_remove},
+ {"list", lua_history_list},
+ {NULL, NULL}
+};
+
+int
+luaopen_history(lua_State *L)
+{
+ luaL_newlib(L, historylib);
+
+ return 1;
+}
+
/*
* The libraries commented out below either lack the proper
* support from libsa, or they are unlikely to be useful
@@ -96,6 +428,8 @@
{"lfs", luaopen_lfs},
{"loader", luaopen_loader},
{"pager", luaopen_pager},
+ {"keybind", luaopen_keybind},
+ {"history", luaopen_history},
{NULL, NULL}
};
@@ -204,3 +538,98 @@
return (luaL_dofile(softc->luap, filename));
}
+
+/*
+ * Keyword/global name completion.
+ * The actual enumeration logic here is a bit odd, since we need to
+ * walk through every (string) key in _G, and then artificially append
+ * language keywords to that list.
+ */
+
+static void *
+token_first()
+{
+ lua_State *L = lua_softc.luap;
+
+ lua_pushglobaltable(L);
+ lua_pushnil(L);
+ return (void*)(intptr_t)!lua_next(L, -2);
+}
+
+/*
+ * From the Lua 5.2 Reference Manual, short keywords commented out
+ * since shortcuts like "d<tab>" for "do" aren't likely to be
+ * useful and will just clutter the list.
+ */
+
+static const char* keywords[] = {
+ "and", "break", /*"do",*/ "else", "elseif", "end",
+ "false", "for", "function", /*"if",*/ /*"in",*/ "local",
+ "nil", "not", /*"or",*/ "repeat", "return", "then",
+ "true", "until", "while"
+};
+
+static void *
+token_next(void *rawlast)
+{
+ intptr_t idx = (intptr_t)rawlast;
+
+ if (idx + 1 > (sizeof(keywords) / sizeof(keywords[0]))) {
+ return (void*)-1;
+ }
+ else if (idx > 0) {
+ return (void*)++idx;
+ } else {
+ lua_State* L = lua_softc.luap;
+
+ return (void*)(intptr_t)!lua_next(L, -2);
+ }
+}
+static void
+token_tostring(void *rawlast, char *out, int len)
+{
+ intptr_t idx = (intptr_t)rawlast;
+
+ if (idx > 0) {
+ snprintf(out, len, "%s", keywords[idx - 1]);
+ }
+ else {
+ lua_State *L = lua_softc.luap;
+
+ int isfunction = lua_isfunction(L, -1);
+ lua_pop(L, 1);
+
+ if (lua_isstring(L, -1)) {
+ lua_pushvalue(L, -1);
+ const char *value = lua_tolstring(L, -1, NULL);
+ lua_pop(L, 1);
+
+ snprintf(out, len, isfunction ? "%s(" : "%s", value);
+ }
+ }
+}
+
+static void
+lua_completer(char *command, char *argv)
+{
+ lua_State *L = lua_softc.luap;
+
+ /*
+ * Since the "last" key is always left on the stack, we would need
+ * to pop it "after" token_first, which isn't possible with how
+ * the compleition works. Instead, we just set up a pseudo stack
+ * frame and pop any leftover values.
+ */
+
+ int top = lua_gettop(L);
+ prompt_generic_complete(argv, token_first, token_next, (void*)-1, token_tostring);
+ lua_settop(L, top);
+}
+
+/*
+ * Fallthrough completion, matches any undefined "command".
+ * Aka, nearly any Lua code, which allows us to jump in and try
+ * to complete Lua specifics without matching any "proper" commands.
+ */
+
+COMPLETION_SET(_, 0, lua_completer);
\ No newline at end of file
diff --git a/stand/common/prompt_bindings.h b/stand/common/prompt_bindings.h
new file mode 100644
--- /dev/null
+++ b/stand/common/prompt_bindings.h
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2022 Connor Bailey
+ *
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#include <sys/queue.h>
+
+struct prompt_input {
+ char mods;
+ char key;
+};
+
+typedef void(*prompt_action)(void*);
+
+struct prompt_keybind {
+ struct prompt_input target;
+ prompt_action action;
+
+ STAILQ_ENTRY(prompt_keybind) next;
+};
+
+struct prompt_predefined_action {
+ char *name;
+ prompt_action action;
+
+ STAILQ_ENTRY(prompt_predefined_action) next;
+};
+
+#define PROMPT_MOD_SHIFT 1
+#define PROMPT_MOD_ALT 2
+#define PROMPT_MOD_CTRL 4
+
+#define PROMPT_ANSI_TO_KEY 0x80
+#define PROMPT_KEY_UP (PROMPT_ANSI_TO_KEY + 'A')
+#define PROMPT_KEY_DOWN (PROMPT_ANSI_TO_KEY + 'B')
+#define PROMPT_KEY_RIGHT (PROMPT_ANSI_TO_KEY + 'C')
+#define PROMPT_KEY_LEFT (PROMPT_ANSI_TO_KEY + 'D')
+#define PROMPT_KEY_END (PROMPT_ANSI_TO_KEY + 'F')
+#define PROMPT_KEY_HOME (PROMPT_ANSI_TO_KEY + 'H')
+
+#define PROMPT_KEY_INSERT (PROMPT_ANSI_TO_KEY + 'E')
+#define PROMPT_KEY_DELETE (PROMPT_ANSI_TO_KEY + 'I')
+
+#define PROMPT_KEY_PGUP (PROMPT_ANSI_TO_KEY + 'J')
+#define PROMPT_KEY_PGDN (PROMPT_ANSI_TO_KEY + 'K')
+
+struct prompt_input prompt_parse_input(char);
+char prompt_input_to_char(struct prompt_input input);
+
+/*
+ * Takes a stream of input escapes, parses them and executes any bindings or
+ * converts the input into a character and returns it.
+ */
+char prompt_on_input(char);
+
+struct prompt_keybind *prompt_find_binding(char, char);
+struct prompt_keybind *prompt_add_binding_raw(int, char, char, prompt_action);
+struct prompt_keybind *prompt_add_binding(char, char, prompt_action);
+void prompt_remove_binding(struct prompt_keybind *);
+
+void prompt_stroke_to_string(char *, size_t, struct prompt_input);
+void prompt_print_stroke(struct prompt_input);
+struct prompt_input prompt_parse_stroke(const char *);
+
+struct prompt_keybind *prompt_add_stroke_action_binding(char *, char *);
+
+struct prompt_keybind *prompt_first_binding();
+struct prompt_keybind *prompt_next_binding(struct prompt_keybind *);
+
+#define PREDEF_ACTION_SET(name, func) \
+ static struct prompt_predefined_action _predef_ ## func = { name, func }; \
+ DATA_SET(Xpredef_action_set, _predef_ ## func)
+
+SET_DECLARE(Xpredef_action_set, struct prompt_predefined_action);
\ No newline at end of file
diff --git a/stand/common/prompt_bindings.c b/stand/common/prompt_bindings.c
new file mode 100644
--- /dev/null
+++ b/stand/common/prompt_bindings.c
@@ -0,0 +1,637 @@
+/*-
+ * Copyright (c) 2022 Connor Bailey
+ *
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#include <stand.h>
+#include "bootstrap.h"
+
+#include "prompt_bindings.h"
+
+#define BS '\x8'
+#define TAB '\x9'
+#define CR '\xd'
+#define ESC '\x1b'
+#define DEL '\x7f'
+
+/*
+ * In the input escape parser, we need to handle:
+ * "char"
+ * "ESC char"
+ * "ESC [ code ~"
+ * "ESC [ char ; mods"
+ * "ESC [ mods ; code ~"
+ * each of which is assigned a state, with the proper transitions between each.
+ *
+ * Special keys are just (PROMPT_ANSI_TO_KEY + ANSI_CODE) with unused ANSI codes
+ * picked for VT codes.
+ */
+
+enum {
+ ESC_NORMAL,
+ ESC_ESC,
+ ESC_BRACKET,
+ ESC_BRACKET_EITHER,
+ ESC_CODE_MODS,
+ ESC_CODE_MODS_END
+} prompt_esc_state;
+
+static char prompt_mods_or_code;
+static char prompt_char_or_mods;
+
+static void
+unshift(struct prompt_input *result, char in)
+{
+ if (isupper(in)) {
+ result->mods |= PROMPT_MOD_SHIFT;
+ in = tolower(in);
+ }
+
+ result->key = in;
+}
+
+/* Map [1-9]~ VT codes back into keycodes */
+
+static char prompt_vt[] = {
+ PROMPT_KEY_HOME, PROMPT_KEY_INSERT, PROMPT_KEY_DELETE, PROMPT_KEY_END,
+ PROMPT_KEY_PGUP, PROMPT_KEY_PGDN, PROMPT_KEY_HOME, PROMPT_KEY_END
+};
+
+struct prompt_input
+prompt_parse_input(char next)
+{
+ struct prompt_input result = { 0 };
+
+ switch (prompt_esc_state) {
+ case ESC_NORMAL:
+ if (next == ESC) {
+ prompt_esc_state = ESC_ESC;
+ }
+ else if (iscntrl(next) && next != BS && next != TAB && next != CR) {
+ /*
+ * Control characters turn into ctrl+key
+ */
+
+ result.key = next - 1 + 'a';
+ result.mods |= PROMPT_MOD_CTRL;
+ } else {
+ unshift(&result, next);
+ }
+ break;
+ case ESC_ESC:
+ if (next == '[') {
+ prompt_esc_state = ESC_BRACKET;
+ } else {
+ /* "ESC char" turns back into alt+char */
+
+ result.mods |= PROMPT_MOD_ALT;
+ unshift(&result, next);
+ prompt_esc_state = ESC_NORMAL;
+ }
+ break;
+ case ESC_BRACKET:
+ if ('A' <= next && next <= 'Z') {
+ /*
+ * Plain ANSI escapes without modifiers terminate after the first
+ * non-numeric character
+ */
+
+ result.key = PROMPT_ANSI_TO_KEY + next;
+ prompt_esc_state = ESC_NORMAL;
+ }
+ else if ('1' <= next && next <= '9') {
+ prompt_esc_state = ESC_BRACKET_EITHER;
+ prompt_mods_or_code = next;
+ } else {
+ /*
+ * "ESC [ char" into alt+char, mainly for "ESC [ [" to work
+ */
+
+ result.mods |= PROMPT_MOD_ALT;
+ unshift(&result, next);
+ prompt_esc_state = ESC_NORMAL;
+ }
+ break;
+ case ESC_BRACKET_EITHER:
+ if (next == ';') {
+ prompt_esc_state = ESC_CODE_MODS;
+ }
+ else if (next == '~') {
+ /*
+ * VT escape terminated by ~, mods_or_code is a code
+ */
+
+ prompt_mods_or_code -= '1';
+
+ if (prompt_mods_or_code < 8) {
+ result.key = prompt_vt[(int)prompt_mods_or_code];
+ }
+
+ prompt_esc_state = ESC_NORMAL;
+ } else {
+ /*
+ * ESC [ mods ; char
+ */
+
+ result.mods = prompt_mods_or_code - '0' - 1;
+
+ unshift(&result, next);
+
+ prompt_esc_state = ESC_NORMAL;
+ }
+ break;
+ case ESC_CODE_MODS:
+ prompt_char_or_mods = next;
+ prompt_esc_state = ESC_CODE_MODS_END;
+ break;
+ case ESC_CODE_MODS_END:
+ result.mods = prompt_char_or_mods - '0' - 1;
+
+ if (next == '~') {
+ /* VT escape terminated by ~, mods_or_code is a code */
+
+ prompt_mods_or_code -= '1';
+
+ if (prompt_mods_or_code < 8) {
+ result.key = prompt_vt[(int)prompt_mods_or_code];
+ }
+ }
+ else if (prompt_mods_or_code == '1' && ('A' <= next && next <= 'Z')) {
+ /* ANSI escape in the "ESC [ mods ; char" format */
+
+ result.key = PROMPT_ANSI_TO_KEY + next;
+ }
+
+ prompt_esc_state = ESC_NORMAL;
+
+ break;
+ }
+
+ return result;
+}
+
+char
+prompt_input_to_char(struct prompt_input input)
+{
+ if (input.key > 0) {
+ if (input.mods & PROMPT_MOD_SHIFT) {
+ if (islower(input.key)) {
+ return toupper(input.key);
+ }
+ }
+ else if (input.mods) {
+ return 0;
+ }
+
+ return input.key;
+ }
+
+ return 0;
+}
+
+STAILQ_HEAD(prompt_binds, prompt_keybind) prompt_binds_head =
+ STAILQ_HEAD_INITIALIZER(prompt_binds_head);
+
+struct prompt_keybind *
+prompt_find_binding(char mods, char key)
+{
+ struct prompt_keybind *p;
+
+ STAILQ_FOREACH(p, &prompt_binds_head, next) {
+ if (p->target.mods == mods && p->target.key == key) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+struct prompt_keybind *
+prompt_add_binding_raw(int extraspace, char mods, char key, prompt_action action)
+{
+ /*
+ * Allocate extra space on the end of the result for the caller to use how
+ * they like, it will be passed to their callback.
+ */
+
+ struct prompt_keybind* result = prompt_find_binding(mods, key);
+ int new = result == NULL;
+
+ if (new) {
+ result = malloc(sizeof(struct prompt_keybind) + extraspace);
+ }
+
+ result->target.mods = mods;
+ result->target.key = key;
+ result->action = action;
+
+ if (new) {
+ STAILQ_INSERT_TAIL(&prompt_binds_head, result, next);
+ }
+
+ return result;
+}
+
+struct prompt_keybind *
+prompt_add_binding(char mods, char key, prompt_action action)
+{
+ return prompt_add_binding_raw(0, mods, key, action);
+}
+
+void
+prompt_remove_binding(struct prompt_keybind *bind)
+{
+ STAILQ_REMOVE(&prompt_binds_head, bind, prompt_keybind, next);
+
+ free(bind);
+}
+
+char
+prompt_on_input(char in)
+{
+ struct prompt_input input = prompt_parse_input(in);
+
+ struct prompt_keybind *binding = prompt_find_binding(input.mods, input.key);
+
+ if (binding != NULL) {
+ /*
+ * Pass any caller data stored past the end of the binding
+ */
+
+ binding->action(sizeof(struct prompt_keybind) + (void*)binding);
+ return 0;
+ }
+
+ return prompt_input_to_char(input);
+}
+
+struct keyname_map {
+ char *name;
+ char key;
+};
+
+/*
+ * Control characters
+ */
+
+static struct keyname_map emacs_shortname_to_key[] = {
+ {"BS", BS},
+ {"TAB", TAB},
+ {"RET", CR},
+ {"ESC", ESC},
+ {"SPC", ' '},
+ {"DEL", DEL},
+ {0, 0}
+};
+
+/*
+ * Special characters
+ */
+
+static struct keyname_map emacs_longname_to_key[] = {
+ {"<left>", PROMPT_KEY_LEFT},
+ {"<up>", PROMPT_KEY_UP},
+ {"<right>", PROMPT_KEY_RIGHT},
+ {"<down>", PROMPT_KEY_DOWN},
+ {"<end>", PROMPT_KEY_END},
+ {"<home>", PROMPT_KEY_HOME},
+ {"<insert>", PROMPT_KEY_INSERT},
+ {"<delete>", PROMPT_KEY_DELETE},
+ {"<prior>", PROMPT_KEY_PGUP},
+ {"<next>", PROMPT_KEY_PGDN},
+ {0, 0}
+};
+
+static char
+lookup_key_from_name(struct keyname_map *map, const char *name)
+{
+ int i = 0;
+
+ while (map[i].key != 0) {
+ if (strcmp(map[i].name, name) == 0) {
+ return map[i].key;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+static char *
+lookup_name_from_key(struct keyname_map *map, const char key)
+{
+ int i = 0;
+
+ while (map[i].key != 0) {
+ if (map[i].key == key) {
+ return map[i].name;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+void
+prompt_stroke_to_string(char *buf, size_t len, struct prompt_input stroke)
+{
+ int off = 0;
+
+ if (stroke.mods) {
+ if (stroke.mods & PROMPT_MOD_ALT) {
+ off += snprintf(&buf[off], len, "M-");
+ }
+
+ if (stroke.mods & PROMPT_MOD_CTRL) {
+ off += snprintf(&buf[off], len, "C-");
+ }
+
+ if (stroke.mods & PROMPT_MOD_SHIFT) {
+ off += snprintf(&buf[off], len, "S-");
+ }
+ }
+
+ if (iscntrl(stroke.key)) {
+ /*
+ * Control characters
+ */
+
+ char *name = lookup_name_from_key(emacs_shortname_to_key, stroke.key);
+
+ if (name) {
+ off += snprintf(&buf[off], len, "%s", name);
+ } else {
+ off += snprintf(&buf[off], len, "\\x%x", stroke.key);
+ }
+ }
+ else if (!iscntrl(stroke.key) && stroke.key != ' ') {
+ /*
+ * Printable characters (no isprint, easy enough to fake)
+ */
+
+ off += snprintf(&buf[off], len, "%c", stroke.key);
+ } else {
+ /*
+ * Special characters
+ */
+
+ char *name = lookup_name_from_key(emacs_longname_to_key, stroke.key);
+
+ if (name) {
+ off += snprintf(&buf[off], len, "%s", name);
+ } else {
+ off += snprintf(&buf[off], len, "\\x%x", stroke.key);
+ }
+ }
+}
+
+void
+prompt_print_stroke(struct prompt_input stroke)
+{
+ char buf[20] = { 0 };
+
+ prompt_stroke_to_string(buf, sizeof(buf), stroke);
+
+ printf("%s", buf);
+}
+
+struct prompt_input
+prompt_parse_stroke(const char *stroke)
+{
+ struct prompt_input result = { 0 };
+
+ int len = strlen(stroke);
+ const char *p = stroke;
+
+ /*
+ * Any number of "X-" modifiers, back to back
+ */
+
+ while (len >= 3 && p[1] == '-') {
+ switch (*p) {
+ case 'C':
+ result.mods |= PROMPT_MOD_CTRL;
+ break;
+ case 'M':
+ result.mods |= PROMPT_MOD_ALT;
+ break;
+ case 'S':
+ result.mods |= PROMPT_MOD_SHIFT;
+ break;
+ }
+
+ p += 2;
+ len -= 2;
+ }
+
+ if (len != 1) {
+ /*
+ * More left to parse than a single character like "M-x"
+ */
+
+ if (p[0] == '<') {
+ /*
+ * "M-<special>"
+ */
+
+ result.key = lookup_key_from_name(emacs_longname_to_key, p);
+ } else {
+ /*
+ * "M-CTRL"
+ */
+
+ result.key = lookup_key_from_name(emacs_shortname_to_key, p);
+ }
+ } else {
+ /*
+ * A single character, might include shift as a modifier if it is uppercase
+ */
+
+ char ascii = *p;
+
+ if (isupper(ascii)) {
+ result.mods |= PROMPT_MOD_SHIFT;
+ ascii = tolower(ascii);
+ }
+
+ result.key = ascii;
+ }
+
+ return result;
+}
+
+struct prompt_keybind *
+prompt_first_binding()
+{
+ return STAILQ_FIRST(&prompt_binds_head);
+}
+struct prompt_keybind *
+prompt_next_binding(struct prompt_keybind* bind)
+{
+ return STAILQ_NEXT(bind, next);
+}
+
+struct prompt_keybind *
+prompt_add_stroke_binding(char *stroke, prompt_action action)
+{
+ struct prompt_input input = prompt_parse_stroke(stroke);
+
+ if (input.mods == 0 && input.key == 0) {
+ return NULL;
+ }
+
+ return prompt_add_binding(input.mods, input.key, action);
+}
+
+/*
+ * Predefined actions just map names to functions, mainly for simp since it isn't
+ * possible to define a callback.
+ * Still useful for Lua though, since otherwise Lua would need to manually
+ * implement every editing option instead of just using the predefined ones.
+ *
+ * Lua translates Xpredef_action_set into the keybind.actions table at runtime
+ * and then each entry in keybind.actions can be passed to keybind.register to
+ * accomplish the same as the simp "keybind" command.
+ */
+
+static struct prompt_predefined_action *
+find_predef_by_name(char *name)
+{
+ struct prompt_predefined_action **ppa;
+
+ SET_FOREACH(ppa, Xpredef_action_set) {
+ struct prompt_predefined_action *a = *ppa;
+
+ if (strcmp(a->name, name) == 0) {
+ return a;
+ }
+ }
+
+ return NULL;
+}
+
+static struct prompt_predefined_action *
+find_predef_by_action(prompt_action action)
+{
+ struct prompt_predefined_action **ppa;
+
+ SET_FOREACH(ppa, Xpredef_action_set) {
+ struct prompt_predefined_action *a = *ppa;
+
+ if (a->action == action) {
+ return a;
+ }
+ }
+
+ return NULL;
+}
+
+struct prompt_keybind *
+prompt_add_stroke_action_binding(char *stroke, char *action_name)
+{
+ struct prompt_predefined_action *predef = find_predef_by_name(action_name);
+
+ if (predef == NULL) {
+ return NULL;
+ }
+
+ return prompt_add_stroke_binding(stroke, predef->action);
+}
+
+COMMAND_SET(keybind, "keybind", "bind a key to an action", command_keybind);
+
+static int
+command_keybind(int argc, char *argv[])
+{
+ if (argc != 3) {
+ command_errmsg = "wrong number of arguments";
+ return CMD_ERROR;
+ }
+
+ struct prompt_keybind *bind = prompt_add_stroke_action_binding(argv[1], argv[2]);
+
+ if (bind != NULL) {
+ return CMD_OK;
+ } else {
+ snprintf(command_errbuf, sizeof(command_errbuf), "could not bind '%s' to '%s'", argv[1], argv[2]);
+
+ return CMD_ERROR;
+ }
+}
+
+COMMAND_SET(keybinds, "keybinds", "list bound keys", command_keybinds);
+
+static int
+command_keybinds(int argc, char *argv[])
+{
+ struct prompt_keybind *p;
+
+ STAILQ_FOREACH(p, &prompt_binds_head, next) {
+ prompt_print_stroke(p->target);
+
+ struct prompt_predefined_action *predef = find_predef_by_action(p->action);
+
+ if (predef != NULL) {
+ printf(" %s\n", predef->name);
+ } else {
+ /*
+ * Wouldn't be very kind of us to dig into Lua's data stored after the
+ * binding, so we have to default to a generic name for Lua actions.
+ */
+
+ printf(" <interpreter callback %p>\n", p->action);
+ }
+ }
+
+ return CMD_OK;
+}
+
+COMMAND_SET(keyunbind, "keyunbind", "unbind a previously bound key", command_keyunbind);
+
+static int
+command_keyunbind(int argc, char *argv[]) {
+ if (argc != 2) {
+ command_errmsg = "wrong number of arguments";
+ return CMD_ERROR;
+ }
+
+ struct prompt_input stroke = prompt_parse_stroke(argv[1]);
+
+ if (stroke.mods == 0 && stroke.key == 0) {
+ command_errmsg = "could not parse key stroke";
+ return CMD_ERROR;
+ }
+
+ struct prompt_keybind *bind = prompt_find_binding(stroke.mods, stroke.key);
+
+ if (bind == NULL) {
+ command_errmsg = "could not find any binding for key stroke";
+ return CMD_ERROR;
+ }
+
+ prompt_remove_binding(bind);
+
+ return CMD_OK;
+}
+
+COMMAND_SET(showkey, "showkey", "shows how a single keystroke is parsed", command_showkey);
+
+static int
+command_showkey(int argc, char *argv[]) {
+ if (argc != 1) {
+ command_errmsg = "wrong number of arguments";
+ return CMD_ERROR;
+ }
+
+ struct prompt_input in = { 0 };
+
+ while (in.mods == 0 && in.key == 0)
+ in = prompt_parse_input(getchar());
+
+ prompt_print_stroke(in);
+ printf("\n");
+
+ return CMD_OK;
+}
diff --git a/stand/common/prompt_editing.h b/stand/common/prompt_editing.h
new file mode 100644
--- /dev/null
+++ b/stand/common/prompt_editing.h
@@ -0,0 +1,102 @@
+/*-
+ * Copyright (c) 2022 Connor Bailey
+ *
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#define PROMPT_LINE_LENGTH 256
+
+/*
+ * The prompt is just a gap buffer, with a single kill buffer, and a linked list
+ * of history entries.
+ */
+
+struct prompt_history_entry {
+ char line[PROMPT_LINE_LENGTH];
+ TAILQ_ENTRY(prompt_history_entry) entry;
+};
+
+struct prompt_buffer {
+ char line[PROMPT_LINE_LENGTH + 1];
+ int cursor;
+ int gap;
+
+ char kill[PROMPT_LINE_LENGTH + 1];
+ int killcursor;
+
+ TAILQ_HEAD(prompt_history_head, prompt_history_entry) history_head;
+ struct prompt_history_entry *history_cursor;
+};
+
+extern struct prompt_buffer prompt_prompt;
+
+void prompt_init();
+void prompt_reset();
+void prompt_rawinput(char);
+char *prompt_getline();
+
+/*
+ * Editing actions
+ */
+
+void prompt_forward_char(void *);
+void prompt_backward_char(void *);
+
+void prompt_move_end_of_line(void *);
+void prompt_move_beginning_of_line(void *);
+
+void prompt_forward_word(void *);
+void prompt_backward_word(void *);
+
+void prompt_delete_forward_char(void *);
+void prompt_delete_backward_char(void *);
+
+void prompt_yank(void *);
+
+void prompt_forward_kill_word(void *);
+void prompt_backward_kill_word(void *);
+void prompt_kill_line(void *);
+
+void prompt_next_history_element(void *);
+void prompt_previous_history_element(void *);
+
+/*
+ * History manipulation
+ */
+
+void prompt_history_add(const char *, int);
+void prompt_history_remove(struct prompt_history_entry *);
+struct prompt_history_entry *prompt_history_first();
+struct prompt_history_entry *prompt_history_next(struct prompt_history_entry *);
+
+/*
+ * Completion
+ */
+
+void prompt_complete_command(void*);
+void prompt_complete_smart(void*);
+
+typedef void*(generic_completer_first)();
+typedef void*(generic_completer_next_item)(void *);
+typedef void(generic_completer_item_to_string)(void *, char *, int);
+
+void prompt_generic_complete(char *, generic_completer_first, generic_completer_next_item, void *, generic_completer_item_to_string);
+
+typedef void(*prompt_completer)(char *, char *);
+
+void keyunbind_completer(char *, char *);
+void environ_completer(char *, char *);
+void predefined_action_completer(char *, char *);
+void path_completer(char *, char *);
+
+typedef struct {
+ const char *command;
+ int argn;
+ prompt_completer completer;
+} prompt_completion_entry;
+
+#define COMPLETION_SET(command, argn, func) \
+ static prompt_completion_entry _completer_ ## command ## argn = { #command, argn, func }; \
+ DATA_SET(Xcompleter_set, _completer_ ## command ## argn)
+
+SET_DECLARE(Xcompleter_set, prompt_completion_entry);
\ No newline at end of file
diff --git a/stand/common/prompt_editing.c b/stand/common/prompt_editing.c
new file mode 100644
--- /dev/null
+++ b/stand/common/prompt_editing.c
@@ -0,0 +1,915 @@
+/*-
+ * Copyright (c) 2022 Connor Bailey
+ *
+ * SPDX-License-Identifier: BSD-2-clause
+ */
+
+#include <stand.h>
+#include <string.h>
+#include "bootstrap.h"
+
+#include "prompt_bindings.h"
+#include "prompt_editing.h"
+
+/*
+ * Helper macros, just to make each action a bit more readable
+ */
+
+#define LINE (prompt_prompt.line)
+#define CURSOR (prompt_prompt.cursor)
+#define GAP (prompt_prompt.gap)
+#define KILL (prompt_prompt.kill)
+#define KILLCURSOR (prompt_prompt.killcursor)
+#define HISTORY (&prompt_prompt.history_head)
+#define HISTORYCURSOR (prompt_prompt.history_cursor)
+
+static void
+prompt_show_aftergap()
+{
+ /*
+ * Clear to end of line, reprint "LINE[GAP:END]", move cursor back so it is
+ * right before the contents of the gap.
+ */
+
+ int gaplen = PROMPT_LINE_LENGTH - GAP;
+ char *aftergap = &LINE[GAP];
+
+ printf("\x1b[0K");
+
+ if (gaplen) {
+ printf("%s", aftergap);
+ printf("\x1b[%dD", gaplen);
+ }
+}
+
+static void
+prompt_reprint()
+{
+ interp_emit_prompt();
+
+ LINE[CURSOR] = 0;
+ printf("%s", LINE);
+
+ prompt_show_aftergap();
+}
+
+void
+prompt_reset()
+{
+ /*
+ * Called to simulate "end of line" in the gap buffer
+ */
+
+ CURSOR = 0;
+ GAP = PROMPT_LINE_LENGTH;
+ LINE[GAP] = '\0';
+}
+
+void
+prompt_init()
+{
+ /*
+ * Called once to get the buffer ready to go
+ */
+
+ prompt_reset();
+ KILLCURSOR = 0;
+ KILL[KILLCURSOR] = '\0';
+
+ TAILQ_INIT(HISTORY);
+}
+
+void
+prompt_rawinput(char in)
+{
+ /*
+ * Add a character to the buffer without processing it as input
+ */
+
+ LINE[CURSOR++] = in;
+
+ printf("%c", in);
+
+ prompt_show_aftergap();
+}
+
+char *
+prompt_getline()
+{
+ /*
+ * To get a whole line, we just need to move GAP to the end of the line
+ * which will put the entire line left of the gap, with CURSOR being the
+ * length of the line
+ *
+ * We also use this as a chance to add the line to the history, and reset
+ * the history pointer. This makes the next "history-previous-element" get
+ * the item which we just added to the history.
+ */
+
+ prompt_move_end_of_line(NULL);
+ LINE[CURSOR] = '\0';
+ HISTORYCURSOR = NULL;
+
+ if (CURSOR != 0) {
+ prompt_history_add(LINE, CURSOR);
+ }
+
+ return LINE;
+}
+
+void
+prompt_forward_char(void *data)
+{
+ if (GAP != PROMPT_LINE_LENGTH) {
+ LINE[CURSOR++] = LINE[GAP++];
+
+ printf("\x1b[1C");
+ }
+}
+
+PREDEF_ACTION_SET("forward-char", prompt_forward_char);
+
+void
+prompt_backward_char(void *data)
+{
+ if (CURSOR != 0) {
+ LINE[--GAP] = LINE[--CURSOR];
+
+ printf("\x1b[1D");
+ }
+}
+
+PREDEF_ACTION_SET("backward-char", prompt_backward_char);
+
+void
+prompt_delete_backward_char(void *data)
+{
+ if (CURSOR != 0) {
+ LINE[--CURSOR] = '\0';
+
+ putchar('\b');
+
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("delete-backward-char", prompt_delete_backward_char);
+
+void
+prompt_delete_forward_char(void *data)
+{
+ if (GAP != PROMPT_LINE_LENGTH) {
+ LINE[GAP++] = '\0';
+
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("delete-forward-char", prompt_delete_forward_char);
+
+void
+prompt_move_end_of_line(void *data)
+{
+ int gapsize = PROMPT_LINE_LENGTH - GAP;
+
+ if (gapsize != 0) {
+ printf("\x1b[%iC", gapsize);
+
+ for (int i = 0; i < gapsize; i++) {
+ LINE[CURSOR++] = LINE[GAP++];
+ }
+ }
+}
+
+PREDEF_ACTION_SET("move-end-of-line", prompt_move_end_of_line);
+
+void
+prompt_move_beginning_of_line(void *data)
+{
+ int cursorsize = CURSOR;
+
+ if (cursorsize != 0) {
+ printf("\x1b[%iD", cursorsize);
+
+ for (int i = 0; i < cursorsize; i++) {
+ LINE[--GAP] = LINE[--CURSOR];
+ }
+ }
+}
+
+PREDEF_ACTION_SET("move-beginning-of-line", prompt_move_beginning_of_line);
+
+static int
+count_forward_word()
+{
+ /*
+ * Ignore whitespace, then consume a whole word forward
+ */
+
+ int gapsize = PROMPT_LINE_LENGTH - GAP;
+ int run = 0;
+
+ for (; run < gapsize && isspace(LINE[GAP + run]); run++);
+ for (; run < gapsize && isgraph(LINE[GAP + run]); run++);
+
+ return run;
+}
+static int
+count_backward_word()
+{
+ /*
+ * Ignore whitespace, then consume a whole word backward
+ */
+
+ int cursorsize = CURSOR;
+ int run = 0;
+
+ for (; run < cursorsize && isspace(LINE[CURSOR - run - 1]); run++);
+ for (; run < cursorsize && isgraph(LINE[CURSOR - run - 1]); run++);
+
+ return run;
+}
+
+void
+prompt_forward_word(void *data)
+{
+ int run = count_forward_word();
+
+ if (run != 0) {
+ for (int i = 0; i < run; i++) {
+ LINE[CURSOR++] = LINE[GAP++];
+ }
+
+ printf("\x1b[%iC", run);
+ }
+}
+
+PREDEF_ACTION_SET("forward-word", prompt_forward_word);
+
+void
+prompt_backward_word(void *data)
+{
+ int run = count_backward_word();
+
+ if (run != 0) {
+ for (int i = 0; i < run; i++) {
+ LINE[--GAP] = LINE[--CURSOR];
+ }
+
+ printf("\x1b[%iD", run);
+ }
+}
+
+PREDEF_ACTION_SET("backward-word", prompt_backward_word);
+
+void
+prompt_yank(void *data)
+{
+ /*
+ * Copy KILL[0:KILLCURSOR] back into LINE, starting at CURSOR
+ * (pushing the gap back)
+ */
+
+ if (KILLCURSOR) {
+ for (int i = 0; i < KILLCURSOR; i++) {
+ LINE[CURSOR++] = KILL[i];
+ printf("%c", KILL[i]);
+ }
+
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("yank", prompt_yank);
+
+void
+prompt_forward_kill_word(void *data)
+{
+ int run = count_forward_word();
+
+ if (run != 0) {
+ /*
+ * Find a word, copy it into KILL, then remove it from the gap
+ */
+
+ memcpy(KILL, &LINE[GAP], run);
+ KILLCURSOR = run;
+
+ GAP += run;
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("kill-word", prompt_forward_kill_word);
+
+void
+prompt_backward_kill_word(void *data)
+{
+ int run = count_backward_word();
+
+ if (run != 0) {
+ /*
+ * Find a word, copy it into KILL, then remove it from the end of CURSOR
+ */
+
+ memcpy(KILL, &LINE[CURSOR - run], run);
+ KILLCURSOR = run;
+
+ printf("\x1b[%iD", run);
+
+ CURSOR -= run;
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("backward-kill-word", prompt_backward_kill_word);
+
+void
+prompt_kill_line(void *data)
+{
+ prompt_move_beginning_of_line(NULL);
+
+ int gapsize = PROMPT_LINE_LENGTH - GAP;
+
+ if (gapsize) {
+ /*
+ * Kill an entire line, CURSOR is already 0'd by moving to the start of the
+ * line, so we just need to reset GAP to reset the buffer.
+ */
+
+ memcpy(KILL, &LINE[GAP], gapsize);
+ KILLCURSOR = gapsize;
+
+ GAP = PROMPT_LINE_LENGTH;
+
+ prompt_show_aftergap();
+ }
+}
+
+PREDEF_ACTION_SET("kill-line", prompt_kill_line);
+
+void
+prompt_recall_history(struct prompt_history_entry *entry)
+{
+ /*
+ * Clear the command line, then recall a whole line from history (if there is one)
+ */
+
+ prompt_move_beginning_of_line(NULL);
+ prompt_reset();
+ prompt_show_aftergap();
+
+ if (entry != NULL) {
+ CURSOR = strlen(entry->line);
+ memcpy(LINE, entry->line, CURSOR);
+ printf("%s", entry->line);
+ }
+}
+
+void
+prompt_next_history_element(void *data)
+{
+ /*
+ * "next-history-element" functions as "delete-line" when at the start of
+ * history
+ */
+
+ if (HISTORYCURSOR != NULL) {
+ HISTORYCURSOR = TAILQ_NEXT(HISTORYCURSOR, entry);
+ }
+
+ prompt_recall_history(HISTORYCURSOR);
+}
+
+PREDEF_ACTION_SET("next-history-element", prompt_next_history_element);
+
+void
+prompt_previous_history_element(void *data)
+{
+ /*
+ * "previous-history-element" at the start of history starts at the most
+ * recently added entry
+ */
+
+ if (HISTORYCURSOR == NULL) {
+ HISTORYCURSOR = TAILQ_LAST(HISTORY, prompt_history_head);
+ }
+ else {
+ HISTORYCURSOR = TAILQ_PREV(HISTORYCURSOR, prompt_history_head, entry);
+ }
+
+ prompt_recall_history(HISTORYCURSOR);
+}
+
+PREDEF_ACTION_SET("previous-history-element", prompt_previous_history_element);
+
+/*
+ * History command/Lua interface
+ */
+
+void
+prompt_history_add(const char *line, int len)
+{
+ struct prompt_history_entry *entry = malloc(sizeof(struct prompt_history_entry));
+ memcpy(entry->line, line, len);
+
+ TAILQ_INSERT_TAIL(HISTORY, entry, entry);
+}
+void
+prompt_history_remove(struct prompt_history_entry *entry)
+{
+ TAILQ_REMOVE(HISTORY, entry, entry);
+ free(entry);
+}
+
+/*
+ * Used by Lua to populate the "history" global
+ */
+
+struct prompt_history_entry *
+prompt_history_first()
+{
+ return TAILQ_FIRST(HISTORY);
+}
+struct prompt_history_entry *
+prompt_history_next(struct prompt_history_entry *entry)
+{
+ return TAILQ_NEXT(entry, entry);
+}
+
+COMMAND_SET(history, "history", "display history entries", command_history);
+
+static int
+command_history(int argc, char *argv[])
+{
+ struct prompt_history_entry *e;
+
+ /*
+ * Pop the "history" entry from the history
+ */
+ prompt_history_remove(TAILQ_LAST(HISTORY, prompt_history_head));
+
+ pager_open();
+
+ TAILQ_FOREACH(e, HISTORY, entry) {
+ pager_output(e->line);
+ pager_output("\n");
+ }
+
+ pager_close();
+
+ return 0;
+}
+
+/*
+ * Completion logic
+ */
+
+#define PROMPT_COLUMNS 80
+
+void
+prompt_generic_complete(char *argv, generic_completer_first first, generic_completer_next_item next,
+ void *last, generic_completer_item_to_string tostring)
+{
+ /*
+ * Technically, we should be able to call tostring on the same
+ * "handle" at any time and get the same result, but unfortunately
+ * that doesn't work for dirents, so instead of remembering the exact
+ * item that we've matched, in all cases we just remember what the
+ * item stringified to (and recall that instead).
+ */
+
+ char buf[40] = { 0 };
+
+ char maxbuf[40] = { 0 };
+ int maxlen = 0;
+
+ char matchbuf[40] = { 0 };
+ int matches = 0;
+
+ int arglen = strlen(argv);
+
+ void* p = first();
+
+ while (p != last) {
+ tostring(p, buf, sizeof(buf));
+ int len = strlen(buf);
+
+ if (len >= arglen && strncmp(argv, buf, arglen) == 0) {
+ matches++;
+
+ if (matches == 1) {
+ memcpy(matchbuf, buf, sizeof(buf));
+ }
+
+ if (len > maxlen) {
+ memcpy(maxbuf, buf, sizeof(buf));
+ maxlen = len;
+ }
+ }
+
+ p = next(p);
+ }
+
+ if (matches == 0) {
+ return;
+ }
+ else if (matches == 1) {
+ /*
+ * Single match, just complete it.
+ */
+
+ char *remainder = matchbuf + arglen;
+ int rlen = strlen(remainder);
+
+ printf("%s", remainder);
+ memcpy(&LINE[CURSOR], remainder, rlen);
+ CURSOR += rlen;
+ } else {
+ /*
+ * Many matches, print all aligned into columns, and then re-print the
+ * prompt and command line below all options.
+ * If all matches share a common prefix though, complete through to the
+ * end of the prefix so you can "jump" through options by just typing
+ * the few characters of difference.
+ */
+
+ char *prefixbuf = maxbuf;
+ int prefixlen = strlen(prefixbuf);
+
+ int column = 0;
+ int maxcolums = PROMPT_COLUMNS / maxlen;
+
+ printf("\n");
+ pager_open();
+
+ p = first();
+
+ while (p != last) {
+ tostring(p, buf, sizeof(buf));
+ int len = strlen(buf);
+
+ if (len >= arglen && strncmp(argv, buf, arglen) == 0) {
+ pager_output(buf);
+
+ if (++column == maxcolums) {
+ column = 0;
+
+ if (pager_output("\n")) {
+ break;
+ }
+ } else {
+ for (int i = len; i <= maxlen; i++) {
+ pager_output(" ");
+ }
+ }
+
+ for (int i = 0; i < prefixlen; i++) {
+ if (buf[i] != prefixbuf[i]) {
+ prefixbuf[i] = '\0';
+ prefixlen = i;
+ break;
+ }
+ }
+ }
+
+ p = next(p);
+ }
+
+ pager_close();
+ printf("\n");
+ prompt_reprint();
+
+ if (prefixlen != 0 && prefixlen > arglen) {
+ char *remainder = prefixbuf + arglen;
+ int rlen = prefixlen - arglen;
+
+ printf("%s", remainder);
+ memcpy(&LINE[CURSOR], remainder, rlen);
+ CURSOR += rlen;
+ }
+ }
+}
+
+/*
+ * Command completer
+ */
+
+static void *
+command_first()
+{
+ return SET_BEGIN(Xcommand_set);
+}
+static void *
+command_next(void *rawlast)
+{
+ struct bootblk_command **pcmd = rawlast;
+
+ return (void*)++pcmd;
+}
+static void
+command_tostring(void *rawlast, char *out, int len)
+{
+ struct bootblk_command **pcmd = rawlast;
+
+ snprintf(out, len, "%s", (*pcmd)->c_name);
+}
+
+void prompt_complete_command(void *data) {
+ LINE[CURSOR] = 0;
+
+ prompt_generic_complete(LINE, command_first, command_next, SET_LIMIT(Xcommand_set), command_tostring);
+}
+
+PREDEF_ACTION_SET("command-complete", prompt_complete_command);
+
+/*
+ * "smart"/context sensitive completer
+ */
+
+void
+prompt_complete_smart(void *data)
+{
+ /*
+ * "smart" completion, completes a command if there isn't one typed out
+ * already, or tries to complete command arguments.
+ */
+
+ if (GAP != PROMPT_LINE_LENGTH) {
+ return;
+ }
+
+ int cmdlen = 0;
+
+ for (; cmdlen < CURSOR && isalnum(LINE[cmdlen]); cmdlen++);
+
+ if (!(isspace(LINE[cmdlen]) || ispunct(LINE[cmdlen])) || cmdlen == CURSOR) {
+ prompt_complete_command(NULL);
+ return;
+ }
+
+ char old = LINE[cmdlen];
+ LINE[cmdlen] = '\0';
+
+ char *command = LINE;
+
+ char args[PROMPT_LINE_LENGTH] = { 0 };
+ memcpy(args, &LINE[cmdlen + 1], CURSOR - cmdlen - 1);
+
+ /*
+ * Find the number and text of the last/most recent argument so the completer
+ * has enough context to actually complete the argument.
+ * Doesn't need to be bulletproof since we only need the last arg, and the count.
+ */
+
+ int argc = 1;
+ char *last = args;
+ char *next = strpbrk(args, "\t\f\v ");
+
+ while (next != NULL) {
+ *next = '\0';
+ last = next + 1;
+
+ next = strpbrk(last, "\t\f\v ");
+ argc++;
+ }
+
+ /*
+ * There's two special cases for completion, either a "_" command which
+ * matches any undefined command, or a "0" arg index, which matches any
+ * argument number.
+ * Matching an undefined command is an escape hatch for completing languages
+ * which are too complicated to properly parse here, and the "0" arg index
+ * is for commands involving flags and (again) more complicated parsing.
+ */
+
+ int defined = false;
+
+ struct bootblk_command **pcmd;
+ SET_FOREACH(pcmd, Xcommand_set) {
+ if (strcmp((*pcmd)->c_name, command) == 0) {
+ defined = true;
+ break;
+ }
+ }
+
+ prompt_completion_entry *entry = NULL;
+ prompt_completion_entry *fallthrough = NULL;
+
+ prompt_completion_entry **pce;
+ SET_FOREACH(pce, Xcompleter_set) {
+ prompt_completion_entry *e = *pce;
+
+ if (strcmp(e->command, command) == 0 && (e->argn == 0 || e->argn == argc)) {
+ entry = e;
+ }
+ else if (strcmp(e->command, "_") == 0 && !defined) {
+ fallthrough = e;
+ }
+ }
+
+ LINE[cmdlen] = old;
+
+ if (entry == NULL) {
+ if (fallthrough != NULL) {
+ fallthrough->completer(command, last);
+ }
+ } else {
+ entry->completer(command, last);
+ }
+}
+
+PREDEF_ACTION_SET("smart-complete", prompt_complete_smart);
+
+/*
+ * Misc completers, some command-specific, some generic
+ */
+
+static void *
+keybind_first()
+{
+ return (void*)prompt_first_binding();
+}
+static void *
+keybind_next(void *rawlast)
+{
+ return (void*)prompt_next_binding((struct prompt_keybind *)rawlast);
+}
+static void
+keybind_tostring(void *raw, char *out, int len)
+{
+ struct prompt_keybind *p = raw;
+
+ prompt_stroke_to_string(out, len, p->target);
+}
+
+void
+keyunbind_completer(char *command, char *argv)
+{
+ prompt_generic_complete(argv, keybind_first, keybind_next, NULL, keybind_tostring);
+}
+
+COMPLETION_SET(keybind, 2, predefined_action_completer);
+
+static void *
+environ_first()
+{
+ return environ;
+}
+static void *
+environ_next(void* rawlast)
+{
+ struct env_var *ev = rawlast;
+
+ return ev->ev_next;
+}
+static void
+environ_tostring(void *raw, char *out, int len)
+{
+ struct env_var *ev = raw;
+
+ snprintf(out, len, "%s", ev->ev_name);
+}
+
+void
+environ_completer(char *command, char *argv)
+{
+ prompt_generic_complete(argv, environ_first, environ_next, NULL, environ_tostring);
+}
+
+COMPLETION_SET(show, 1, environ_completer);
+COMPLETION_SET(set, 1, environ_completer);
+COMPLETION_SET(unset, 1, environ_completer);
+
+static void *
+predef_first()
+{
+ return SET_BEGIN(Xpredef_action_set);
+}
+static void *
+predef_next(void* rawlast)
+{
+ struct prompt_predefined_action **ppa = rawlast;
+
+ return (void*)++ppa;
+}
+static void
+predef_tostring(void *rawlast, char *out, int len)
+{
+ struct prompt_predefined_action **ppa = rawlast;
+
+ snprintf(out, len, "%s", (*ppa)->name);
+}
+
+void
+predefined_action_completer(char *command, char *argv)
+{
+ prompt_generic_complete(argv, predef_first, predef_next, SET_LIMIT(Xpredef_action_set), predef_tostring);
+}
+
+/*
+ * Path completion doesn't fit into the first/next/tostring model super well,
+ * since we're working with an fd which contains its own state. Plus, since
+ * first() gets called twice, we need to have it reset the internal state of that
+ * fd so we can read all dirents again.
+ * In practice, this just means we need to remember the fd, and the name of the
+ * fd so it can be reopened.
+ */
+
+static const char *path_dirname;
+static int path_fd;
+
+static void *
+path_next(void *raw)
+{
+ return readdirfd(path_fd);
+}
+
+static void *
+path_first()
+{
+ if (path_fd > 0) {
+ close(path_fd);
+ }
+
+ path_fd = open(path_dirname, O_RDONLY);
+
+ return path_next(NULL);
+}
+
+static void
+path_tostring(void *rawlast, char *out, int len)
+{
+ struct dirent *entry = rawlast;
+
+ /*
+ * We need to ensure that path_dirname contains a path with a trailing "/"
+ * otherwise we'll spit out a bunch of garbage that can't be completed.
+ * In practice, the only path *with* a trailing "/" is going to be "/"
+ * itself since our dirname/basename split will always destroy the last "/".
+ */
+
+ int dirnamelen = snprintf(out, len, "%s", path_dirname);
+ out += dirnamelen;
+ len -= dirnamelen;
+
+ if (*(out - 1) != '/') {
+ *out++ = '/';
+ len--;
+ }
+
+ /*
+ * We also want to show directories (not "." and ".." though) with a trailing
+ * "/" so they can be completed as a whole, and then any of their entries
+ * can also be completed with minimal typing.
+ */
+
+ char *fmt = "%s";
+
+ if ((entry->d_type & DT_DIR) && strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) {
+ fmt = "%s/";
+ }
+
+ snprintf(out, len, fmt, entry->d_name);
+}
+
+void
+path_completer(char *command, char *argv)
+{
+ /*
+ * We want to split argv into a dirname/basename pair, so we can check each
+ * entry inside of dirname if it matches the prefix basename.
+ * So, we just need to find the last "/" in argv, and replace it with a null
+ * terminator. Then ("/" + 1) is our basename, and argv is our dirname.
+ * However, since path_tostring gives us an absolute path, we actually need
+ * to throw away the basename and match against the (already absolute)
+ * path in argv, which is why we operate on the copy "path" instead.
+ */
+
+ char path[128] = { 0 };
+ snprintf(path, 128, "%s", argv);
+
+ if (strlen(path) == 0) {
+ path[0] = '/';
+ }
+
+ char *dirname = path;
+ char *basename = path + strlen(path);
+
+ while (basename > dirname && *(basename - 1) != '/') {
+ basename--;
+ }
+
+ *(basename - 1) = '\0';
+
+ if (strlen(dirname) == 0) {
+ dirname = "/";
+ }
+
+ path_dirname = dirname;
+ path_fd = 0;
+
+ prompt_generic_complete(argv, path_first, path_next, NULL, path_tostring);
+}
+
+COMPLETION_SET(ls, 0, path_completer);
\ No newline at end of file
diff --git a/stand/efi/libefi/efi_console.c b/stand/efi/libefi/efi_console.c
--- a/stand/efi/libefi/efi_console.c
+++ b/stand/efi/libefi/efi_console.c
@@ -682,6 +682,15 @@
end_term();
}
+static void
+CR(int x, int y)
+{
+ if (args[0] > 0)
+ args[0]--;
+ curs_move(&curx, &cury, curx + x, cury + y);
+ end_term();
+}
+
/* Home cursor (left top corner), also called from mode command. */
void
HO(void)
@@ -792,6 +801,18 @@
else
args[++argc] = 0;
break;
+ case 'C':
+ if (argc < 0)
+ CR(1, 0);
+ else
+ CR(args[0], 0);
+ break;
+ case 'D':
+ if (argc < 0)
+ CR(-1, 0);
+ else
+ CR(-args[0], 0);
+ break;
case 'H': /* ho = \E[H */
if (argc < 0)
HO();
@@ -806,6 +827,12 @@
else
bail_out(c);
break;
+ case 'K':
+ if (argc < 0)
+ CL(0);
+ else
+ CL(args[0]);
+ break;
case 'm':
if (argc < 0) {
fg_c = DEFAULT_FGCOLOR;
@@ -1261,7 +1288,7 @@
}
/*
- * Converts an EFI key shift state into a PC style modifer mask
+ * Converts an EFI key shift state into a PC style modifier mask
*/
static char
@@ -1307,7 +1334,7 @@
}
/*
- * Writes a xtern style input escape to keybuf, with modifiers
+ * Writes a xterm style input escape to keybuf, with modifiers
*/
static void
@@ -1350,7 +1377,7 @@
else if (kss & EFI_RIGHT_ALT_PRESSED ||
kss & EFI_LEFT_ALT_PRESSED) {
/*
- * alt+[a-z] to ESC [ char
+ * alt+[a-z] to esc
*/
keybuf[0] = 0x1b; /* esc */
@@ -1393,14 +1420,14 @@
case SCAN_DELETE:
keybuf_insvt('3', kss);
break;
- case SCAN_PAGE_UP: /* PGUP */
+ case SCAN_PAGE_UP:
keybuf_insvt('5', kss);
break;
case SCAN_PAGE_DOWN:
keybuf_insvt('6', kss);
break;
case SCAN_ESC:
- keybuf[0] = 0x1b; /* esc */
+ keybuf[0] = 0x1b; /* esc */
break;
default:
keybuf[0] = key->UnicodeChar;
diff --git a/stand/i386/libi386/vidconsole.c b/stand/i386/libi386/vidconsole.c
--- a/stand/i386/libi386/vidconsole.c
+++ b/stand/i386/libi386/vidconsole.c
@@ -1070,6 +1070,180 @@
vidc_biosputchar(c);
}
+/*
+ * Converts BIOS keyboard flags into a PC style modifer mask
+ */
+
+static char
+keybuf_kss2mod(uint32_t kss) {
+ char modifiers = 0;
+
+ if ((kss & 0x1) ||
+ (kss & 0x2)) {
+ /* left shift or right shift */
+
+ modifiers |= 1;
+ }
+
+ if (kss & 0x4) {
+ /* ctrl */
+
+ modifiers |= 4;
+ }
+
+ if (kss & 0x8) {
+ /* alt */
+
+ modifiers |= 2;
+ }
+
+ return '1' + modifiers;
+}
+
+/*
+ * Writes a VT220 style input escape to keybuf, with modifiers
+ */
+
+static int
+keybuf_insvt(const char keycode, uint32_t kss) {
+ int i = 0;
+
+ keybuf[i++] = '[';
+ keybuf[i++] = keycode;
+
+ if ((kss & 0xf) != 0) {
+ /* ignore all BIOS keyboard flags besides shift/ctrl/alt */
+
+ keybuf[i++] = ';';
+ keybuf[i++] = keybuf_kss2mod(kss);
+ }
+
+ keybuf[i++] = '~';
+
+ return 0x1b; /* esc */
+}
+
+/*
+ * Writes a xtern style input escape to keybuf, with modifiers
+ */
+
+static int
+keybuf_insxterm(const char key, uint32_t kss) {
+ int i = 0;
+
+ keybuf[i++] = '[';
+
+ if ((kss & 0xf) != 0) {
+ /* ignore all BIOS keyboard flags besides shift/ctrl/alt */
+
+ keybuf[i++] = '1';
+ keybuf[i++] = ';';
+
+ keybuf[i++] = keybuf_kss2mod(kss);
+ }
+
+ keybuf[i++] = key;
+
+ return 0x1b; /* esc */
+}
+
+/* Helpers to normalize BIOS scancodes
+ *
+ * Unfortunately the BIOS encodes modifiers into the scancode
+ * and alt even zeros out the ASCII code we get back as well,
+ * so to properly support alt+key we need a big lookup to convert
+ * the modified scancodes back into the "normal" ones, and then
+ * deal with just normal scancodes and modifiers.
+ *
+ * Additionally, special keys have seemingly random scancodes with
+ * no apparent relationship between normal/ctrl/alt versions, so
+ * we also need to remap those as well.
+ *
+ * The only pattern I've found is that the shifted scancodes are
+ * the same as the normal scancodes, only the embedded ASCII code
+ * changes.
+ */
+
+struct bios_scancode_remap {
+ union {
+ struct {
+ union {
+ unsigned char normal;
+ unsigned char shift;
+ };
+
+ unsigned char ctrl;
+ unsigned char alt;
+ };
+
+ unsigned char codes[3];
+ };
+
+ char ascii;
+};
+
+static struct bios_scancode_remap bios_sc_map[] = {
+/* normal ctrl alt ascii */
+ {0x1e, 0x1e, 0x1e, 'a'},
+ {0x30, 0x30, 0x30, 'b'},
+ {0x2e, 0x2e, 0x2e, 'c'},
+ {0x20, 0x20, 0x20, 'd'},
+ {0x12, 0x12, 0x12, 'e'},
+ {0x21, 0x21, 0x21, 'f'},
+ {0x22, 0x22, 0x22, 'g'},
+ {0x23, 0x23, 0x23, 'h'},
+ {0x17, 0x17, 0x17, 'i'},
+ {0x24, 0x24, 0x24, 'j'},
+ {0x25, 0x25, 0x25, 'k'},
+ {0x26, 0x26, 0x26, 'l'},
+ {0x32, 0x32, 0x32, 'm'},
+ {0x31, 0x31, 0x31, 'n'},
+ {0x18, 0x18, 0x18, 'o'},
+ {0x19, 0x19, 0x19, 'p'},
+ {0x10, 0x10, 0x10, 'q'},
+ {0x13, 0x13, 0x13, 'r'},
+ {0x1f, 0x1f, 0x1f, 's'},
+ {0x14, 0x14, 0x14, 't'},
+ {0x16, 0x16, 0x16, 'u'},
+ {0x2f, 0x2f, 0x2f, 'v'},
+ {0x11, 0x11, 0x11, 'w'},
+ {0x2d, 0x2d, 0x2d, 'x'},
+ {0x15, 0x15, 0x15, 'y'},
+ {0x2c, 0x2c, 0x2c, 'z'},
+ {0x02, 0xff, 0x78, '1'},
+ {0x03, 0x03, 0x79, '2'},
+ {0x04, 0xff, 0x7a, '3'},
+ {0x05, 0xff, 0x7b, '4'},
+ {0x06, 0xff, 0x7c, '5'},
+ {0x07, 0x07, 0x7d, '6'},
+ {0x08, 0xff, 0x7e, '7'},
+ {0x09, 0xff, 0x7f, '8'},
+ {0x0a, 0xff, 0x80, '9'},
+ {0x0b, 0xff, 0x81, '0'},
+ {0x0c, 0x0c, 0x82, '-'},
+ {0x0d, 0xff, 0x83, '='},
+ {0x1a, 0x1a, 0x1a, '['},
+ {0x1b, 0x1b, 0x1b, ']'},
+ {0x27, 0xff, 0x27, ';'},
+ {0x28, 0xff, 0xff, '\''},
+ {0x29, 0xff, 0xff, '`'},
+ {0x0e, 0x0e, 0x0e, 0x08}, /* backspace */
+ {0x53, 0x93, 0xa3, 0x00}, /* del */
+ {0x50, 0x91, 0xa0, 0x00}, /* down */
+ {0x4f, 0x75, 0x9f, 0x00}, /* end */
+ {0x1c, 0x1c, 0xa6, 0x0a}, /* enter */
+ {0x01, 0x01, 0x01, 0x00}, /* esc */
+ {0x47, 0x77, 0x97, 0x00}, /* home */
+ {0x52, 0x92, 0xa2, 0x00}, /* ins */
+ {0x4b, 0x73, 0x9b, 0x00}, /* left */
+ {0x51, 0x76, 0xa1, 0x00}, /* pgdn */
+ {0x49, 0x84, 0x99, 0x00}, /* pgup */
+ {0x4d, 0x74, 0x9d, 0x00}, /* right */
+ {0x39, 0x39, 0x39, ' '}, /* spacebar */
+ {0x0f, 0x94, 0xa5, '\t'}, /* tab */
+ {0x48, 0x8d, 0x98, 0x00}, /* up */
+};
+
static int
vidc_getchar(void)
{
@@ -1088,30 +1262,73 @@
v86.addr = 0x16;
v86.eax = 0x0;
v86int();
- if ((v86.eax & 0xff) != 0) {
- return (v86.eax & 0xff);
+
+ int scancode = (v86.eax & 0xff00) >> 8;
+ int character = v86.eax & 0xff;
+
+ v86.ctl = 0;
+ v86.addr = 0x16;
+ v86.eax = 0x200;
+ v86int();
+
+ int modifiers = v86.eax & 0xff;
+
+ int len = sizeof(bios_sc_map) / sizeof(bios_sc_map[0]);
+
+ for (int i = 0; i < len; i++) {
+ struct bios_scancode_remap* map = &bios_sc_map[i];
+
+ for (int c = 0; c < 3; c++) {
+ if (map->codes[c] == scancode) {
+ scancode = map->normal;
+
+ if (character == 0) {
+ /*
+ * preserve character from BIOS so we don't
+ * have to manually handle shift/control
+ */
+
+ character = map->ascii;
+ }
+
+ break;
+ }
+ }
}
-
- /* extended keys */
- switch (v86.eax & 0xff00) {
- case 0x4800: /* up */
- keybuf[0] = '[';
- keybuf[1] = 'A';
- return (0x1b); /* esc */
- case 0x4b00: /* left */
- keybuf[0] = '[';
- keybuf[1] = 'D';
- return (0x1b); /* esc */
- case 0x4d00: /* right */
- keybuf[0] = '[';
- keybuf[1] = 'C';
- return (0x1b); /* esc */
- case 0x5000: /* down */
- keybuf[0] = '[';
- keybuf[1] = 'B';
- return (0x1b); /* esc */
+
+ switch (scancode) {
+ case 0x48: /* up */
+ return keybuf_insxterm('A', modifiers);
+ case 0x4b: /* left */
+ return keybuf_insxterm('D', modifiers);
+ case 0x4d: /* right */
+ return keybuf_insxterm('C', modifiers);
+ case 0x50: /* down */
+ return keybuf_insxterm('B', modifiers);
+ case 0x47: /* home */
+ return keybuf_insxterm('H', modifiers);
+ case 0x4f: /* end */
+ return keybuf_insxterm('F', modifiers);
+ case 0x52: /* insert */
+ return keybuf_insvt('2', modifiers);
+ case 0x53: /* delete */
+ return keybuf_insvt('3', modifiers);
+ case 0x49: /* pgup */
+ return keybuf_insvt('5', modifiers);
+ case 0x51: /* pgdn */
+ return keybuf_insvt('6', modifiers);
default:
- return (-1);
+ if (modifiers & 0x8) {
+ /* alt */
+
+ if (('a' <= character) && (character <= 'z')) {
+ keybuf[0] = character;
+
+ return (0x1b); /* esc */
+ }
+ }
+
+ return character;
}
} else {
return (-1);
diff --git a/stand/loader.mk b/stand/loader.mk
--- a/stand/loader.mk
+++ b/stand/loader.mk
@@ -5,7 +5,7 @@
CFLAGS+=-I${LDRSRC}
SRCS+= boot.c commands.c console.c devopen.c interp.c
-SRCS+= interp_backslash.c interp_parse.c ls.c misc.c
+SRCS+= interp_backslash.c interp_parse.c prompt_bindings.c prompt_editing.c ls.c misc.c
SRCS+= module.c nvstore.c pnglite.c tslog.c
CFLAGS.module.c += -I$(SRCTOP)/sys/teken -I${SRCTOP}/contrib/pnglite

File Metadata

Mime Type
text/plain
Expires
Tue, Apr 28, 6:26 PM (5 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
32298264
Default Alt Text
D36585.id110583.diff (57 KB)

Event Timeline