Files
MontaukOS/programs/src/shell/tabcompletion.cpp
T

191 lines
3.5 KiB
C++

/*
* tabcompletion.cpp
* Tab completion module
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/string.h>
#include <montauk/syscall.h>
#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;
}