@electerm/electerm-react 2.3.198 → 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/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 +2 -1
- 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/terminal-command-dropdown.jsx +3 -0
- package/client/components/terminal/terminal.jsx +165 -103
- 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 -50
- 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
|
@@ -29,7 +29,7 @@ const linuxListGroup = 'cat /etc/group'
|
|
|
29
29
|
|
|
30
30
|
export async function remoteListUsers (pid) {
|
|
31
31
|
const users = await runCmd(pid, linuxListUser)
|
|
32
|
-
.catch(
|
|
32
|
+
.catch(console.error)
|
|
33
33
|
if (users) {
|
|
34
34
|
return parseNames(users)
|
|
35
35
|
}
|
|
@@ -38,7 +38,7 @@ export async function remoteListUsers (pid) {
|
|
|
38
38
|
|
|
39
39
|
export async function remoteListGroups (pid) {
|
|
40
40
|
const groups = await runCmd(pid, linuxListGroup)
|
|
41
|
-
.catch(
|
|
41
|
+
.catch(console.error)
|
|
42
42
|
if (groups) {
|
|
43
43
|
return parseNames(groups)
|
|
44
44
|
}
|
|
@@ -50,7 +50,7 @@ export async function localListUsers () {
|
|
|
50
50
|
return {}
|
|
51
51
|
} else if (isMac) {
|
|
52
52
|
const g = await fs.run('dscl . -list /Users UniqueID')
|
|
53
|
-
.catch(
|
|
53
|
+
.catch(console.error)
|
|
54
54
|
return g
|
|
55
55
|
? g.split('\n')
|
|
56
56
|
.reduce((p, s) => {
|
|
@@ -65,7 +65,7 @@ export async function localListUsers () {
|
|
|
65
65
|
}, {})
|
|
66
66
|
: {}
|
|
67
67
|
} else {
|
|
68
|
-
const g = await fs.run(linuxListUser).catch(
|
|
68
|
+
const g = await fs.run(linuxListUser).catch(console.error)
|
|
69
69
|
return g
|
|
70
70
|
? parseNames(g)
|
|
71
71
|
: {}
|
|
@@ -77,7 +77,7 @@ export async function localListGroups () {
|
|
|
77
77
|
return {}
|
|
78
78
|
} else if (isMac) {
|
|
79
79
|
const g = await fs.run('dscl . list /Groups PrimaryGroupID')
|
|
80
|
-
.catch(
|
|
80
|
+
.catch(console.error)
|
|
81
81
|
return g
|
|
82
82
|
? g.split('\n')
|
|
83
83
|
.reduce((p, s) => {
|
|
@@ -89,7 +89,7 @@ export async function localListGroups () {
|
|
|
89
89
|
}, {})
|
|
90
90
|
: {}
|
|
91
91
|
} else {
|
|
92
|
-
const g = await fs.run(linuxListGroup).catch(
|
|
92
|
+
const g = await fs.run(linuxListGroup).catch(console.error)
|
|
93
93
|
return g
|
|
94
94
|
? parseNames(g)
|
|
95
95
|
: {}
|
|
@@ -2,7 +2,9 @@ import { Component } from 'react'
|
|
|
2
2
|
import { refs } from '../common/ref'
|
|
3
3
|
import generate from '../../common/uid'
|
|
4
4
|
import runIdle from '../../common/run-idle'
|
|
5
|
-
import { Spin
|
|
5
|
+
import { Spin } from 'antd'
|
|
6
|
+
import { notification } from '../common/notification'
|
|
7
|
+
import Modal from '../common/modal'
|
|
6
8
|
import clone from '../../common/to-simple-obj'
|
|
7
9
|
import { isEqual, last, isNumber, some, isArray, pick, uniq, debounce } from 'lodash-es'
|
|
8
10
|
import FileSection from './file-item'
|
|
@@ -846,8 +848,8 @@ export default class Sftp extends Component {
|
|
|
846
848
|
sftp,
|
|
847
849
|
realpath
|
|
848
850
|
).catch(e => {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
+
console.debug('seems a bad symbolic link')
|
|
852
|
+
console.debug(e)
|
|
851
853
|
return null
|
|
852
854
|
})
|
|
853
855
|
if (!realFileInfo) {
|
|
@@ -981,7 +983,7 @@ export default class Sftp extends Component {
|
|
|
981
983
|
const np = this.parsePath(type, this.state[nt])
|
|
982
984
|
if (!isValidPath(np)) {
|
|
983
985
|
return notification.warning({
|
|
984
|
-
|
|
986
|
+
message: 'path not valid'
|
|
985
987
|
})
|
|
986
988
|
}
|
|
987
989
|
this.setState({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { Button } from 'antd'
|
|
3
|
+
import { notification } from '../common/notification'
|
|
3
4
|
import * as ls from '../../common/safe-local-storage'
|
|
4
5
|
import {
|
|
5
6
|
sshConfigKey,
|
|
@@ -20,7 +21,7 @@ function handleIgnore () {
|
|
|
20
21
|
|
|
21
22
|
function showNotification () {
|
|
22
23
|
notification.info({
|
|
23
|
-
|
|
24
|
+
message: e('loadSshConfigs'),
|
|
24
25
|
duration: 0,
|
|
25
26
|
placement: 'bottom',
|
|
26
27
|
key: 'sshConfigNotify',
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
} from '@ant-design/icons'
|
|
13
13
|
import {
|
|
14
14
|
Tooltip,
|
|
15
|
-
message,
|
|
16
15
|
Dropdown
|
|
17
16
|
} from 'antd'
|
|
17
|
+
import message from '../common/message'
|
|
18
18
|
import classnames from 'classnames'
|
|
19
19
|
import { pick } from 'lodash-es'
|
|
20
20
|
import Input from '../common/input-auto-focus'
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import React, { useState } from 'react'
|
|
6
6
|
import { auto } from 'manate/react'
|
|
7
7
|
import Modal from '../common/modal'
|
|
8
|
-
import { Input, Select, Button, Space,
|
|
8
|
+
import { Input, Select, Button, Space, Radio } from 'antd'
|
|
9
|
+
import message from '../common/message'
|
|
9
10
|
import { SaveOutlined, EditOutlined } from '@ant-design/icons'
|
|
10
11
|
|
|
11
12
|
const e = window.translate
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* customize AttachAddon
|
|
3
3
|
*/
|
|
4
4
|
import { AttachAddon } from '@xterm/addon-attach'
|
|
5
|
-
import regEscape from 'escape-string-regexp'
|
|
6
5
|
|
|
7
6
|
export default class AttachAddonCustom extends AttachAddon {
|
|
8
7
|
constructor (term, socket, isWindowsShell) {
|
|
@@ -10,6 +9,80 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
10
9
|
this.term = term
|
|
11
10
|
this.socket = socket
|
|
12
11
|
this.isWindowsShell = isWindowsShell
|
|
12
|
+
// Output suppression state for shell integration injection
|
|
13
|
+
this.outputSuppressed = false
|
|
14
|
+
this.suppressedData = []
|
|
15
|
+
this.suppressTimeout = null
|
|
16
|
+
this.onSuppressionEndCallback = null
|
|
17
|
+
// Track if we've received initial data from the terminal
|
|
18
|
+
this.hasReceivedInitialData = false
|
|
19
|
+
this.onInitialDataCallback = null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Set callback for when initial data is received
|
|
24
|
+
* @param {Function} callback - Called when first data arrives
|
|
25
|
+
*/
|
|
26
|
+
onInitialData = (callback) => {
|
|
27
|
+
if (this.hasReceivedInitialData) {
|
|
28
|
+
// Already received, call immediately
|
|
29
|
+
callback()
|
|
30
|
+
} else {
|
|
31
|
+
this.onInitialDataCallback = callback
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
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
|
+
startOutputSuppression = (timeout = 3000, onEnd = null) => {
|
|
41
|
+
this.outputSuppressed = true
|
|
42
|
+
this.suppressedData = []
|
|
43
|
+
this.onSuppressionEndCallback = onEnd
|
|
44
|
+
// Safety timeout to ensure we always resume
|
|
45
|
+
this.suppressTimeout = setTimeout(() => {
|
|
46
|
+
console.warn('[AttachAddon] Output suppression timeout reached, resuming')
|
|
47
|
+
this.stopOutputSuppression(false)
|
|
48
|
+
}, timeout)
|
|
49
|
+
}
|
|
50
|
+
|
|
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
|
+
stopOutputSuppression = (discard = true) => {
|
|
56
|
+
if (this.suppressTimeout) {
|
|
57
|
+
clearTimeout(this.suppressTimeout)
|
|
58
|
+
this.suppressTimeout = null
|
|
59
|
+
}
|
|
60
|
+
this.outputSuppressed = false
|
|
61
|
+
|
|
62
|
+
if (!discard && this.suppressedData.length > 0) {
|
|
63
|
+
// Write buffered data to terminal
|
|
64
|
+
for (const data of this.suppressedData) {
|
|
65
|
+
this.writeToTerminalDirect(data)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
this.suppressedData = []
|
|
69
|
+
|
|
70
|
+
// Call the end callback if set
|
|
71
|
+
if (this.onSuppressionEndCallback) {
|
|
72
|
+
const callback = this.onSuppressionEndCallback
|
|
73
|
+
this.onSuppressionEndCallback = null
|
|
74
|
+
callback()
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if we should resume output based on OSC 633 detection
|
|
80
|
+
* Called when shell integration is detected
|
|
81
|
+
*/
|
|
82
|
+
onShellIntegrationDetected = () => {
|
|
83
|
+
if (this.outputSuppressed) {
|
|
84
|
+
this.stopOutputSuppression(true) // Discard the integration command output
|
|
85
|
+
}
|
|
13
86
|
}
|
|
14
87
|
|
|
15
88
|
activate (terminal = this.term) {
|
|
@@ -43,11 +116,76 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
43
116
|
}
|
|
44
117
|
}
|
|
45
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Check if data contains OSC 633 shell integration sequences
|
|
121
|
+
* @param {string} str - Data string to check
|
|
122
|
+
* @returns {boolean} True if OSC 633 sequence detected
|
|
123
|
+
*/
|
|
124
|
+
checkForShellIntegration = (str) => {
|
|
125
|
+
// OSC 633 sequences: ESC]633;X where X is A, B, C, D, E, or P
|
|
126
|
+
// ESC is character code 27 (0x1b)
|
|
127
|
+
// Use includes with the actual characters to avoid lint warning
|
|
128
|
+
const ESC = String.fromCharCode(27)
|
|
129
|
+
return str.includes(ESC + ']633;')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Write directly to terminal, bypassing suppression check
|
|
134
|
+
* Used for flushing buffered data
|
|
135
|
+
*/
|
|
136
|
+
writeToTerminalDirect = (data) => {
|
|
137
|
+
const { term } = this
|
|
138
|
+
if (term.parent?.onZmodem) {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (typeof data === 'string') {
|
|
142
|
+
return term.write(data)
|
|
143
|
+
}
|
|
144
|
+
term?.write(data)
|
|
145
|
+
}
|
|
146
|
+
|
|
46
147
|
writeToTerminal = (data) => {
|
|
47
148
|
const { term } = this
|
|
48
149
|
if (term.parent?.onZmodem) {
|
|
49
150
|
return
|
|
50
151
|
}
|
|
152
|
+
|
|
153
|
+
// Track initial data arrival
|
|
154
|
+
if (!this.hasReceivedInitialData) {
|
|
155
|
+
this.hasReceivedInitialData = true
|
|
156
|
+
if (this.onInitialDataCallback) {
|
|
157
|
+
const callback = this.onInitialDataCallback
|
|
158
|
+
this.onInitialDataCallback = null
|
|
159
|
+
// Call after a micro-delay to ensure this data is written first
|
|
160
|
+
setTimeout(callback, 0)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check for shell integration in the data (only when suppressing)
|
|
165
|
+
if (this.outputSuppressed) {
|
|
166
|
+
let str = data
|
|
167
|
+
if (typeof data !== 'string') {
|
|
168
|
+
// Convert to string to check for OSC 633
|
|
169
|
+
const decoder = this.decoder || new TextDecoder('utf-8')
|
|
170
|
+
try {
|
|
171
|
+
str = decoder.decode(data instanceof ArrayBuffer ? data : new Uint8Array(data))
|
|
172
|
+
} catch (e) {
|
|
173
|
+
str = ''
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// If we detect OSC 633, shell integration is working
|
|
178
|
+
if (this.checkForShellIntegration(str)) {
|
|
179
|
+
this.onShellIntegrationDetected()
|
|
180
|
+
// Don't buffer this - just discard the integration output
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Buffer the data while suppressed
|
|
185
|
+
this.suppressedData.push(data)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
51
189
|
if (typeof data === 'string') {
|
|
52
190
|
return term.write(data)
|
|
53
191
|
}
|
|
@@ -62,31 +200,9 @@ export default class AttachAddonCustom extends AttachAddon {
|
|
|
62
200
|
const { term } = this
|
|
63
201
|
term?.parent?.notifyOnData()
|
|
64
202
|
const str = this.decoder.decode(data)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
} = term
|
|
69
|
-
const nss = str.split('\r')
|
|
70
|
-
const nnss = []
|
|
71
|
-
for (const str1 of nss) {
|
|
72
|
-
const ns = str1.trim()
|
|
73
|
-
if (cwdId) {
|
|
74
|
-
const cwdIdEscaped = regEscape(cwdId)
|
|
75
|
-
const dirRegex = new RegExp(`${cwdIdEscaped}([^\\n]+?)${cwdIdEscaped}`, 'g')
|
|
76
|
-
if (ns.match(dirRegex)) {
|
|
77
|
-
const cwd = dirRegex.exec(ns)[1].trim()
|
|
78
|
-
if (cwd === '~' || cwd === '%d' || cwd === '%/' || cwd === '$PWD') term.parent.setCwd('')
|
|
79
|
-
else term.parent.setCwd(cwd)
|
|
80
|
-
nnss.push(ns.replaceAll(dirRegex, ''))
|
|
81
|
-
} else nnss.push(str1)
|
|
82
|
-
} else {
|
|
83
|
-
nnss.push(str1)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
term.write(nnss.join('\r'))
|
|
87
|
-
} else {
|
|
88
|
-
term?.write(str)
|
|
89
|
-
}
|
|
203
|
+
// CWD tracking is now handled by shell integration automatically
|
|
204
|
+
// No need to parse PS1 markers
|
|
205
|
+
term?.write(str)
|
|
90
206
|
}
|
|
91
207
|
|
|
92
208
|
sendToServer = (data) => {
|
|
@@ -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
|
}
|