@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.
- package/client/common/clipboard.js +1 -1
- package/client/common/constants.js +2 -2
- package/client/common/download.jsx +3 -2
- package/client/common/error-handler.jsx +5 -9
- package/client/common/fetch-from-server.js +1 -1
- package/client/common/fetch.jsx +5 -5
- package/client/common/icon-helpers.jsx +16 -0
- package/client/common/parse-json-safe.js +1 -1
- package/client/common/pre.js +0 -7
- package/client/common/sftp.js +1 -1
- package/client/common/terminal-theme.js +1 -1
- package/client/common/transfer.js +2 -2
- package/client/common/upgrade.js +2 -2
- package/client/components/ai/ai-chat.jsx +10 -1
- package/client/components/auth/login.jsx +1 -1
- package/client/components/bg/css-overwrite.jsx +1 -1
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/common/ssh-agent.jsx +33 -0
- package/client/components/bookmark-form/config/common-fields.js +2 -4
- package/client/components/bookmark-form/config/serial.js +1 -1
- package/client/components/bookmark-form/config/ssh.js +1 -0
- package/client/components/bookmark-form/form-renderer.jsx +3 -2
- package/client/components/common/input-auto-focus.jsx +1 -1
- package/client/components/common/message.jsx +131 -0
- package/client/components/common/message.styl +58 -0
- package/client/components/common/modal.jsx +176 -0
- package/client/components/common/modal.styl +22 -0
- package/client/components/common/notification-with-details.jsx +1 -1
- package/client/components/common/notification.jsx +94 -0
- package/client/components/common/notification.styl +51 -0
- package/client/components/main/connection-hopping-warnning.jsx +1 -3
- package/client/components/main/error-wrapper.jsx +3 -2
- package/client/components/main/main.jsx +4 -11
- package/client/components/main/upgrade.jsx +6 -4
- package/client/components/profile/profile-form-elem.jsx +1 -1
- package/client/components/quick-commands/quick-commands-box.jsx +5 -2
- package/client/components/quick-commands/quick-commands-form-elem.jsx +1 -1
- package/client/components/rdp/rdp-session.jsx +2 -2
- package/client/components/session/session.jsx +4 -9
- package/client/components/setting-panel/deep-link-control.jsx +4 -3
- package/client/components/setting-panel/keyword-input.jsx +60 -0
- package/client/components/setting-panel/keywords-form.jsx +2 -7
- package/client/components/setting-panel/setting-common.jsx +1 -1
- package/client/components/setting-panel/setting-terminal.jsx +1 -1
- package/client/components/setting-panel/tab-settings.jsx +1 -1
- package/client/components/setting-sync/setting-sync-form.jsx +53 -3
- package/client/components/setting-sync/setting-sync.jsx +2 -1
- package/client/components/sftp/owner-list.js +6 -6
- package/client/components/sftp/sftp-entry.jsx +6 -4
- package/client/components/shortcuts/shortcut-editor.jsx +2 -2
- package/client/components/ssh-config/ssh-config-load-notify.jsx +3 -2
- package/client/components/tabs/tab.jsx +1 -1
- package/client/components/tabs/workspace-save-modal.jsx +2 -1
- package/client/components/terminal/attach-addon-custom.js +142 -26
- package/client/components/terminal/command-tracker-addon.js +164 -53
- package/client/components/terminal/highlight-addon.js +84 -43
- package/client/components/terminal/shell.js +138 -0
- package/client/components/terminal/term-search.styl +1 -0
- package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
- package/client/components/terminal/terminal.jsx +166 -104
- package/client/components/theme/theme-form.jsx +2 -1
- package/client/components/tree-list/bookmark-transport.jsx +27 -5
- package/client/components/vnc/vnc-session.jsx +1 -1
- package/client/components/widgets/widget-notification-with-details.jsx +1 -1
- package/client/store/common.js +5 -2
- package/client/store/db-upgrade.js +1 -1
- package/client/store/init-state.js +2 -1
- package/client/store/load-data.js +2 -2
- package/client/store/mcp-handler.js +9 -56
- package/client/store/setting.js +1 -3
- package/client/store/store.js +2 -1
- package/client/store/sync.js +14 -8
- package/client/store/system-menu.js +2 -1
- package/client/store/tab.js +1 -1
- package/client/store/widgets.js +1 -3
- package/package.json +1 -1
- package/client/common/track.js +0 -7
- 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.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.
|
|
8
|
-
this.
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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.
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
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
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
terminal.displayRaw ?
|
|
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.
|
|
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
|
+
}
|
|
@@ -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
|
}
|