Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions js/terminal-ext.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ extend = (term) => {
term._promptRawText = () =>
`${term.user}${term.sep}${term.host} ${term.cwd} $`;
term.deepLink = window.location.hash.replace("#", "").split("-").join(" ");

// Simple tab completion state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

term.promptText = () => {
var text = term
Expand Down
125 changes: 93 additions & 32 deletions js/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ function runRootTerminal(term) {
if (term._initialized && !term.locked) {
switch (e) {
case '\r': // Enter
// Reset tab state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

var exitStatus;
term.currentLine = term.currentLine.trim();
const tokens = term.currentLine.split(" ");
Expand Down Expand Up @@ -51,25 +56,45 @@ function runRootTerminal(term) {
}
break;
case '\u0003': // Ctrl+C
// Reset tab state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

term.prompt();
term.clearCurrentLine(true);
break;
case '\u0008': // Ctrl+H
case '\u007F': // Backspace (DEL)
// Reset tab state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

// Do not delete the prompt
if (term.pos() > 0) {
const newLine = term.currentLine.slice(0, term.pos() - 1) + term.currentLine.slice(term.pos());
term.setCurrentLine(newLine, true)
}
break;
case '\033[A': // up
// Reset tab state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

var h = [...term.history].reverse();
if (term.historyCursor < h.length - 1) {
term.historyCursor += 1;
term.setCurrentLine(h[term.historyCursor], false);
}
break;
case '\033[B': // down
// Reset tab state
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

var h = [...term.history].reverse();
if (term.historyCursor > 0) {
term.historyCursor -= 1;
Expand All @@ -89,42 +114,78 @@ function runRootTerminal(term) {
}
break;
case '\t': // tab
cmd = term.currentLine.split(" ")[0];
const rest = term.currentLine.slice(cmd.length).trim();
const autocompleteCmds = Object.keys(commands).filter((c) => c.startsWith(cmd));
var autocompleteArgs;

// detect what to autocomplete
if (autocompleteCmds && autocompleteCmds.length > 1) {
const oldLine = term.currentLine;
term.stylePrint(`\r\n${autocompleteCmds.sort().join(" ")}`);
term.prompt();
term.setCurrentLine(oldLine);
} else if (["cat", "tail", "less", "head", "open", "mv", "cp", "chown", "chmod"].includes(cmd)) {
autocompleteArgs = _filesHere().filter((f) => f.startsWith(rest));
} else if (["whois", "finger", "groups"].includes(cmd)) {
autocompleteArgs = Object.keys(team).filter((f) => f.startsWith(rest));
} else if (["man", "woman", "tldr"].includes(cmd)) {
autocompleteArgs = Object.keys(portfolio).filter((f) => f.startsWith(rest));
} else if (["cd"].includes(cmd)) {
autocompleteArgs = _filesHere().filter((dir) => dir.startsWith(rest) && !_DIRS[term.cwd].includes(dir));
const tabParts = term.currentLine.split(" ");
const tabCmd = tabParts[0];
const tabRest = tabParts.slice(1).join(" ");

// Check if we need to reset tab state (input changed)
if (term.tabBase !== term.currentLine) {
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = term.currentLine;

// Get completions based on context
if (tabParts.length === 1) {
// Completing command
term.tabOptions = Object.keys(commands).filter(c => c.startsWith(tabCmd)).sort();
} else if (["cat", "tail", "less", "head", "open", "mv", "cp", "chown", "chmod", "ls"].includes(tabCmd)) {
term.tabOptions = _filesHere().filter(f => f.startsWith(tabRest)).sort();
} else if (["whois", "finger", "groups"].includes(tabCmd)) {
term.tabOptions = Object.keys(team).filter(f => f.startsWith(tabRest)).sort();
} else if (["man", "woman", "tldr"].includes(tabCmd)) {
term.tabOptions = Object.keys(portfolio).filter(f => f.startsWith(tabRest)).sort();
} else if (["cd"].includes(tabCmd)) {
term.tabOptions = _filesHere().filter(dir => dir.startsWith(tabRest) && !_DIRS[term.cwd].includes(dir)).sort();
}
}

// do the autocompleting
if (autocompleteArgs && autocompleteArgs.length > 1) {
const oldLine = term.currentLine;
term.writeln(`\r\n${autocompleteArgs.join(" ")}`);
term.prompt();
term.setCurrentLine(oldLine);
} else if (commands[cmd] && autocompleteArgs && autocompleteArgs.length > 0) {
term.setCurrentLine(`${cmd} ${autocompleteArgs[0]}`);
} else if (commands[cmd] && autocompleteArgs && autocompleteArgs.length == 0) {
term.setCurrentLine(`${cmd} ${rest}`);
} else if (autocompleteCmds && autocompleteCmds.length == 1) {
term.setCurrentLine(`${autocompleteCmds[0]} `);

// Handle tab completion
if (term.tabOptions.length === 0) {
// No completions
} else if (term.tabOptions.length === 1) {
// Single match - complete it
if (tabParts.length === 1) {
// Check if it's already an exact match (like typing "ls" completely)
if (tabCmd === term.tabOptions[0]) {
// Exact match - just add a space
term.setCurrentLine(`${term.tabOptions[0]} `);
} else {
// Partial match - complete it
term.setCurrentLine(`${term.tabOptions[0]} `);
}
} else {
term.setCurrentLine(`${tabCmd} ${term.tabOptions[0]}`);
}
term.tabBase = "";
term.tabIndex = 0;
term.tabOptions = [];
} else {
// Multiple matches
if (term.tabIndex === 0) {
// First tab - show options
term.writeln(`\r\n${term.tabOptions.join(" ")}`);
term.prompt();
term.setCurrentLine(term.currentLine);
term.tabIndex = 1;
} else {
// Cycling through options
const option = term.tabOptions[(term.tabIndex - 1) % term.tabOptions.length];
if (tabParts.length === 1) {
term.setCurrentLine(option);
} else {
term.setCurrentLine(`${tabCmd} ${option}`);
}
term.tabIndex++;
term.tabBase = term.currentLine; // Update base to current selection
}
}
break;
default: // Print all other characters
// Reset tab state on any other key
term.tabIndex = 0;
term.tabOptions = [];
term.tabBase = "";

const newLine = `${term.currentLine.slice(0, term.pos())}${e}${term.currentLine.slice(term.pos())}`;
term.setCurrentLine(newLine, true);
break;
Expand Down