Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F154310324
D36585.id110583.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
57 KB
Referenced Files
None
Subscribers
None
D36585.id110583.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D36585: bootloader prompt: Add support for editing, history, and command/argument completion
Attached
Detach File
Event Timeline
Log In to Comment