@electerm/electerm-react 2.3.191 → 2.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/client/common/clipboard.js +1 -1
  2. package/client/common/constants.js +2 -2
  3. package/client/common/download.jsx +3 -2
  4. package/client/common/error-handler.jsx +5 -9
  5. package/client/common/fetch-from-server.js +1 -1
  6. package/client/common/fetch.jsx +5 -5
  7. package/client/common/icon-helpers.jsx +16 -0
  8. package/client/common/parse-json-safe.js +1 -1
  9. package/client/common/pre.js +0 -7
  10. package/client/common/sftp.js +1 -1
  11. package/client/common/terminal-theme.js +1 -1
  12. package/client/common/transfer.js +2 -2
  13. package/client/common/upgrade.js +2 -2
  14. package/client/components/ai/ai-chat.jsx +10 -1
  15. package/client/components/auth/login.jsx +1 -1
  16. package/client/components/bg/css-overwrite.jsx +1 -1
  17. package/client/components/bookmark-form/common/fields.jsx +3 -0
  18. package/client/components/bookmark-form/common/ssh-agent.jsx +33 -0
  19. package/client/components/bookmark-form/config/common-fields.js +2 -4
  20. package/client/components/bookmark-form/config/serial.js +1 -1
  21. package/client/components/bookmark-form/config/ssh.js +1 -0
  22. package/client/components/bookmark-form/form-renderer.jsx +3 -2
  23. package/client/components/common/input-auto-focus.jsx +1 -1
  24. package/client/components/common/message.jsx +131 -0
  25. package/client/components/common/message.styl +58 -0
  26. package/client/components/common/modal.jsx +176 -0
  27. package/client/components/common/modal.styl +22 -0
  28. package/client/components/common/notification-with-details.jsx +1 -1
  29. package/client/components/common/notification.jsx +94 -0
  30. package/client/components/common/notification.styl +51 -0
  31. package/client/components/main/connection-hopping-warnning.jsx +1 -3
  32. package/client/components/main/error-wrapper.jsx +3 -2
  33. package/client/components/main/main.jsx +4 -11
  34. package/client/components/main/upgrade.jsx +6 -4
  35. package/client/components/profile/profile-form-elem.jsx +1 -1
  36. package/client/components/quick-commands/quick-commands-box.jsx +5 -2
  37. package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
  38. package/client/components/rdp/rdp-session.jsx +2 -2
  39. package/client/components/session/session.jsx +4 -9
  40. package/client/components/setting-panel/deep-link-control.jsx +4 -3
  41. package/client/components/setting-panel/keyword-input.jsx +60 -0
  42. package/client/components/setting-panel/keywords-form.jsx +2 -7
  43. package/client/components/setting-panel/setting-common.jsx +1 -1
  44. package/client/components/setting-panel/setting-terminal.jsx +1 -1
  45. package/client/components/setting-panel/tab-settings.jsx +1 -1
  46. package/client/components/setting-sync/setting-sync-form.jsx +53 -3
  47. package/client/components/setting-sync/setting-sync.jsx +2 -1
  48. package/client/components/sftp/owner-list.js +6 -6
  49. package/client/components/sftp/sftp-entry.jsx +6 -4
  50. package/client/components/shortcuts/shortcut-editor.jsx +2 -2
  51. package/client/components/ssh-config/ssh-config-load-notify.jsx +3 -2
  52. package/client/components/tabs/tab.jsx +1 -1
  53. package/client/components/tabs/workspace-save-modal.jsx +2 -1
  54. package/client/components/terminal/attach-addon-custom.js +142 -26
  55. package/client/components/terminal/command-tracker-addon.js +164 -53
  56. package/client/components/terminal/highlight-addon.js +84 -43
  57. package/client/components/terminal/shell.js +138 -0
  58. package/client/components/terminal/term-search.styl +1 -0
  59. package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
  60. package/client/components/terminal/terminal.jsx +166 -104
  61. package/client/components/theme/theme-form.jsx +2 -1
  62. package/client/components/tree-list/bookmark-transport.jsx +27 -5
  63. package/client/components/vnc/vnc-session.jsx +1 -1
  64. package/client/components/widgets/widget-notification-with-details.jsx +1 -1
  65. package/client/store/common.js +5 -2
  66. package/client/store/db-upgrade.js +1 -1
  67. package/client/store/init-state.js +2 -1
  68. package/client/store/load-data.js +2 -2
  69. package/client/store/mcp-handler.js +9 -56
  70. package/client/store/setting.js +1 -3
  71. package/client/store/store.js +2 -1
  72. package/client/store/sync.js +14 -8
  73. package/client/store/system-menu.js +2 -1
  74. package/client/store/tab.js +1 -1
  75. package/client/store/widgets.js +1 -3
  76. package/package.json +1 -1
  77. package/client/common/track.js +0 -7
  78. package/client/components/batch-op/batch-op-entry.jsx +0 -13
