feat: add rudimentary tab completion for executables to shell

This commit is contained in:
2026-06-16 14:02:46 +02:00
parent e3dc1e881d
commit 3b87ab5ec5
5 changed files with 104 additions and 4 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 25
#define MONTAUK_BUILD_NUMBER 64
+1 -1
View File
@@ -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 ----
+24 -2
View File
@@ -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;
}
+4
View File
@@ -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);
+74
View File
@@ -0,0 +1,74 @@
/*
* tabcompletion.cpp
* Tab completion module
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/string.h>
#include <montauk/syscall.h>
#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;
}