From 3b87ab5ec5d4fdaa15e2fc35294fa2795d0f956f Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 16 Jun 2026 14:02:46 +0200 Subject: [PATCH] feat: add rudimentary tab completion for executables to shell --- kernel/src/Api/BuildNo.hpp | 2 +- programs/src/shell/Makefile | 2 +- programs/src/shell/main.cpp | 26 +++++++++- programs/src/shell/shell.h | 4 ++ programs/src/shell/tabcompletion.cpp | 74 ++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 programs/src/shell/tabcompletion.cpp diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 39cdee0..5b60b68 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 25 +#define MONTAUK_BUILD_NUMBER 64 diff --git a/programs/src/shell/Makefile b/programs/src/shell/Makefile index d52cb28..496861c 100644 --- a/programs/src/shell/Makefile +++ b/programs/src/shell/Makefile @@ -64,7 +64,7 @@ LDFLAGS := \ # ---- Source files ---- -SRCS := main.cpp vars.cpp builtins.cpp exec.cpp +SRCS := main.cpp vars.cpp builtins.cpp exec.cpp tabcompletion.cpp OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o)) # ---- Target ---- diff --git a/programs/src/shell/main.cpp b/programs/src/shell/main.cpp index efa7b49..f27f346 100644 --- a/programs/src/shell/main.cpp +++ b/programs/src/shell/main.cpp @@ -39,8 +39,6 @@ void sync_cwd() { // ---- Session info (read once at startup) ---- void read_session() { - // TODO Why is getuser treated as not defined in VSCode intellisense? - montauk::getuser( (char *)&session_user, // Buffer 32 // Buffer max size @@ -352,6 +350,10 @@ static constexpr uint8_t SC_DOWN = 0x50; static constexpr uint8_t SC_LEFT = 0x4B; static constexpr uint8_t SC_RIGHT = 0x4D; +// ---- Tab key scancode ---- + +static constexpr uint8_t SC_TAB = 0x0F; + static bool empty_key_event(const montauk::abi::KeyEvent& ev) { return ev.scancode == 0 && ev.ascii == 0 && !ev.pressed && !ev.shift && !ev.ctrl && !ev.alt; @@ -427,6 +429,26 @@ extern "C" void _start() { line[0] = '\0'; } } + + continue; + } + + if (ev.scancode == SC_TAB) { + line[pos] = '\0'; + + char* table[128] = { }; + int n = get_completions(line, table, 128); + + montauk::print("\n"); + for (int i = 0; i < n; i++) { + montauk::print(" "); + montauk::print(table[i]); + montauk::print("\n"); + } + + pos = 0; + prompt(); + continue; } diff --git a/programs/src/shell/shell.h b/programs/src/shell/shell.h index 52c139a..e457e61 100644 --- a/programs/src/shell/shell.h +++ b/programs/src/shell/shell.h @@ -126,3 +126,7 @@ bool switch_drive(int drive); // ---- External execution (exec.cpp) ---- int exec_external(const char* cmd, const char* args); + +// ---- Tab completion (tabcompletion.cpp) ---- + +int get_completions(char* string, char** array, int arrayMax); diff --git a/programs/src/shell/tabcompletion.cpp b/programs/src/shell/tabcompletion.cpp new file mode 100644 index 0000000..e95e2c4 --- /dev/null +++ b/programs/src/shell/tabcompletion.cpp @@ -0,0 +1,74 @@ +/* + * tabcompletion.cpp + * Tab completion module + * Copyright (c) 2026 Daniel Hammer + */ + +#include +#include + +#define RD_MAX 128 + +/* + Does the file path end with .elf? +*/ +bool is_executable(const char* path) { + size_t len = montauk::slen(path); + + if (path[len - 1] == 'f' && + path[len - 2] == 'l' && + path[len - 3] == 'e' && + path[len - 4] == '.') + { + return true; + } + + return false; +} + +/* + i.e., "shell.elf" => "shell" +*/ +const char* strip_elf_extension(const char* path) { + size_t len = montauk::slen(path); + + char* p = (char* ) path; + p[len - 4] = '\0'; + + return (const char*)p; +} + +int get_completions( // returns no. of results + char* string, // String to match/complete + char** array, // Array of strings containing completions + int arrayMax + ) { + + const char* names[RD_MAX] = { }; + + int n = montauk::readdir("0:/os", names, RD_MAX); + int r = 0; + + for (int i = 0; i < n; i++) { + /* + Shift by 3 characters (names[i] + 3): + "os/shell.elf" => "shell.elf" + + This is somewhat of a workaround. + */ + if (montauk::starts_with(names[i] + 3, (const char*)string)) { + if ( + r > arrayMax || + !is_executable(names[i]) + ) break; + + char* p = (char *)names[i] + 3; + strip_elf_extension(p); + + array[r] = (char *)p; + r++; + } + } + + return r; +}