/* * tabcompletion.cpp * Tab completion module * Copyright (c) 2026 Daniel Hammer */ #include #include #include "shell.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; } int find_first_slash_pos(const char* string) { int i = 0; while (string[i] != '\0') { if (string[i] == '/') { return i; } i++; } return -1; } /* 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; } const char* build_montauk_path( char* buffer, int max, int volume_number, const char* subpath ) { char volume_number_str[4]; // 3 digits (max. 999) + null byte int_to_str(volume_number, volume_number_str, 4); // example path: size_t vn_length = slen(volume_number_str); // 0 size_t subpath_length = slen(subpath); // users/admin size_t bytes_required = vn_length + subpath_length + 2; // + ":" and "/" (full path 0:/users/admin) if (max < bytes_required) { return nullptr; } int pos = 0; for (int i = 0; i < vn_length; i++) { buffer[pos] = volume_number_str[i]; pos++; } buffer[pos] = ':'; pos++; buffer[pos] = '/'; pos++; for (int i = 0; i < subpath_length; i++) { buffer[pos] = subpath[i]; pos++; } return buffer; } const char* remove_path_prefix( const char* prefix, // users/admin const char* string // users/admin/config/ ) { // result: config/ int pos = 0; while (string[pos] == prefix[pos]) { pos++; } return &string[pos]; } int get_completions( // returns no. of results char* string, // String to match/complete char** array, // Array of strings containing completions int arrayMax, char* cwd, int volume_number ) { // --- System commands (0:/os/*.elf) ---- 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 n characters (names[i] + n), n = position of first slash in path + 1: "os/shell.elf" => "shell.elf" This is somewhat of a workaround. */ int inc = find_first_slash_pos(names[i]) + 1; if (montauk::starts_with(names[i] + inc, (const char*)string)) { if (r > arrayMax) break; if (!is_executable(names[i])) continue; char* p = (char *)names[i] + inc; strip_elf_extension(p); array[r] = (char *)p; r++; } } // ---- Shell builtins ---- size_t builtins_count = sizeof(shell_builtins) / sizeof(const char*); for (size_t i = 0; i < builtins_count; i++) { if (montauk::starts_with(shell_builtins[i], (const char*)string)) { if (r > arrayMax) break; array[r] = (char *)shell_builtins[i]; r++; } } // ---- cwd file matching ---- if (cwd != nullptr) { char full_path[RD_MAX] = { }; build_montauk_path(full_path, RD_MAX, volume_number, cwd); const char* cwd_entries[RD_MAX] = { }; int n = montauk::readdir(full_path, cwd_entries, RD_MAX); for (int i = 0; i < n; i++) { const char* cwd_entry_path = remove_path_prefix(cwd, cwd_entries[i]); if (cwd_entry_path[0] == '/') { cwd_entry_path++; } if (montauk::starts_with(cwd_entry_path, (const char*)string)) { if (r > arrayMax) break; array[r] = (char *)cwd_entry_path; r++; } } } return r; }