@electerm/electerm-react 2.8.16 → 2.10.6
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/constants.js +3 -3
- package/client/common/pre.js +1 -120
- package/client/components/bookmark-form/ai-bookmark-form.jsx +324 -0
- package/client/components/bookmark-form/bookmark-form.styl +1 -1
- package/client/components/bookmark-form/bookmark-schema.js +179 -0
- package/client/components/bookmark-form/common/ai-category-select.jsx +32 -0
- package/client/components/bookmark-form/common/category-select.jsx +2 -4
- package/client/components/bookmark-form/common/fields.jsx +0 -10
- package/client/components/bookmark-form/config/rdp.js +0 -1
- package/client/components/bookmark-form/config/session-config.js +3 -1
- package/client/components/bookmark-form/config/spice.js +44 -0
- package/client/components/bookmark-form/config/vnc.js +1 -2
- package/client/components/bookmark-form/fix-bookmark-default.js +134 -0
- package/client/components/bookmark-form/index.jsx +74 -13
- package/client/components/session/session.jsx +13 -3
- package/client/components/setting-panel/keywords-transport.jsx +0 -1
- package/client/components/shortcuts/shortcut-handler.js +11 -5
- package/client/components/sidebar/index.jsx +11 -1
- package/client/components/spice/spice-session.jsx +276 -0
- package/client/components/tabs/add-btn-menu.jsx +9 -2
- package/client/components/terminal/attach-addon-custom.js +20 -76
- package/client/components/terminal/terminal.jsx +34 -28
- package/client/components/terminal/transfer-client-base.js +232 -0
- package/client/components/terminal/trzsz-client.js +306 -0
- package/client/components/terminal/xterm-loader.js +109 -0
- package/client/components/terminal/zmodem-client.js +13 -166
- package/client/components/text-editor/simple-editor.jsx +1 -2
- package/client/entry/electerm.jsx +0 -2
- package/client/store/system-menu.js +10 -0
- package/package.json +1 -1
- package/client/common/trzsz.js +0 -46
- package/client/components/bookmark-form/common/wiki-alert.jsx +0 -9
- package/client/components/terminal/fs.js +0 -59
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { PureComponent, createRef } from 'react'
|
|
2
|
+
import { createTerm } from '../terminal/terminal-apis'
|
|
3
|
+
import deepCopy from 'json-deep-copy'
|
|
4
|
+
import clone from '../../common/to-simple-obj'
|
|
5
|
+
import { handleErr } from '../../common/fetch'
|
|
6
|
+
import {
|
|
7
|
+
statusMap
|
|
8
|
+
} from '../../common/constants'
|
|
9
|
+
import {
|
|
10
|
+
Spin
|
|
11
|
+
} from 'antd'
|
|
12
|
+
import {
|
|
13
|
+
ReloadOutlined
|
|
14
|
+
} from '@ant-design/icons'
|
|
15
|
+
import RemoteFloatControl from '../common/remote-float-control'
|
|
16
|
+
|
|
17
|
+
async function loadSpiceModule () {
|
|
18
|
+
if (window.spiceHtml5) return
|
|
19
|
+
const mod = await import('spice-client')
|
|
20
|
+
window.spiceHtml5 = {
|
|
21
|
+
SpiceMainConn: mod.SpiceMainConn,
|
|
22
|
+
sendCtrlAltDel: mod.sendCtrlAltDel
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default class SpiceSession extends PureComponent {
|
|
27
|
+
constructor (props) {
|
|
28
|
+
super(props)
|
|
29
|
+
this.state = {
|
|
30
|
+
loading: false,
|
|
31
|
+
connected: false
|
|
32
|
+
}
|
|
33
|
+
this.spiceConn = null
|
|
34
|
+
this.screenId = `spice-screen-${props.tab.id}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
domRef = createRef()
|
|
38
|
+
|
|
39
|
+
componentDidMount () {
|
|
40
|
+
this.remoteInit()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
componentWillUnmount () {
|
|
44
|
+
this.cleanup()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
cleanup = () => {
|
|
48
|
+
if (this.spiceConn) {
|
|
49
|
+
try {
|
|
50
|
+
this.spiceConn.stop()
|
|
51
|
+
} catch (e) {}
|
|
52
|
+
this.spiceConn = null
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setStatus = status => {
|
|
57
|
+
const id = this.props.tab?.id
|
|
58
|
+
this.props.editTab(id, {
|
|
59
|
+
status
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
buildWsUrl = (port, type = 'spice', extra = '') => {
|
|
64
|
+
const { host, tokenElecterm } = this.props.config
|
|
65
|
+
const { id } = this.props.tab
|
|
66
|
+
if (window.et.buildWsUrl) {
|
|
67
|
+
return window.et.buildWsUrl(
|
|
68
|
+
host,
|
|
69
|
+
port,
|
|
70
|
+
tokenElecterm,
|
|
71
|
+
id,
|
|
72
|
+
type,
|
|
73
|
+
extra
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return `ws://${host}:${port}/${type}/${id}?token=${tokenElecterm}${extra}`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
calcCanvasSize = () => {
|
|
80
|
+
const { width, height } = this.props
|
|
81
|
+
return {
|
|
82
|
+
width: width - 10,
|
|
83
|
+
height: height - 80
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
handleSendCtrlAltDel = () => {
|
|
88
|
+
if (this.spiceConn && window.spiceHtml5?.sendCtrlAltDel) {
|
|
89
|
+
window.spiceHtml5.sendCtrlAltDel(this.spiceConn)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getControlProps = (options = {}) => {
|
|
94
|
+
const {
|
|
95
|
+
fixedPosition = true,
|
|
96
|
+
showExitFullscreen = true,
|
|
97
|
+
className = ''
|
|
98
|
+
} = options
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
isFullScreen: this.props.fullscreen,
|
|
102
|
+
onSendCtrlAltDel: this.handleSendCtrlAltDel,
|
|
103
|
+
screens: [],
|
|
104
|
+
currentScreen: null,
|
|
105
|
+
onSelectScreen: () => {},
|
|
106
|
+
fixedPosition,
|
|
107
|
+
showExitFullscreen,
|
|
108
|
+
className
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
renderControl = () => {
|
|
113
|
+
const contrlProps = this.getControlProps({
|
|
114
|
+
fixedPosition: false,
|
|
115
|
+
showExitFullscreen: false,
|
|
116
|
+
className: 'mg1l'
|
|
117
|
+
})
|
|
118
|
+
return (
|
|
119
|
+
<div className='pd1 fix session-v-info'>
|
|
120
|
+
<div className='fleft'>
|
|
121
|
+
<ReloadOutlined
|
|
122
|
+
onClick={this.handleReInit}
|
|
123
|
+
className='mg2r mg1l pointer'
|
|
124
|
+
/>
|
|
125
|
+
{this.renderInfo()}
|
|
126
|
+
</div>
|
|
127
|
+
<div className='fright'>
|
|
128
|
+
{this.props.fullscreenIcon()}
|
|
129
|
+
<RemoteFloatControl {...contrlProps} />
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
remoteInit = async () => {
|
|
136
|
+
this.setState({
|
|
137
|
+
loading: true
|
|
138
|
+
})
|
|
139
|
+
const { config } = this.props
|
|
140
|
+
const { id } = this.props
|
|
141
|
+
const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
|
|
142
|
+
const {
|
|
143
|
+
type,
|
|
144
|
+
term: terminalType,
|
|
145
|
+
password
|
|
146
|
+
} = tab
|
|
147
|
+
const opts = clone({
|
|
148
|
+
term: terminalType || config.terminalType,
|
|
149
|
+
tabId: id,
|
|
150
|
+
uid: tab.id,
|
|
151
|
+
srcTabId: tab.id,
|
|
152
|
+
termType: type,
|
|
153
|
+
...tab
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
const r = await createTerm(opts)
|
|
157
|
+
.catch(err => {
|
|
158
|
+
const text = err.message
|
|
159
|
+
handleErr({ message: text })
|
|
160
|
+
})
|
|
161
|
+
if (!r) {
|
|
162
|
+
this.setState({ loading: false })
|
|
163
|
+
this.setStatus(statusMap.error)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const { pid, port } = r
|
|
168
|
+
this.pid = pid
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await loadSpiceModule()
|
|
172
|
+
} catch (e) {
|
|
173
|
+
console.error('[SPICE] Failed to load SPICE module:', e)
|
|
174
|
+
this.setState({ loading: false })
|
|
175
|
+
this.setStatus(statusMap.error)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.setStatus(statusMap.success)
|
|
180
|
+
|
|
181
|
+
const { width, height } = this.calcCanvasSize()
|
|
182
|
+
const wsUrl = this.buildWsUrl(port, 'spice', `&width=${width}&height=${height}`)
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const SpiceMainConn = window.spiceHtml5.SpiceMainConn
|
|
186
|
+
const spiceOpts = {
|
|
187
|
+
uri: wsUrl,
|
|
188
|
+
password: password || '',
|
|
189
|
+
screen_id: this.screenId,
|
|
190
|
+
onsuccess: () => {
|
|
191
|
+
this.setState({
|
|
192
|
+
loading: false,
|
|
193
|
+
connected: true
|
|
194
|
+
})
|
|
195
|
+
this.setStatus(statusMap.success)
|
|
196
|
+
},
|
|
197
|
+
onerror: (e) => {
|
|
198
|
+
console.error('[SPICE] Connection error:', e)
|
|
199
|
+
this.setState({ loading: false })
|
|
200
|
+
this.setStatus(statusMap.error)
|
|
201
|
+
},
|
|
202
|
+
onagent: () => {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.spiceConn = new SpiceMainConn(spiceOpts)
|
|
206
|
+
|
|
207
|
+
this.setState({
|
|
208
|
+
loading: false
|
|
209
|
+
})
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.error('[SPICE] Connection failed:', e)
|
|
212
|
+
this.setState({ loading: false })
|
|
213
|
+
this.setStatus(statusMap.error)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
handleReInit = () => {
|
|
218
|
+
this.cleanup()
|
|
219
|
+
this.props.reloadTab(
|
|
220
|
+
this.props.tab
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
renderInfo () {
|
|
225
|
+
const {
|
|
226
|
+
host,
|
|
227
|
+
port,
|
|
228
|
+
username
|
|
229
|
+
} = this.props.tab
|
|
230
|
+
return (
|
|
231
|
+
<span className='mg2l mg2r'>
|
|
232
|
+
{username}@{host}:{port}
|
|
233
|
+
</span>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
render () {
|
|
238
|
+
const { width: w, height: h } = this.props
|
|
239
|
+
const { loading } = this.state
|
|
240
|
+
const { width: innerWidth, height: innerHeight } = this.calcCanvasSize()
|
|
241
|
+
const wrapperStyle = {
|
|
242
|
+
width: innerWidth + 'px',
|
|
243
|
+
height: innerHeight + 'px',
|
|
244
|
+
overflow: 'hidden'
|
|
245
|
+
}
|
|
246
|
+
const contrlProps = this.getControlProps()
|
|
247
|
+
return (
|
|
248
|
+
<Spin spinning={loading}>
|
|
249
|
+
<div
|
|
250
|
+
className='rdp-session-wrap session-v-wrap'
|
|
251
|
+
style={{
|
|
252
|
+
width: w + 'px',
|
|
253
|
+
height: h + 'px'
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{this.renderControl()}
|
|
257
|
+
<RemoteFloatControl {...contrlProps} />
|
|
258
|
+
<div
|
|
259
|
+
style={wrapperStyle}
|
|
260
|
+
className='spice-scroll-wrapper'
|
|
261
|
+
>
|
|
262
|
+
<div
|
|
263
|
+
ref={this.domRef}
|
|
264
|
+
id={this.screenId}
|
|
265
|
+
className='spice-session-wrap session-v-wrap'
|
|
266
|
+
style={{
|
|
267
|
+
width: '100%',
|
|
268
|
+
height: '100%'
|
|
269
|
+
}}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</Spin>
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import React, { useCallback } from 'react'
|
|
6
6
|
import {
|
|
7
7
|
CodeFilled,
|
|
8
|
-
RightSquareFilled
|
|
8
|
+
RightSquareFilled,
|
|
9
|
+
RobotOutlined
|
|
9
10
|
} from '@ant-design/icons'
|
|
10
11
|
import BookmarksList from '../sidebar/bookmark-select'
|
|
11
12
|
import DragHandle from '../common/drag-handle'
|
|
@@ -23,7 +24,7 @@ export default function AddBtnMenu ({
|
|
|
23
24
|
addPanelWidth,
|
|
24
25
|
setAddPanelWidth
|
|
25
26
|
}) {
|
|
26
|
-
const { onNewSsh } = window.store
|
|
27
|
+
const { onNewSsh, onNewSshAI } = window.store
|
|
27
28
|
const cls = 'pd2x pd1y context-item pointer'
|
|
28
29
|
const addTabBtn = window.store.hasNodePty
|
|
29
30
|
? (
|
|
@@ -80,6 +81,12 @@ export default function AddBtnMenu ({
|
|
|
80
81
|
<CodeFilled /> {e('newBookmark')}
|
|
81
82
|
</div>
|
|
82
83
|
{addTabBtn}
|
|
84
|
+
<div
|
|
85
|
+
className={cls}
|
|
86
|
+
onClick={onNewSshAI}
|
|
87
|
+
>
|
|
88
|
+
<RobotOutlined /> {e('createBookmarkByAI')}
|
|
89
|
+
</div>
|
|
83
90
|
</div>
|
|
84
91
|
<div className='add-menu-list'>
|
|
85
92
|
<BookmarksList
|
|
@@ -1,57 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
* customize AttachAddon
|
|
3
|
-
*/
|
|
4
|
-
import { AttachAddon } from '@xterm/addon-attach'
|
|
1
|
+
import { loadAttachAddon } from './xterm-loader.js'
|
|
5
2
|
|
|
6
|
-
export default class AttachAddonCustom
|
|
3
|
+
export default class AttachAddonCustom {
|
|
7
4
|
constructor (term, socket, isWindowsShell) {
|
|
8
|
-
super(socket)
|
|
9
5
|
this.term = term
|
|
10
6
|
this.socket = socket
|
|
11
7
|
this.isWindowsShell = isWindowsShell
|
|
12
|
-
// Output suppression state for shell integration injection
|
|
13
8
|
this.outputSuppressed = false
|
|
14
9
|
this.suppressedData = []
|
|
15
10
|
this.suppressTimeout = null
|
|
16
11
|
this.onSuppressionEndCallback = null
|
|
17
|
-
// Track if we've received initial data from the terminal
|
|
18
12
|
this.hasReceivedInitialData = false
|
|
19
13
|
this.onInitialDataCallback = null
|
|
14
|
+
this._bidirectional = true
|
|
15
|
+
this._disposables = []
|
|
16
|
+
this._socket = socket
|
|
17
|
+
this.decoder = new TextDecoder('utf-8')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_initBase = async () => {
|
|
21
|
+
const AttachAddon = await loadAttachAddon()
|
|
22
|
+
const base = new AttachAddon(this._socket, { bidirectional: this._bidirectional })
|
|
23
|
+
this._sendData = base._sendData.bind(base)
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
/**
|
|
23
|
-
* Set callback for when initial data is received
|
|
24
|
-
* @param {Function} callback - Called when first data arrives
|
|
25
|
-
*/
|
|
26
26
|
onInitialData = (callback) => {
|
|
27
27
|
if (this.hasReceivedInitialData) {
|
|
28
|
-
// Already received, call immediately
|
|
29
28
|
callback()
|
|
30
29
|
} else {
|
|
31
30
|
this.onInitialDataCallback = callback
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
/**
|
|
36
|
-
* Start suppressing output - used during shell integration injection
|
|
37
|
-
* @param {number} timeout - Max time to suppress in ms (safety fallback)
|
|
38
|
-
* @param {Function} onEnd - Callback when suppression ends
|
|
39
|
-
*/
|
|
40
34
|
startOutputSuppression = (timeout = 3000, onEnd = null) => {
|
|
41
35
|
this.outputSuppressed = true
|
|
42
36
|
this.suppressedData = []
|
|
43
37
|
this.onSuppressionEndCallback = onEnd
|
|
44
|
-
// Safety timeout to ensure we always resume
|
|
45
38
|
this.suppressTimeout = setTimeout(() => {
|
|
46
39
|
console.warn('[AttachAddon] Output suppression timeout reached, resuming')
|
|
47
40
|
this.stopOutputSuppression(false)
|
|
48
41
|
}, timeout)
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
/**
|
|
52
|
-
* Stop suppressing output and optionally discard buffered data
|
|
53
|
-
* @param {boolean} discard - If true, discard buffered data; if false, write it to terminal
|
|
54
|
-
*/
|
|
55
44
|
stopOutputSuppression = (discard = true) => {
|
|
56
45
|
if (this.suppressTimeout) {
|
|
57
46
|
clearTimeout(this.suppressTimeout)
|
|
@@ -60,14 +49,12 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
60
49
|
this.outputSuppressed = false
|
|
61
50
|
|
|
62
51
|
if (!discard && this.suppressedData.length > 0) {
|
|
63
|
-
// Write buffered data to terminal
|
|
64
52
|
for (const data of this.suppressedData) {
|
|
65
53
|
this.writeToTerminalDirect(data)
|
|
66
54
|
}
|
|
67
55
|
}
|
|
68
56
|
this.suppressedData = []
|
|
69
57
|
|
|
70
|
-
// Call the end callback if set
|
|
71
58
|
if (this.onSuppressionEndCallback) {
|
|
72
59
|
const callback = this.onSuppressionEndCallback
|
|
73
60
|
this.onSuppressionEndCallback = null
|
|
@@ -75,77 +62,43 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
75
62
|
}
|
|
76
63
|
}
|
|
77
64
|
|
|
78
|
-
/**
|
|
79
|
-
* Check if we should resume output based on OSC 633 detection
|
|
80
|
-
* Called when shell integration is detected
|
|
81
|
-
*/
|
|
82
65
|
onShellIntegrationDetected = () => {
|
|
83
66
|
if (this.outputSuppressed) {
|
|
84
|
-
this.stopOutputSuppression(true)
|
|
67
|
+
this.stopOutputSuppression(true)
|
|
85
68
|
}
|
|
86
69
|
}
|
|
87
70
|
|
|
88
|
-
activate (terminal = this.term) {
|
|
89
|
-
this.
|
|
90
|
-
this.writeToTerminal,
|
|
91
|
-
this.sendToServer,
|
|
92
|
-
terminal.cols,
|
|
93
|
-
this.isWindowsShell
|
|
94
|
-
)
|
|
95
|
-
|
|
71
|
+
activate = async (terminal = this.term) => {
|
|
72
|
+
await this._initBase()
|
|
96
73
|
this.addSocketListener(this._socket, 'message', this.onMsg)
|
|
97
74
|
|
|
98
75
|
if (this._bidirectional) {
|
|
99
|
-
this._disposables.push(terminal.onData((data) => this.
|
|
100
|
-
this._disposables.push(terminal.onBinary((data) => this.
|
|
76
|
+
this._disposables.push(terminal.onData((data) => this.sendToServer(data)))
|
|
77
|
+
this._disposables.push(terminal.onBinary((data) => this.sendToServer(new Uint8Array(data))))
|
|
101
78
|
}
|
|
102
79
|
|
|
103
|
-
this._disposables.push(terminal.onResize((size) => this.trzsz.setTerminalColumns(size.cols)))
|
|
104
|
-
|
|
105
80
|
this._disposables.push(this.addSocketListener(this._socket, 'close', () => this.dispose()))
|
|
106
81
|
this._disposables.push(this.addSocketListener(this._socket, 'error', () => this.dispose()))
|
|
107
82
|
}
|
|
108
83
|
|
|
109
84
|
onMsg = (ev) => {
|
|
110
|
-
// Check if it's a JSON zmodem control message
|
|
111
85
|
if (typeof ev.data === 'string') {
|
|
112
86
|
try {
|
|
113
87
|
const msg = JSON.parse(ev.data)
|
|
114
|
-
if (msg.action === 'zmodem-event') {
|
|
115
|
-
// Let zmodem-client handle this, don't write to terminal
|
|
88
|
+
if (msg.action === 'zmodem-event' || msg.action === 'trzsz-event') {
|
|
116
89
|
return
|
|
117
90
|
}
|
|
118
|
-
} catch (e) {
|
|
119
|
-
// Not JSON, continue processing
|
|
120
|
-
}
|
|
91
|
+
} catch (e) {}
|
|
121
92
|
}
|
|
122
93
|
|
|
123
|
-
|
|
124
|
-
// bypass trzsz processing to avoid interference with the application's display
|
|
125
|
-
if (this.term?.buffer?.active?.type === 'alternate') {
|
|
126
|
-
this.writeToTerminal(ev.data)
|
|
127
|
-
} else {
|
|
128
|
-
this.trzsz.processServerOutput(ev.data)
|
|
129
|
-
}
|
|
94
|
+
this.writeToTerminal(ev.data)
|
|
130
95
|
}
|
|
131
96
|
|
|
132
|
-
/**
|
|
133
|
-
* Check if data contains OSC 633 shell integration sequences
|
|
134
|
-
* @param {string} str - Data string to check
|
|
135
|
-
* @returns {boolean} True if OSC 633 sequence detected
|
|
136
|
-
*/
|
|
137
97
|
checkForShellIntegration = (str) => {
|
|
138
|
-
// OSC 633 sequences: ESC]633;X where X is A, B, C, D, E, or P
|
|
139
|
-
// ESC is character code 27 (0x1b)
|
|
140
|
-
// Use includes with the actual characters to avoid lint warning
|
|
141
98
|
const ESC = String.fromCharCode(27)
|
|
142
99
|
return str.includes(ESC + ']633;')
|
|
143
100
|
}
|
|
144
101
|
|
|
145
|
-
/**
|
|
146
|
-
* Write directly to terminal, bypassing suppression check
|
|
147
|
-
* Used for flushing buffered data
|
|
148
|
-
*/
|
|
149
102
|
writeToTerminalDirect = (data) => {
|
|
150
103
|
const { term } = this
|
|
151
104
|
if (term.parent?.onZmodem) {
|
|
@@ -163,22 +116,18 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
163
116
|
return
|
|
164
117
|
}
|
|
165
118
|
|
|
166
|
-
// Track initial data arrival
|
|
167
119
|
if (!this.hasReceivedInitialData) {
|
|
168
120
|
this.hasReceivedInitialData = true
|
|
169
121
|
if (this.onInitialDataCallback) {
|
|
170
122
|
const callback = this.onInitialDataCallback
|
|
171
123
|
this.onInitialDataCallback = null
|
|
172
|
-
// Call after a micro-delay to ensure this data is written first
|
|
173
124
|
setTimeout(callback, 0)
|
|
174
125
|
}
|
|
175
126
|
}
|
|
176
127
|
|
|
177
|
-
// Check for shell integration in the data (only when suppressing)
|
|
178
128
|
if (this.outputSuppressed) {
|
|
179
129
|
let str = data
|
|
180
130
|
if (typeof data !== 'string') {
|
|
181
|
-
// Convert to string to check for OSC 633
|
|
182
131
|
const decoder = this.decoder || new TextDecoder('utf-8')
|
|
183
132
|
try {
|
|
184
133
|
str = decoder.decode(data instanceof ArrayBuffer ? data : new Uint8Array(data))
|
|
@@ -187,14 +136,11 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
187
136
|
}
|
|
188
137
|
}
|
|
189
138
|
|
|
190
|
-
// If we detect OSC 633, shell integration is working
|
|
191
139
|
if (this.checkForShellIntegration(str)) {
|
|
192
140
|
this.onShellIntegrationDetected()
|
|
193
|
-
// Don't buffer this - just discard the integration output
|
|
194
141
|
return
|
|
195
142
|
}
|
|
196
143
|
|
|
197
|
-
// Buffer the data while suppressed
|
|
198
144
|
this.suppressedData.push(data)
|
|
199
145
|
return
|
|
200
146
|
}
|
|
@@ -213,8 +159,6 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
213
159
|
const { term } = this
|
|
214
160
|
term?.parent?.notifyOnData()
|
|
215
161
|
const str = this.decoder.decode(data)
|
|
216
|
-
// CWD tracking is now handled by shell integration automatically
|
|
217
|
-
// No need to parse PS1 markers
|
|
218
162
|
term?.write(str)
|
|
219
163
|
}
|
|
220
164
|
|