@@ -1,83 +1,194 @@
1
+ /**
2
+ * CommandTrackerAddon - Tracks the current command in the terminal
3
+ *
4
+ * This addon uses Shell Integration via OSC 633 escape sequences for reliable
5
+ * command tracking. The shell emits special sequences that tell us:
6
+ * - OSC 633 ; A - Prompt started
7
+ * - OSC 633 ; B - Command input started (ready for typing)
8
+ * - OSC 633 ; C - Command execution started (output begins)
9
+ * - OSC 633 ; D ; <exitCode> - Command finished
10
+ * - OSC 633 ; E ; <command> - The command line being executed
11
+ * - OSC 633 ; P ; Cwd=<path> - Current working directory
12
+ *
13
+ * This properly handles:
14
+ * - Command history (arrow up/down)
15
+ * - Tab completion
16
+ * - Paste operations
17
+ * - Shell-side editing (readline, vi-mode)
18
+ * - Multi-line commands
19
+ * - Any custom prompt
20
+ */
21
+
1
22
  export class CommandTrackerAddon {
2
23
  constructor () {
3
24
  this.terminal = undefined
4
- this.activeCommand = ''
5
- this.currentCommand = ''
6
- this.cursorPosition = 0
7
- this.timeout = null
8
- this.handleKey = this.debounce(this._handleKey, 200) // 10ms debounce
25
+ this._disposables = []
26
+
27
+ // Shell integration state
28
+ this.currentCommand = '' // Command being typed
29
+ this.executedCommand = '' // Last executed command
30
+ this.lastExitCode = null
31
+ this.cwd = ''
32
+ this.shellIntegrationActive = false
33
+
34
+ // Event callbacks for shell integration events
35
+ this._onCommandExecuted = null // Called when OSC 633;E is received
36
+ this._onCwdChanged = null // Called when OSC 633;P;Cwd= is received
9
37
  }
10
38
 
11
- debounce = (func, wait) => {
12
- return (...args) => {
13
- const later = () => {
14
- clearTimeout(this.timeout)
15
- func.apply(this, args)
16
- }
39
+ /**
40
+ * Register callback for when a command is executed (received via OSC 633;E)
41
+ * @param {function} callback - Called with (command: string)
42
+ */
43
+ onCommandExecuted (callback) {
44
+ this._onCommandExecuted = callback
45
+ }
17
46
 
18
- clearTimeout(this.timeout)
19
- this.timeout = setTimeout(later, wait)
20
- }
47
+ /**
48
+ * Register callback for when CWD changes (received via OSC 633;P;Cwd=)
49
+ * @param {function} callback - Called with (cwd: string)
50
+ */
51
+ onCwdChanged (callback) {
52
+ this._onCwdChanged = callback
21
53
  }
22
54
 
23
55
  activate (terminal) {
24
56
  this.terminal = terminal
57
+
58
+ // Register OSC 633 handler for shell integration
59
+ // OSC 633 is the VS Code / modern terminal shell integration protocol
60
+ if (terminal.parser && terminal.parser.registerOscHandler) {
61
+ const oscHandler = terminal.parser.registerOscHandler(633, (data) => {
62
+ return this._handleOsc633(data)
63
+ })
64
+ this._disposables.push(oscHandler)
65
+ }
25
66
  }
26
67
 
27
68
  dispose () {
28
- this.term = null
69
+ this.terminal = null
29
70
  if (this._disposables) {
30
71
  this._disposables.forEach(d => d.dispose())
31
72
  this._disposables.length = 0
32
73
  }
33
- if (this.timeout) {
34
- clearTimeout(this.timeout)
35
- this.timeout = null
36
- }
37
74
  }
38
75
 
39
- handleData = (data) => {
40
- // Handle regular input
41
- this.activeCommand = this.activeCommand.slice(0, this.cursorPosition) + data + this.activeCommand.slice(this.cursorPosition)
42
- this.cursorPosition += data.length
76
+ /**
77
+ * Handle OSC 633 shell integration sequences
78
+ * @param {string} data - The OSC data after "633;"
79
+ * @returns {boolean} Whether the sequence was handled
80
+ */
81
+ _handleOsc633 (data) {
82
+ if (!data) return false
83
+
84
+ // Parse the sequence: first char is the command type
85
+ const command = data.charAt(0)
86
+ const args = data.length > 1 ? data.substring(2) : '' // Skip "X;" part
87
+
88
+ switch (command) {
89
+ case 'A': // Prompt started
90
+ this.shellIntegrationActive = true
91
+ // Reset current command when new prompt appears
92
+ this.currentCommand = ''
93
+ return true
94
+
95
+ case 'B': // Command input started (after prompt)
96
+ return true
97
+
98
+ case 'C': // Command execution started
99
+ return true
100
+
101
+ case 'D': // Command finished
102
+ // Parse exit code if provided
103
+ if (args) {
104
+ this.lastExitCode = parseInt(args, 10)
105
+ } else {
106
+ this.lastExitCode = null
107
+ }
108
+ return true
109
+
110
+ case 'E': // Command line
111
+ // The actual command being executed
112
+ this.executedCommand = this._deserializeOscValue(args)
113
+ this.currentCommand = this.executedCommand
114
+ // Call the callback if registered
115
+ if (this._onCommandExecuted && this.executedCommand) {
116
+ this._onCommandExecuted(this.executedCommand)
117
+ }
118
+ return true
119
+
120
+ case 'P': // Property (e.g., Cwd=<path>)
121
+ this._handleProperty(args)
122
+ return true
123
+
124
+ default:
125
+ return false
126
+ }
43
127
  }
44
128
 
45
- // This is now our internal handler
46
- _handleKey = (e) => {
47
- const { key } = e
48
- if (e.ctrlKey && key.toLowerCase() === 'c') {
49
- this.clearCommand()
50
- } else if (key === 'Enter') {
51
- // Command executed, reset
52
- this.currentCommand = this.activeCommand
53
- this.activeCommand = ''
54
- this.cursorPosition = 0
55
- } else if (key === 'Backspace') {
56
- // Handle backspace
57
- if (this.cursorPosition > 0) {
58
- this.activeCommand = this.activeCommand.slice(0, this.cursorPosition - 1) + this.activeCommand.slice(this.cursorPosition)
59
- this.cursorPosition--
60
- }
61
- } else if (key === 'ArrowLeft') {
62
- // Move cursor left
63
- if (this.cursorPosition > 0) {
64
- this.cursorPosition--
65
- }
66
- } else if (key === 'ArrowRight') {
67
- // Move cursor right
68
- if (this.cursorPosition < this.activeCommand.length) {
69
- this.cursorPosition++
129
+ /**
130
+ * Handle OSC 633 ; P property sequences
131
+ * @param {string} data - Property data like "Cwd=/path/to/dir"
132
+ */
133
+ _handleProperty (data) {
134
+ const eqIndex = data.indexOf('=')
135
+ if (eqIndex === -1) return
136
+
137
+ const key = data.substring(0, eqIndex)
138
+ const value = this._deserializeOscValue(data.substring(eqIndex + 1))
139
+
140
+ switch (key) {
141
+ case 'Cwd': {
142
+ const oldCwd = this.cwd
143
+ this.cwd = value
144
+ // Call the callback if registered and CWD actually changed
145
+ if (this._onCwdChanged && oldCwd !== value) {
146
+ this._onCwdChanged(value)
147
+ }
148
+ break
70
149
  }
150
+ // Add more properties as needed
71
151
  }
72
152
  }
73
153
 
74
- clearCommand () {
75
- this.activeCommand = ''
76
- this.currentCommand = ''
77
- this.cursorPosition = 0
154
+ /**
155
+ * Deserialize OSC 633 escaped values
156
+ * Handles: \\ -> \, \x3b -> ;
157
+ * @param {string} value - Escaped value
158
+ * @returns {string} Unescaped value
159
+ */
160
+ _deserializeOscValue (value) {
161
+ if (!value) return ''
162
+ return value
163
+ .replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
164
+ .replace(/\\\\/g, '\\')
78
165
  }
79
166
 
167
+ /**
168
+ * Get the current command (from shell integration)
169
+ */
80
170
  getCurrentCommand () {
81
- return this.activeCommand || this.currentCommand
171
+ return this.executedCommand || this.currentCommand || ''
172
+ }
173
+
174
+ /**
175
+ * Get the last exit code (if available via shell integration)
176
+ */
177
+ getLastExitCode () {
178
+ return this.lastExitCode
179
+ }
180
+
181
+ /**
182
+ * Get current working directory (if available via shell integration)
183
+ */
184
+ getCwd () {
185
+ return this.cwd
186
+ }
187
+
188
+ /**
189
+ * Check if shell integration is active
190
+ */
191
+ hasShellIntegration () {
192
+ return this.shellIntegrationActive
82
193
  }
83
194
  }
@@ -1,6 +1,39 @@
1
1
  export class KeywordHighlighterAddon {
2
2
  constructor (keywords) {
3
- this.keywords = keywords
3
+ this.keywords = keywords || []
4
+ this.compiledPatterns = this.compilePatterns()
5
+ }
6
+
7
+ // Pre-compile all regex patterns once for better performance
8
+ compilePatterns = () => {
9
+ const patterns = []
10
+ for (const obj of this.keywords) {
11
+ const { keyword, color = 'red' } = obj || {}
12
+ if (keyword) {
13
+ try {
14
+ patterns.push({
15
+ regex: new RegExp(keyword, 'gi'),
16
+ colorCode: this.getColorCode(color)
17
+ })
18
+ } catch (e) {
19
+ console.error('Invalid keyword regex:', keyword, e)
20
+ }
21
+ }
22
+ }
23
+ return patterns
24
+ }
25
+
26
+ getColorCode = (color) => {
27
+ const colorMap = {
28
+ green: '\u001b[32m',
29
+ yellow: '\u001b[33m',
30
+ blue: '\u001b[34m',
31
+ magenta: '\u001b[35m',
32
+ cyan: '\u001b[36m',
33
+ white: '\u001b[37m',
34
+ red: '\u001b[31m'
35
+ }
36
+ return colorMap[color] || colorMap.red
4
37
  }
5
38
 
6
39
  escape = str => {
@@ -8,63 +41,71 @@ export class KeywordHighlighterAddon {
8
41
  .replace(/\033/g, '\\033')
9
42
  }
10
43
 
11
- colorize = (color) => {
12
- // Use a switch statement to map color names to ANSI codes
13
- switch (color) {
14
- case 'green':
15
- return '\u001b[32m$&\u001b[0m'
16
- case 'yellow':
17
- return '\u001b[33m$&\u001b[0m'
18
- case 'blue':
19
- return '\u001b[34m$&\u001b[0m'
20
- case 'magenta':
21
- return '\u001b[35m$&\u001b[0m'
22
- case 'cyan':
23
- return '\u001b[36m$&\u001b[0m'
24
- case 'white':
25
- return '\u001b[37m$&\u001b[0m'
26
- default:
27
- return '\u001b[31m$&\u001b[0m'
44
+ highlightKeywords = (text) => {
45
+ // Early exit if no patterns
46
+ if (this.compiledPatterns.length === 0) {
47
+ return text
28
48
  }
29
- }
30
49
 
31
- highlightKeywords = (text) => {
32
- for (const obj of this.keywords) {
33
- const {
34
- keyword,
35
- color = 'red'
36
- } = obj || {}
37
- if (keyword) {
38
- try {
39
- const regex = new RegExp(`(${keyword})`, 'gi')
40
- if (regex.test(text)) {
41
- return text.replace(regex, this.colorize(color))
42
- }
43
- } catch (e) {
44
- window.store.onError(e)
45
- }
50
+ // Split text into segments: ANSI/OSC sequences vs plain text
51
+ // Match OSC sequences (ESC ] ... BEL or ESC ] ... ESC \) and CSI sequences (ESC [ ... letter)
52
+ // Use String.fromCharCode to avoid lint warnings about control characters
53
+ const ESC = String.fromCharCode(27) // \x1b
54
+ const BEL = String.fromCharCode(7) // \x07
55
+ // eslint-disable-next-line no-control-regex
56
+ const ansiPattern = new RegExp('(' + ESC + '\\][^' + BEL + ESC + ']*(?:' + BEL + '|' + ESC + '\\\\)|' + ESC + '\\[[0-9;]*[A-Za-z])', 'g')
57
+
58
+ const segments = []
59
+ let lastIndex = 0
60
+ let match
61
+
62
+ while ((match = ansiPattern.exec(text)) !== null) {
63
+ // Add plain text before this sequence
64
+ if (match.index > lastIndex) {
65
+ segments.push({ type: 'text', content: text.slice(lastIndex, match.index) })
46
66
  }
67
+ // Add the ANSI sequence (don't highlight)
68
+ segments.push({ type: 'ansi', content: match[0] })
69
+ lastIndex = ansiPattern.lastIndex
70
+ }
71
+
72
+ // Add remaining plain text
73
+ if (lastIndex < text.length) {
74
+ segments.push({ type: 'text', content: text.slice(lastIndex) })
47
75
  }
48
- return text
76
+
77
+ // Highlight only plain text segments
78
+ const result = segments.map(seg => {
79
+ if (seg.type === 'ansi') {
80
+ return seg.content
81
+ }
82
+ let content = seg.content
83
+ for (const { regex, colorCode } of this.compiledPatterns) {
84
+ regex.lastIndex = 0
85
+ content = content.replace(regex, (m) => `${colorCode}${m}\u001b[0m`)
86
+ }
87
+ return content
88
+ }).join('')
89
+
90
+ return result
49
91
  }
50
92
 
51
93
  activate (terminal) {
52
94
  this.terminal = terminal
53
- // Override the write method to automatically highlight keywords
54
- const originalWrite = terminal.write
55
- terminal.write = (data) => {
56
- originalWrite.call(
57
- terminal,
58
- terminal.displayRaw ? this.escape(data) : this.highlightKeywords(data)
95
+ // Store the original write method properly bound to terminal
96
+ this.originalWrite = terminal.write.bind(terminal)
97
+ const self = this
98
+ terminal.write = function (data) {
99
+ self.originalWrite(
100
+ terminal.displayRaw ? self.escape(data) : self.highlightKeywords(data)
59
101
  )
60
102
  }
61
- this.originalWrite = originalWrite
62
103
  }
63
104
 
64
105
  dispose () {
65
106
  // Restore the original write method when disposing the addon
66
107
  this.terminal.write = this.originalWrite
67
108
  this.originalWrite = null
68
- this.term = null
109
+ this.terminal = null
69
110
  }
70
111
  }
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Client-side Shell Integration Commands
3
+ *
4
+ * These are minimal shell integration commands that can be sent directly
5
+ * to a local or remote shell from the frontend after connection.
6
+ * They enable OSC 633 command tracking without needing server-side file sourcing.
7
+ *
8
+ * OSC 633 Protocol:
9
+ * - OSC 633 ; A - Prompt started
10
+ * - OSC 633 ; B - Command input started (ready for typing)
11
+ * - OSC 633 ; C - Command execution started
12
+ * - OSC 633 ; D ; <exitCode> - Command finished
13
+ * - OSC 633 ; E ; <command> - Command line being executed
14
+ * - OSC 633 ; P ; Cwd=<path> - Current working directory
15
+ */
16
+
17
+ /* eslint-disable no-template-curly-in-string, no-useless-escape */
18
+
19
+ /**
20
+ * Get inline shell integration command for bash (one-liner format)
21
+ * Properly formatted for semicolon joining
22
+ */
23
+ function getBashInlineIntegration () {
24
+ // Each statement is complete and can be joined with semicolons
25
+ return [
26
+ 'if [[ $- == *i* ]] && [[ -z "${ELECTERM_SHELL_INTEGRATION:-}" ]]',
27
+ 'then export ELECTERM_SHELL_INTEGRATION=1',
28
+ '__e_esc() { local v="$1"; v="${v//\\\\/\\\\\\\\}"; v="${v//;/\\\\x3b}"; printf \'%s\' "$v"; }',
29
+ '__e_pre() { [[ "$BASH_COMMAND" == "$PROMPT_COMMAND" ]] && return; [[ "$BASH_COMMAND" == "__e_"* ]] && return; [[ "${__e_in:-0}" == "0" ]] && { __e_in=1; printf \'\\e]633;E;%s\\a\\e]633;C\\a\' "$(__e_esc "$BASH_COMMAND")"; }; }',
30
+ '__e_cmd() { local c="$?"; [[ "${__e_in:-0}" == "1" ]] && { printf \'\\e]633;D;%s\\a\' "$c"; __e_in=0; }; printf \'\\e]633;P;Cwd=%s\\a\\e]633;A\\a\' "$(__e_esc "$PWD")"; return "$c"; }',
31
+ 'trap \'__e_pre\' DEBUG',
32
+ 'PROMPT_COMMAND="__e_cmd${PROMPT_COMMAND:+; $PROMPT_COMMAND}"',
33
+ 'fi'
34
+ ].join('; ')
35
+ }
36
+
37
+ /**
38
+ * Get inline shell integration command for zsh (one-liner format)
39
+ * Properly formatted for semicolon joining
40
+ */
41
+ function getZshInlineIntegration () {
42
+ // Each statement is complete and can be joined with semicolons
43
+ // Note: 'then' must have a space/newline before the next command, not semicolon
44
+ return [
45
+ 'if [[ -o interactive ]] && [[ -z "${ELECTERM_SHELL_INTEGRATION:-}" ]]',
46
+ 'then export ELECTERM_SHELL_INTEGRATION=1',
47
+ '__e_esc() { local v="$1"; v="${v//\\\\/\\\\\\\\}"; v="${v//;/\\\\x3b}"; builtin printf \'%s\' "$v"; }',
48
+ '__e_preexec() { __e_cmd="$1"; builtin printf \'\\e]633;E;%s\\a\\e]633;C\\a\' "$(__e_esc "$1")"; }',
49
+ '__e_precmd() { local c="$?"; [[ -n "$__e_cmd" ]] && builtin printf \'\\e]633;D;%s\\a\' "$c"; __e_cmd=""; builtin printf \'\\e]633;P;Cwd=%s\\a\\e]633;A\\a\' "$(__e_esc "$PWD")"; }',
50
+ 'autoload -Uz add-zsh-hook',
51
+ 'add-zsh-hook precmd __e_precmd',
52
+ 'add-zsh-hook preexec __e_preexec',
53
+ 'fi'
54
+ ].join('; ')
55
+ }
56
+
57
+ /**
58
+ * Get inline shell integration command for fish (one-liner format)
59
+ */
60
+ function getFishInlineIntegration () {
61
+ return [
62
+ 'if status is-interactive; and not set -q ELECTERM_SHELL_INTEGRATION',
63
+ 'set -g ELECTERM_SHELL_INTEGRATION 1',
64
+ 'function __e_esc; echo $argv | string replace -a \'\\\\\' \'\\\\\\\\\' | string replace -a \';\' \'\\\\x3b\'; end',
65
+ 'function __e_prompt --on-event fish_prompt; printf \'\\e]633;A\\a\\e]633;P;Cwd=%s\\a\' (__e_esc "$PWD"); end',
66
+ 'function __e_preexec --on-event fish_preexec; printf \'\\e]633;E;%s\\a\\e]633;C\\a\' (__e_esc "$argv"); end',
67
+ 'function __e_postexec --on-event fish_postexec; printf \'\\e]633;D;%s\\a\' $status; end',
68
+ 'end'
69
+ ].join('; ')
70
+ }
71
+
72
+ /**
73
+ * Get shell integration command based on detected shell type
74
+ * @param {string} shellType - 'bash', 'zsh', or 'fish'
75
+ * @returns {string} Shell integration command to send
76
+ */
77
+ export function getInlineShellIntegration (shellType) {
78
+ switch (shellType) {
79
+ case 'bash':
80
+ return getBashInlineIntegration()
81
+ case 'zsh':
82
+ return getZshInlineIntegration()
83
+ case 'fish':
84
+ return getFishInlineIntegration()
85
+ default:
86
+ // Try bash as default for sh-compatible shells
87
+ return getBashInlineIntegration()
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Wrap shell integration command for execution
93
+ * Now simplified since output suppression is handled at the attach addon level
94
+ * @param {string} cmd - Shell integration command
95
+ * @param {string} shellType - Shell type (unused, kept for API compatibility)
96
+ * @returns {string} Command ready to send to terminal
97
+ */
98
+ export function wrapSilent (cmd, shellType) {
99
+ // Escape single quotes for embedding in single-quoted string
100
+ const escaped = cmd.replace(/'/g, "'\\''")
101
+ // The leading space prevents the command from being saved to history
102
+ // The eval wrapper ensures proper execution
103
+ return ` eval '${escaped}' 2>/dev/null\r`
104
+ }
105
+
106
+ /**
107
+ * Get complete shell integration command ready to send
108
+ * @param {string} shellType - 'bash', 'zsh', or 'fish'
109
+ * @returns {string} Complete command to send to terminal
110
+ */
111
+ export function getShellIntegrationCommand (shellType = 'bash') {
112
+ const cmd = getInlineShellIntegration(shellType)
113
+ return wrapSilent(cmd, shellType)
114
+ }
115
+
116
+ /**
117
+ * Detect shell type from shell path or login script
118
+ * @param {string} shellPath - Path to shell executable or login script
119
+ * @returns {string} Shell type: 'bash', 'zsh', 'fish', or 'bash' (default)
120
+ */
121
+ export function detectShellType (shellPath = '') {
122
+ if (!shellPath) return 'bash'
123
+
124
+ const normalizedPath = shellPath.toLowerCase()
125
+
126
+ if (normalizedPath.includes('zsh')) {
127
+ return 'zsh'
128
+ } else if (normalizedPath.includes('fish')) {
129
+ return 'fish'
130
+ } else if (normalizedPath.includes('bash')) {
131
+ return 'bash'
132
+ } else if (normalizedPath.includes('sh')) {
133
+ // Generic sh, try bash compatibility
134
+ return 'bash'
135
+ }
136
+
137
+ return 'bash'
138
+ }
@@ -13,3 +13,4 @@
13
13
  top 100px
14
14
  right 5px
15
15
  z-index 200
16
+ background var(--main)
@@ -171,6 +171,7 @@ export default class TerminalCmdSuggestions extends Component {
171
171
  const { activeTabId } = window.store
172
172
  const terminal = refs.get('term-' + activeTabId)
173
173
  if (!terminal) {
174
+ console.log('No active terminal found')
174
175
  return
175
176
  }
176
177
 
@@ -186,6 +187,8 @@ export default class TerminalCmdSuggestions extends Component {
186
187
  txt = pre + command
187
188
  }
188
189
  terminal.attachAddon._sendData(txt)
190
+ // Update the terminal's currentInput to reflect the full command
191
+ terminal.setCurrentInput(command)
189
192
  terminal.term.focus()
190
193
  this.closeSuggestions()
191
194
  }