feat: Add tab completion and some long format ls
All checks were successful
Build Docker / BuildImage (push) Successful in 58s

This commit is contained in:
2025-10-27 17:07:24 +11:00
parent 687e7af91b
commit d46f2ed40d
2 changed files with 144 additions and 7 deletions

View File

@@ -78,7 +78,7 @@ def setup_path_session():
binaries.append({"name": cmd.split()[0], "type": 2, "content": "", "permissions": 1}) binaries.append({"name": cmd.split()[0], "type": 2, "content": "", "permissions": 1})
boot_files = [] boot_files = []
for bin_file in BOOT_FILES: for bin_file in BOOT_FILES:
boot_files.append({"name": bin_file, "type": 2, "content": "", "permissions": 1}) boot_files.append({"name": bin_file, "type": 1, "content": "", "permissions": 1})
session["paths"] = [ session["paths"] = [
{"name": "home", "type": 0, "children": [ {"name": "home", "type": 0, "children": [
@@ -239,22 +239,68 @@ def ls():
all_files = True all_files = True
args.remove("--all") args.remove("--all")
path = session.get("pwd", f"/home/{getClientIP(request)}") long_format = False
if "-l" in args:
long_format = True
args.remove("-l")
elif "--long" in args:
long_format = True
args.remove("--long")
for arg in args:
if arg.startswith("-") and not arg.startswith("--"):
if "l" in arg:
long_format = True
if "a" in arg:
all_files = True
args.remove(arg)
ip = getClientIP(request)
path = session.get("pwd", f"/home/{ip}")
if args: if args:
path = args[0] path = args[0]
if not path.startswith("/"): if not path.startswith("/"):
# Relative path # Relative path
path = sanitize_path(session.get("pwd", f"/home/{getClientIP(request)}") + "/" + path) path = sanitize_path(session.get("pwd", f"/home/{ip}") + "/" + path)
if not is_valid_path(path): if not is_valid_path(path):
# If it is a file or binary, return it
if is_valid_file(path) or is_valid_binary(path):
if long_format:
node = get_node(path)
permissions = node.get("permissions", 0)
perm_str = ""
perm_str += "r" if permissions > 0 else "-"
perm_str += "w" if permissions > 1 else "-"
perm_str += "x" if (node.get("type", 0) == 2 and permissions > 1) else "-"
user = f"{ip}" if path.startswith(f"/home/{ip}") else "root"
output = f"{perm_str} {user} {user} {path.split('/')[-1]}"
return json_response(request, {"output": output}, 200)
return json_response(request, {"output": path.split("/")[-1]}, 200)
return json_response(request, {"output": f"ls: cannot access '{path}': No such file or directory"}, 200) return json_response(request, {"output": f"ls: cannot access '{path}': No such file or directory"}, 200)
files = get_nodes_in_directory(path) files = get_nodes_in_directory(path)
output = [file["name"] for file in files] output = []
if long_format:
for f in files:
permissions = f.get("permissions", 0)
perm_str = ""
perm_str += "r" if permissions > 0 else "-"
perm_str += "w" if permissions > 1 else "-"
perm_str += "x" if (f.get("type", 0) == 2 and permissions >= 1) else "-"
user = f"{ip}" if path.startswith(f"/home/{ip}") else "root"
output.append(f"{perm_str} {user} {user} {f['name']}")
else:
output = [f["name"] for f in files]
if all_files: if all_files:
output.insert(0, ".") output.insert(0, ".")
output.insert(1, "..") output.insert(1, "..")
else: else:
output = [file for file in output if not file.startswith(".")] output = [file for file in output if not file.startswith(".")]
if long_format:
output = "\n".join(output)
else:
output = " ".join(output) output = " ".join(output)
return json_response(request, {"output": output}, 200) return json_response(request, {"output": output}, 200)

View File

@@ -123,6 +123,9 @@
let nanoCurrentFile = ''; let nanoCurrentFile = '';
let nanoOriginalContent = ''; let nanoOriginalContent = '';
let nanoSavePromptActive = false; let nanoSavePromptActive = false;
let tabCompletionIndex = 0;
let tabCompletionMatches = [];
let lastTabInput = '';
// Function to update the prompt with current pwd // Function to update the prompt with current pwd
async function updatePrompt() { async function updatePrompt() {
@@ -256,10 +259,98 @@
} }
}); });
// Function to get available commands
function getAvailableCommands() {
return ['help', 'about', 'clear', 'echo', 'whoami', 'ls', 'pwd', 'date', 'cd', 'cat', 'rm', 'tree', 'touch', 'nano', 'reset', 'exit'];
}
// Function to get files/directories for tab completion
async function getFilesForCompletion(path = '') {
try {
const response = await fetch('/terminal/execute/ls', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ args: path ? path : '' })
});
const data = await response.json();
if (data.output && !data.output.startsWith('ls:')) {
return data.output.split(' ').filter(f => f.length > 0);
}
} catch (err) {
console.error('Failed to get files:', err);
}
return [];
}
// Function to handle tab completion
async function handleTabCompletion(currentInput) {
const parts = currentInput.split(' ');
const isFirstWord = parts.length === 1;
if (isFirstWord) {
// Complete command names
const commands = getAvailableCommands();
const matches = commands.filter(cmd => cmd.startsWith(parts[0]));
if (matches.length === 1) {
return matches[0] + ' ';
} else if (matches.length > 1) {
// Cycle through matches
if (lastTabInput === currentInput) {
tabCompletionIndex = (tabCompletionIndex + 1) % matches.length;
} else {
tabCompletionIndex = 0;
tabCompletionMatches = matches;
}
lastTabInput = currentInput;
return matches[tabCompletionIndex] + ' ';
}
} else {
// Complete file/directory names
const lastPart = parts[parts.length - 1];
const files = await getFilesForCompletion();
const matches = files.filter(f => f.startsWith(lastPart));
if (matches.length === 1) {
parts[parts.length - 1] = matches[0];
return parts.join(' ') + ' ';
} else if (matches.length > 1) {
// Cycle through matches
if (lastTabInput === currentInput) {
tabCompletionIndex = (tabCompletionIndex + 1) % matches.length;
} else {
tabCompletionIndex = 0;
tabCompletionMatches = matches;
}
lastTabInput = currentInput;
parts[parts.length - 1] = matches[tabCompletionIndex];
return parts.join(' ');
}
}
return currentInput;
}
// Update prompt on load // Update prompt on load
updatePrompt(); updatePrompt();
input.addEventListener('keydown', async (e) => { input.addEventListener('keydown', async (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const currentInput = input.value;
const completed = await handleTabCompletion(currentInput);
input.value = completed;
return;
}
// Reset tab completion on any other key
if (e.key !== 'Tab') {
tabCompletionIndex = 0;
tabCompletionMatches = [];
lastTabInput = '';
}
if (e.key === 'Enter') { if (e.key === 'Enter') {
const command = input.value.trim(); const command = input.value.trim();