@electerm/electerm-react 2.4.16 → 2.4.28
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/components/common/message.jsx +28 -3
- package/client/components/common/message.styl +2 -4
- package/client/components/common/notification.jsx +29 -5
- package/client/components/shortcuts/shortcut-handler.js +0 -4
- package/client/components/terminal/shell.js +47 -21
- package/client/components/terminal/terminal.jsx +152 -43
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { createRoot } from 'react-dom/client'
|
|
3
3
|
import {
|
|
4
4
|
CloseOutlined
|
|
@@ -26,15 +26,40 @@ const notify = (messages) => {
|
|
|
26
26
|
let activeMessages = []
|
|
27
27
|
|
|
28
28
|
function MessageItem ({ id, type, content, duration, onRemove, timestamp }) {
|
|
29
|
+
const timeoutIdRef = useRef(null)
|
|
30
|
+
|
|
29
31
|
useEffect(() => {
|
|
30
32
|
if (duration !== 0) {
|
|
31
33
|
const timer = setTimeout(onRemove, duration * 1000)
|
|
32
|
-
|
|
34
|
+
timeoutIdRef.current = timer
|
|
35
|
+
return () => {
|
|
36
|
+
clearTimeout(timeoutIdRef.current)
|
|
37
|
+
timeoutIdRef.current = null
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
}, [duration, onRemove, timestamp])
|
|
35
41
|
|
|
42
|
+
const handleMouseEnter = () => {
|
|
43
|
+
if (timeoutIdRef.current) {
|
|
44
|
+
clearTimeout(timeoutIdRef.current)
|
|
45
|
+
timeoutIdRef.current = null
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const handleMouseLeave = () => {
|
|
50
|
+
if (duration !== 0) {
|
|
51
|
+
const timer = setTimeout(onRemove, duration * 1000)
|
|
52
|
+
timeoutIdRef.current = timer
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
return (
|
|
37
|
-
<div
|
|
57
|
+
<div
|
|
58
|
+
className={classnames('message-item', type)}
|
|
59
|
+
id={`message-${id}`}
|
|
60
|
+
onMouseEnter={handleMouseEnter}
|
|
61
|
+
onMouseLeave={handleMouseLeave}
|
|
62
|
+
>
|
|
38
63
|
<div className='message-content-wrap'>
|
|
39
64
|
{messageIcons[type]}
|
|
40
65
|
<div className='message-content'>{content}</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
2
2
|
import { CloseOutlined } from '@ant-design/icons'
|
|
3
3
|
import classnames from 'classnames'
|
|
4
4
|
import generateId from '../../common/uid'
|
|
@@ -70,17 +70,41 @@ export function NotificationContainer () {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function NotificationItem ({ message, description, type, onClose, duration = 18.5 }) {
|
|
73
|
+
const timeoutRef = useRef(null)
|
|
74
|
+
|
|
73
75
|
useEffect(() => {
|
|
74
76
|
if (duration > 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
+
timeoutRef.current = setTimeout(onClose, duration * 1000)
|
|
78
|
+
}
|
|
79
|
+
return () => {
|
|
80
|
+
if (timeoutRef.current) {
|
|
81
|
+
clearTimeout(timeoutRef.current)
|
|
82
|
+
timeoutRef.current = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, [])
|
|
86
|
+
|
|
87
|
+
const handleMouseEnter = () => {
|
|
88
|
+
if (timeoutRef.current) {
|
|
89
|
+
clearTimeout(timeoutRef.current)
|
|
90
|
+
timeoutRef.current = null
|
|
77
91
|
}
|
|
78
|
-
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleMouseLeave = () => {
|
|
95
|
+
if (duration > 0 && !timeoutRef.current) {
|
|
96
|
+
timeoutRef.current = setTimeout(onClose, duration * 1000)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
79
99
|
|
|
80
100
|
const className = classnames('notification', type)
|
|
81
101
|
|
|
82
102
|
return (
|
|
83
|
-
<div
|
|
103
|
+
<div
|
|
104
|
+
className={className}
|
|
105
|
+
onMouseEnter={handleMouseEnter}
|
|
106
|
+
onMouseLeave={handleMouseLeave}
|
|
107
|
+
>
|
|
84
108
|
<div className='notification-content'>
|
|
85
109
|
<div className='notification-message'>
|
|
86
110
|
<div className='notification-icon'>{messageIcons[type]}</div>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/* eslint-disable no-template-curly-in-string, no-useless-escape */
|
|
18
|
+
import { runCmd } from './terminal-apis.js'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Get inline shell integration command for bash (one-liner format)
|
|
@@ -69,6 +70,35 @@ function getFishInlineIntegration () {
|
|
|
69
70
|
].join('; ')
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Get inline shell integration command for sh/ash (one-liner format)
|
|
75
|
+
* Uses PS1 injection as sh/ash lack PROMPT_COMMAND or advanced traps.
|
|
76
|
+
*/
|
|
77
|
+
function getShInlineIntegration () {
|
|
78
|
+
return [
|
|
79
|
+
'if [ -z "$ELECTERM_SHELL_INTEGRATION" ]',
|
|
80
|
+
'then export ELECTERM_SHELL_INTEGRATION=1',
|
|
81
|
+
'__e_esc() { printf "%s" "$1" | sed "s/\\\\/\\\\\\\\/g; s/;/\\\\x3b/g"; }',
|
|
82
|
+
// We wrap the current PS1 with OSC 633 sequences.
|
|
83
|
+
// \033]633;P;Cwd=... \007 marks the directory
|
|
84
|
+
// \033]633;A \007 marks the start of the prompt
|
|
85
|
+
'export PS1="\\e]633;P;Cwd=$(__e_esc "$PWD")\\a\\e]633;A\\a${PS1:-# }"',
|
|
86
|
+
'fi'
|
|
87
|
+
].join('; ')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function detectShellType (shellStr) {
|
|
91
|
+
if (shellStr.includes('bash')) {
|
|
92
|
+
return 'bash'
|
|
93
|
+
} else if (shellStr.includes('zsh')) {
|
|
94
|
+
return 'zsh'
|
|
95
|
+
} else if (shellStr.includes('fish')) {
|
|
96
|
+
return 'fish'
|
|
97
|
+
} else {
|
|
98
|
+
return 'sh'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
72
102
|
/**
|
|
73
103
|
* Get shell integration command based on detected shell type
|
|
74
104
|
* @param {string} shellType - 'bash', 'zsh', or 'fish'
|
|
@@ -84,7 +114,7 @@ export function getInlineShellIntegration (shellType) {
|
|
|
84
114
|
return getFishInlineIntegration()
|
|
85
115
|
default:
|
|
86
116
|
// Try bash as default for sh-compatible shells
|
|
87
|
-
return
|
|
117
|
+
return getShInlineIntegration()
|
|
88
118
|
}
|
|
89
119
|
}
|
|
90
120
|
|
|
@@ -112,27 +142,23 @@ export function getShellIntegrationCommand (shellType = 'bash') {
|
|
|
112
142
|
const cmd = getInlineShellIntegration(shellType)
|
|
113
143
|
return wrapSilent(cmd, shellType)
|
|
114
144
|
}
|
|
145
|
+
export async function detectRemoteShell (pid) {
|
|
146
|
+
// 1. We try the version variables first.
|
|
147
|
+
// 2. We try your verified fish check: fish --version ...
|
|
148
|
+
// 3. We use ps -p $$ to check the process name (highly reliable in Linux/Docker).
|
|
149
|
+
// This syntax is safe for Bash, Zsh, and Fish.
|
|
150
|
+
const cmd = 'fish --version 2>/dev/null | grep -q fish && echo fish || { env | grep -q ZSH_VERSION && echo zsh || { env | grep -q BASH_VERSION && echo bash || { ps -p $$ -o comm= 2>/dev/null || echo sh; }; }; }'
|
|
115
151
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
export function detectShellType (shellPath = '') {
|
|
122
|
-
if (!shellPath) return 'bash'
|
|
123
|
-
|
|
124
|
-
const normalizedPath = shellPath.toLowerCase()
|
|
152
|
+
const r = await runCmd(pid, cmd)
|
|
153
|
+
.catch((err) => {
|
|
154
|
+
console.error('detectRemoteShell error', err)
|
|
155
|
+
return 'sh'
|
|
156
|
+
})
|
|
125
157
|
|
|
126
|
-
|
|
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
|
-
}
|
|
158
|
+
const shell = r.trim().toLowerCase()
|
|
136
159
|
|
|
137
|
-
return '
|
|
160
|
+
if (shell.includes('fish')) return 'fish'
|
|
161
|
+
if (shell.includes('zsh')) return 'zsh'
|
|
162
|
+
if (shell.includes('bash')) return 'bash'
|
|
163
|
+
return 'sh' // Fallback for sh/ash/dash
|
|
138
164
|
}
|
|
@@ -52,11 +52,13 @@ import AIIcon from '../icons/ai-icon.jsx'
|
|
|
52
52
|
import { formatBytes } from '../../common/byte-format.js'
|
|
53
53
|
import {
|
|
54
54
|
getShellIntegrationCommand,
|
|
55
|
+
detectRemoteShell,
|
|
55
56
|
detectShellType
|
|
56
57
|
} from './shell.js'
|
|
57
58
|
import * as fs from './fs.js'
|
|
58
59
|
import iconsMap from '../sys-menu/icons-map.jsx'
|
|
59
60
|
import { refs, refsStatic } from '../common/ref.js'
|
|
61
|
+
import ExternalLink from '../common/external-link.jsx'
|
|
60
62
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
61
63
|
import SearchResultBar from './terminal-search-bar'
|
|
62
64
|
|
|
@@ -79,6 +81,9 @@ class Term extends Component {
|
|
|
79
81
|
this.id = `term-${this.props.tab.id}`
|
|
80
82
|
refs.add(this.id, this)
|
|
81
83
|
this.currentInput = ''
|
|
84
|
+
this.shellInjected = false
|
|
85
|
+
this.shellType = null
|
|
86
|
+
this.manualCommandHistory = new Set()
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
domRef = createRef()
|
|
@@ -215,6 +220,41 @@ class Term extends Component {
|
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
}
|
|
223
|
+
|
|
224
|
+
// Check for shell integration related config changes
|
|
225
|
+
const prevShowSuggestions = prevProps.config.showCmdSuggestions
|
|
226
|
+
const currShowSuggestions = props.config.showCmdSuggestions
|
|
227
|
+
const prevSftpFollow = prevProps.sftpPathFollowSsh
|
|
228
|
+
const currSftpFollow = props.sftpPathFollowSsh
|
|
229
|
+
|
|
230
|
+
if (
|
|
231
|
+
(!prevShowSuggestions && currShowSuggestions) ||
|
|
232
|
+
(!prevSftpFollow && currSftpFollow)
|
|
233
|
+
) {
|
|
234
|
+
// Config was toggled to true, try to inject shell integration if not already done
|
|
235
|
+
if (this.canInjectShellIntegration() && !this.shellInjected) {
|
|
236
|
+
// If there's an active execution queue, add to it
|
|
237
|
+
if (this.executionQueue && this.executionQueue.length > 0) {
|
|
238
|
+
this.executionQueue.unshift({
|
|
239
|
+
type: 'shell_integration',
|
|
240
|
+
execute: async () => {
|
|
241
|
+
await this.injectShellIntegration()
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
} else {
|
|
245
|
+
// No active queue, inject directly
|
|
246
|
+
this.injectShellIntegration()
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (
|
|
251
|
+
!prevSftpFollow &&
|
|
252
|
+
currSftpFollow &&
|
|
253
|
+
this.isLocal() &&
|
|
254
|
+
isWin
|
|
255
|
+
) {
|
|
256
|
+
return this.warnSftpFollowUnsupported()
|
|
257
|
+
}
|
|
218
258
|
}
|
|
219
259
|
|
|
220
260
|
timers = {}
|
|
@@ -293,6 +333,14 @@ class Term extends Component {
|
|
|
293
333
|
})
|
|
294
334
|
}
|
|
295
335
|
|
|
336
|
+
warnSftpFollowUnsupported = () => {
|
|
337
|
+
message.warning(
|
|
338
|
+
<span>
|
|
339
|
+
Fish shell/windows shell is not supported for SFTP follow SSH path feature. See: <ExternalLink to='https://github.com/electerm/electerm/wiki/Warning-about-sftp-follow-ssh-path-function'>wiki</ExternalLink>
|
|
340
|
+
</span>
|
|
341
|
+
, 7)
|
|
342
|
+
}
|
|
343
|
+
|
|
296
344
|
pasteShortcut = (e) => {
|
|
297
345
|
if (this.pasteTextTooLong()) {
|
|
298
346
|
this.askUserConfirm()
|
|
@@ -596,7 +644,7 @@ class Term extends Component {
|
|
|
596
644
|
}
|
|
597
645
|
const r = []
|
|
598
646
|
for (const filePath of files) {
|
|
599
|
-
const stat = await getLocalFileInfo(filePath)
|
|
647
|
+
const stat = await getLocalFileInfo(filePath)
|
|
600
648
|
r.push({ ...stat, filePath })
|
|
601
649
|
}
|
|
602
650
|
return r
|
|
@@ -945,6 +993,12 @@ class Term extends Component {
|
|
|
945
993
|
}
|
|
946
994
|
// Handle Enter
|
|
947
995
|
if (d === '\r' || d === '\n') {
|
|
996
|
+
// Add to manual command history if shell integration is not available
|
|
997
|
+
if (this.currentInput.trim() && this.shouldUseManualHistory()) {
|
|
998
|
+
this.manualCommandHistory.add(this.currentInput.trim())
|
|
999
|
+
// Also add to global history for suggestions
|
|
1000
|
+
window.store.addCmdHistory(this.currentInput.trim())
|
|
1001
|
+
}
|
|
948
1002
|
this.currentInput = ''
|
|
949
1003
|
return
|
|
950
1004
|
}
|
|
@@ -1042,6 +1096,7 @@ class Term extends Component {
|
|
|
1042
1096
|
term.onData(this.onData)
|
|
1043
1097
|
this.term = term
|
|
1044
1098
|
term.onSelectionChange(this.onSelectionChange)
|
|
1099
|
+
term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
|
|
1045
1100
|
await this.remoteInit(term)
|
|
1046
1101
|
}
|
|
1047
1102
|
|
|
@@ -1060,7 +1115,7 @@ class Term extends Component {
|
|
|
1060
1115
|
// })
|
|
1061
1116
|
// }
|
|
1062
1117
|
|
|
1063
|
-
runInitScript = () => {
|
|
1118
|
+
runInitScript = async () => {
|
|
1064
1119
|
window.store.triggerResize()
|
|
1065
1120
|
const {
|
|
1066
1121
|
startDirectory,
|
|
@@ -1072,20 +1127,51 @@ class Term extends Component {
|
|
|
1072
1127
|
if (startFolder) {
|
|
1073
1128
|
scripts.unshift({ script: `cd "${startFolder}"`, delay: 0 })
|
|
1074
1129
|
}
|
|
1075
|
-
this.pendingRunScripts = scripts
|
|
1076
1130
|
|
|
1077
|
-
//
|
|
1078
|
-
|
|
1131
|
+
// Create unified execution queue
|
|
1132
|
+
this.executionQueue = []
|
|
1133
|
+
|
|
1134
|
+
// Add shell integration injection to queue if needed
|
|
1079
1135
|
if (this.canInjectShellIntegration()) {
|
|
1080
|
-
this.
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1136
|
+
this.executionQueue.push({
|
|
1137
|
+
type: 'shell_integration',
|
|
1138
|
+
execute: async () => {
|
|
1139
|
+
await this.injectShellIntegration()
|
|
1140
|
+
}
|
|
1141
|
+
})
|
|
1084
1142
|
}
|
|
1143
|
+
|
|
1144
|
+
// Add delayed scripts to queue
|
|
1145
|
+
scripts.forEach(script => {
|
|
1146
|
+
this.executionQueue.push({
|
|
1147
|
+
type: 'delayed_script',
|
|
1148
|
+
script: script.script,
|
|
1149
|
+
delay: script.delay || 0,
|
|
1150
|
+
execute: () => {
|
|
1151
|
+
if (script.script) {
|
|
1152
|
+
this.attachAddon._sendData(script.script + '\r')
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
})
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
this.processExecutionQueue()
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
shouldUseManualHistory = () => {
|
|
1162
|
+
const useManual = this.props.config.showCmdSuggestions &&
|
|
1163
|
+
(this.shellType === 'sh' || (isWin && this.isLocal()))
|
|
1164
|
+
return useManual
|
|
1085
1165
|
}
|
|
1086
1166
|
|
|
1087
1167
|
canInjectShellIntegration = () => {
|
|
1088
|
-
|
|
1168
|
+
const { config } = this.props
|
|
1169
|
+
const canInject = (config.showCmdSuggestions || this.props.sftpPathFollowSsh) &&
|
|
1170
|
+
(
|
|
1171
|
+
this.isSsh() ||
|
|
1172
|
+
(this.isLocal() && !isWin)
|
|
1173
|
+
)
|
|
1174
|
+
return canInject
|
|
1089
1175
|
}
|
|
1090
1176
|
|
|
1091
1177
|
isSsh = () => {
|
|
@@ -1095,70 +1181,93 @@ class Term extends Component {
|
|
|
1095
1181
|
|
|
1096
1182
|
isLocal = () => {
|
|
1097
1183
|
const { host, type } = this.props.tab
|
|
1098
|
-
return !host &&
|
|
1184
|
+
return !host &&
|
|
1185
|
+
(type === 'local' || type === undefined)
|
|
1099
1186
|
}
|
|
1100
1187
|
|
|
1101
1188
|
/**
|
|
1102
|
-
*
|
|
1103
|
-
* Called after shell integration injection completes (or immediately if disabled)
|
|
1189
|
+
* Process the unified execution queue one item at a time
|
|
1104
1190
|
*/
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1191
|
+
processExecutionQueue = async () => {
|
|
1192
|
+
if (!this.executionQueue || this.executionQueue.length === 0) {
|
|
1193
|
+
return
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const item = this.executionQueue.shift()
|
|
1197
|
+
|
|
1198
|
+
try {
|
|
1199
|
+
if (item.type === 'shell_integration') {
|
|
1200
|
+
await item.execute()
|
|
1201
|
+
} else if (item.type === 'delayed_script') {
|
|
1202
|
+
item.execute()
|
|
1203
|
+
// Wait for the specified delay before processing next item
|
|
1204
|
+
if (item.delay > 0) {
|
|
1205
|
+
await new Promise(resolve => {
|
|
1206
|
+
this.timers.timerDelay = setTimeout(resolve, item.delay)
|
|
1207
|
+
})
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
console.error('[Shell Integration] Error processing queue item:', item.type, error)
|
|
1110
1212
|
}
|
|
1111
|
-
|
|
1213
|
+
|
|
1214
|
+
// Process next item
|
|
1215
|
+
this.processExecutionQueue()
|
|
1112
1216
|
}
|
|
1113
1217
|
|
|
1114
1218
|
/**
|
|
1115
1219
|
* Inject shell integration commands from client-side
|
|
1116
1220
|
* This replaces the server-side source xxx.xxx approach
|
|
1117
1221
|
* Uses output suppression to hide the injection command
|
|
1222
|
+
* Returns a promise that resolves when injection is complete
|
|
1118
1223
|
*/
|
|
1119
|
-
injectShellIntegration = () => {
|
|
1120
|
-
|
|
1121
|
-
|
|
1224
|
+
injectShellIntegration = async () => {
|
|
1225
|
+
if (this.shellInjected) {
|
|
1226
|
+
return Promise.resolve()
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
let shellType
|
|
1122
1230
|
if (this.isLocal()) {
|
|
1123
1231
|
const { config } = this.props
|
|
1124
1232
|
const localShell = isMac ? config.execMac : config.execLinux
|
|
1125
1233
|
shellType = detectShellType(localShell)
|
|
1234
|
+
} else if (this.isSsh()) {
|
|
1235
|
+
shellType = await detectRemoteShell(this.pid)
|
|
1126
1236
|
}
|
|
1127
1237
|
|
|
1128
|
-
|
|
1238
|
+
this.shellType = shellType
|
|
1239
|
+
if (shellType === 'fish') {
|
|
1240
|
+
if (this.props.sftpPathFollowSsh) {
|
|
1241
|
+
this.warnSftpFollowUnsupported()
|
|
1242
|
+
}
|
|
1243
|
+
return Promise.resolve()
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Don't inject for sh type shells unless sftpPathFollowSsh is true
|
|
1247
|
+
if (shellType === 'sh' && !this.props.sftpPathFollowSsh) {
|
|
1248
|
+
return Promise.resolve()
|
|
1249
|
+
}
|
|
1129
1250
|
|
|
1130
|
-
// Remote sessions might need longer timeout for shell integration detection
|
|
1131
1251
|
const integrationCmd = getShellIntegrationCommand(shellType)
|
|
1132
1252
|
|
|
1133
|
-
|
|
1253
|
+
return new Promise((resolve) => {
|
|
1134
1254
|
// Wait for initial data (prompt/banner) to arrive before injecting
|
|
1135
1255
|
this.attachAddon.onInitialData(() => {
|
|
1136
1256
|
if (this.attachAddon) {
|
|
1137
1257
|
// Start suppressing output before sending the integration command
|
|
1138
1258
|
// This hides the command and its output until OSC 633 is detected
|
|
1139
|
-
const suppressionTimeout =
|
|
1140
|
-
// Pass callback to
|
|
1259
|
+
const suppressionTimeout = this.isSsh() ? 5000 : 3000
|
|
1260
|
+
// Pass callback to resolve the promise after suppression ends
|
|
1141
1261
|
this.attachAddon.startOutputSuppression(suppressionTimeout, () => {
|
|
1142
|
-
this.
|
|
1262
|
+
this.shellInjected = true
|
|
1263
|
+
resolve()
|
|
1143
1264
|
})
|
|
1144
1265
|
this.attachAddon._sendData(integrationCmd)
|
|
1266
|
+
} else {
|
|
1267
|
+
resolve()
|
|
1145
1268
|
}
|
|
1146
1269
|
})
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
runDelayedScripts = () => {
|
|
1151
|
-
const { delayedScripts } = this
|
|
1152
|
-
if (delayedScripts && delayedScripts.length > 0) {
|
|
1153
|
-
const obj = delayedScripts.shift()
|
|
1154
|
-
if (obj.script) {
|
|
1155
|
-
this.attachAddon._sendData(obj.script + '\r')
|
|
1156
|
-
}
|
|
1157
|
-
if (delayedScripts.length > 0) {
|
|
1158
|
-
const nextDelay = delayedScripts[0].delay || 0
|
|
1159
|
-
this.timers.timerDelay = setTimeout(this.runDelayedScripts, nextDelay)
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1270
|
+
})
|
|
1162
1271
|
}
|
|
1163
1272
|
|
|
1164
1273
|
setStatus = status => {
|