aboutsummaryrefslogtreecommitdiff
path: root/modules/core/terminal.js
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-06-10 18:00:19 +0200
committerAgando <roduccomm@gmail.com>2026-06-10 18:00:19 +0200
commitc3d5fe64c268d7ca85d1e303b98201391f28c9e9 (patch)
treec621037b4f4a905ade1104c40dc0422bfee1ee64 /modules/core/terminal.js
parent39b2df36ee5ccea52558d0537bfee87058558e94 (diff)
auto: 2026-06-10_18:00 changes
Diffstat (limited to 'modules/core/terminal.js')
-rw-r--r--modules/core/terminal.js70
1 files changed, 54 insertions, 16 deletions
diff --git a/modules/core/terminal.js b/modules/core/terminal.js
index fb9f332..647060a 100644
--- a/modules/core/terminal.js
+++ b/modules/core/terminal.js
@@ -238,6 +238,11 @@
this._historySavePending = false;
this.MAX_OUTPUT_LINES = 200;
this._historyCap = 200;
+ // Apply history cap from localStorage on startup (was applied "on next save" before — broken)
+ try {
+ const cfgCap = parseInt(localStorage.getItem('webx11_terminal_cmd_history'));
+ if (cfgCap && cfgCap > 0) this._historyCap = cfgCap;
+ } catch (e) {}
this._aiBusy = false;
this._dirty = false;
this.history = JSON.parse(localStorage.getItem('webx11_command_history') || '[]');
@@ -317,12 +322,42 @@
} else if (e.key === 'Tab') {
e.preventDefault();
if (this.suggestions.length > 0) {
- this.selectedSuggestion = (this.selectedSuggestion + 1) % this.suggestions.length;
+ if (e.shiftKey) {
+ this.selectedSuggestion = this.selectedSuggestion <= 0
+ ? this.suggestions.length - 1
+ : this.selectedSuggestion - 1;
+ } else {
+ this.selectedSuggestion = (this.selectedSuggestion + 1) % this.suggestions.length;
+ }
this.updateAutocompleteDisplay();
}
} else if (e.ctrlKey && (e.key === 'k' || e.key === 'K')) {
e.preventDefault();
this.toggleCmdCards();
+ } else if (e.ctrlKey && (e.key === 'l' || e.key === 'L')) {
+ // Ctrl+L — clear output (standard terminal)
+ e.preventDefault();
+ // Preserve welcome/loading indicators — only clear actual output lines
+ if (this.outputContainer) {
+ for (const child of [...this.outputContainer.children]) {
+ if (!child.id || (!child.id.includes('welcome') && !child.id.includes('loading'))) {
+ child.remove();
+ }
+ }
+ }
+ } else if (e.ctrlKey && (e.key === 'c' || e.key === 'C') && this.inputField.value) {
+ // Ctrl+C — cancel current input line (standard terminal)
+ e.preventDefault();
+ this.print('^C', 'warning');
+ this.inputField.value = '';
+ this.inputField.style.height = 'auto';
+ this.hideAutocomplete();
+ return;
+ } else if (e.ctrlKey && (e.key === 'u' || e.key === 'U')) {
+ // Ctrl+U — clear line from cursor to start (standard terminal)
+ e.preventDefault();
+ this.inputField.value = '';
+ this.inputField.style.height = 'auto';
} else if (e.key === 'Escape') {
if (this._aiBusy) {
this.executeCommand('ai-stop');
@@ -918,17 +953,18 @@
showAutocomplete() {
if (!this.moduleManager || !this.security) return;
const input = this.inputField.value;
+ // Always clear dropdown innerHTML at the start — prevents stale state from prior input
+ this.dropdown.innerHTML = '';
+ this.suggestions = [];
+ this.selectedSuggestion = -1;
+ this._isCdAutocomplete = false;
const trimmed = input.trim();
const commands = this.moduleManager.getAllCommands() || {};
// Merge built-in internals into autocomplete (they're handled via if-chain, not module commands)
for (const c of this.security.commandMeta.keys()) {
if (!(c in commands)) commands[c] = 'builtin';
}
- this.suggestions = [];
- this.selectedSuggestion = -1;
- this._isCdAutocomplete = false;
-
- if (trimmed.length === 0) {
+ if (input.trim().length === 0) {
this.hideAutocomplete();
return;
}
@@ -937,9 +973,11 @@
const cmdPart = parts[0].toLowerCase();
const lowerInput = input.toLowerCase();
- // Expanded command lists for autocomplete
- const fileArgCommands = ['cat', 'preview', 'rm', 'rmdir', 'edit', 'view', 'cp', 'mv', 'touch', 'put', 'run', 'chmod', 'gzip', 'gunzip', 'encrypt', 'decrypt', 'download', 'webdav-backup', 'webdav-restore', 'webdav-upload', 'webdav-download', 'webdav-list', 'trash', 'ls-trash', 'upload', 'vfs-export', 'vfs-import', 'files', 'preview', 'skill-list', 'skill-view', 'skill-create', 'skill-patch', 'skill-delete', 'skill-sync', 'webdav-ls', 'webdav-push', 'webdav-pull', 'webdav-rm', 'webdav-mkdir', 'webdav-rename', 'webdav-move', 'webdav-config', 'nvfs-index', 'nvfs-resolve', 'nvfs-tree', 'nvfs-alias', 'nvfs-sync'];
- const dirArgCommands = ['ls', 'cd', 'mkdir', 'rmdir', 'upload', 'webdav-ls', 'webdav-mkdir', 'vfs-export', 'vfs-import', 'skill-list', 'skill-create'];
+ // Expanded command lists for autocomplete (fallback when commandsMeta lacks argType)
+ // NOTE: Commands with argType:'enum' in their commandsMeta are handled by _tryEnumAutocomplete
+ // and should NOT be in these hardcoded lists (the enum check is called BEFORE these).
+ const fileArgCommands = ['cat', 'preview', 'rm', 'rmdir', 'edit', 'view', 'cp', 'mv', 'touch', 'put', 'run', 'chmod', 'gzip', 'gunzip', 'encrypt', 'decrypt', 'download', 'webdav-backup', 'webdav-restore', 'webdav-upload', 'webdav-download', 'webdav-list', 'trash', 'ls-trash', 'upload', 'vfs-export', 'vfs-import', 'files', 'preview', 'skill-view', 'skill-create', 'skill-patch', 'skill-delete', 'skill-sync', 'webdav-ls', 'webdav-push', 'webdav-pull', 'webdav-rm', 'webdav-mkdir', 'webdav-rename', 'webdav-move', 'webdav-config', 'nvfs-index', 'nvfs-resolve', 'nvfs-tree'];
+ const dirArgCommands = ['ls', 'cd', 'mkdir', 'rmdir', 'upload', 'webdav-ls', 'webdav-mkdir', 'vfs-export', 'vfs-import', 'skill-create'];
const sessionArgCommands = ['session-load', 'session-delete', 'session-export', 'session-save', 'session-list', 'session-import'];
const pathArgCommands = ['cat', 'preview', 'rm', 'edit', 'view', 'cp', 'mv', 'touch', 'put', 'run', 'chmod', 'gzip', 'gunzip', 'encrypt', 'decrypt', 'download', 'webdav-backup', 'webdav-restore', 'webdav-upload', 'webdav-download', 'webdav-list', 'ls', 'cd', 'mkdir', 'rmdir', 'cat', 'preview'];
@@ -966,15 +1004,15 @@
return;
}
- if (sessionArgCommands.includes(cmdPart)) {
+ if (this._tryEnumAutocomplete(cmdPart)) {
+ this.suggestions = this.suggestions.filter(s => s.startsWith(partialTrimmed));
+ } else if (sessionArgCommands.includes(cmdPart)) {
this.suggestions = this._getPathSuggestions(partialTrimmed, 'session');
} else if (dirArgCommands.includes(cmdPart) || extDirCmds.has(cmdPart)) {
this.suggestions = this._getPathSuggestions(partialTrimmed, 'dir');
this._isCdAutocomplete = true;
} else if (fileArgCommands.includes(cmdPart) || extFileCmds.has(cmdPart)) {
this.suggestions = this._getPathSuggestions(partialTrimmed, 'file');
- } else if (this._tryEnumAutocomplete(cmdPart)) {
- this.suggestions = this.suggestions.filter(s => s.startsWith(partialTrimmed));
} else {
// Fallback: show files anyway
this.suggestions = this._getPathSuggestions(partialTrimmed, 'file');
@@ -991,15 +1029,15 @@
// Just typed a space after command — show all matching entries
if (parts.length > 1) {
- if (sessionArgCommands.includes(cmdPart)) {
+ if (this._tryEnumAutocomplete(cmdPart)) {
+ // suggestions already populated
+ } else if (sessionArgCommands.includes(cmdPart)) {
this.suggestions = this._getPathSuggestions('', 'session');
} else if (dirArgCommands.includes(cmdPart) || extDirCmds.has(cmdPart)) {
this.suggestions = this._getPathSuggestions('', 'dir');
this._isCdAutocomplete = true;
} else if (fileArgCommands.includes(cmdPart) || extFileCmds.has(cmdPart)) {
this.suggestions = this._getPathSuggestions('', 'file');
- } else if (this._tryEnumAutocomplete(cmdPart)) {
- // suggestions already populated
} else {
// Fallback: show files
this.suggestions = this._getPathSuggestions('', 'file');
@@ -2078,7 +2116,7 @@
'module-fetcher.js','module-skills.js','module-chatstudio.js'
];
const extFiles = [
- 'calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js','floating.js'
+ 'calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js','floating.js','console.js'
];
const addSection = (title) => {