diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-10 18:00:19 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-10 18:00:19 +0200 |
| commit | c3d5fe64c268d7ca85d1e303b98201391f28c9e9 (patch) | |
| tree | c621037b4f4a905ade1104c40dc0422bfee1ee64 /modules/core/terminal.js | |
| parent | 39b2df36ee5ccea52558d0537bfee87058558e94 (diff) | |
auto: 2026-06-10_18:00 changes
Diffstat (limited to 'modules/core/terminal.js')
| -rw-r--r-- | modules/core/terminal.js | 70 |
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) => { |