@electerm/electerm-react 2.3.198 → 2.4.18
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 +156 -0
- package/client/components/common/message.styl +56 -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 +118 -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 +164 -0
- package/client/components/terminal/terminal-command-dropdown.jsx +3 -0
- package/client/components/terminal/terminal.jsx +276 -118
- 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
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
ReloadOutlined
|
|
9
9
|
} from '@ant-design/icons'
|
|
10
10
|
import {
|
|
11
|
-
notification,
|
|
12
11
|
Spin,
|
|
13
12
|
Button,
|
|
14
|
-
Dropdown
|
|
15
|
-
message,
|
|
16
|
-
Modal
|
|
13
|
+
Dropdown
|
|
17
14
|
} from 'antd'
|
|
15
|
+
import { notification } from '../common/notification'
|
|
16
|
+
import message from '../common/message'
|
|
17
|
+
import Modal from '../common/modal'
|
|
18
18
|
import classnames from 'classnames'
|
|
19
19
|
import './terminal.styl'
|
|
20
20
|
import {
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
isWin,
|
|
25
25
|
transferTypeMap,
|
|
26
26
|
rendererTypes,
|
|
27
|
-
cwdId,
|
|
28
27
|
isMac,
|
|
29
28
|
zmodemTransferPackSize
|
|
30
29
|
} from '../../common/constants.js'
|
|
@@ -51,27 +50,20 @@ import { getFilePath, isUnsafeFilename } from '../../common/file-drop-utils.js'
|
|
|
51
50
|
import { CommandTrackerAddon } from './command-tracker-addon.js'
|
|
52
51
|
import AIIcon from '../icons/ai-icon.jsx'
|
|
53
52
|
import { formatBytes } from '../../common/byte-format.js'
|
|
53
|
+
import {
|
|
54
|
+
getShellIntegrationCommand,
|
|
55
|
+
detectRemoteShell,
|
|
56
|
+
detectShellType
|
|
57
|
+
} from './shell.js'
|
|
54
58
|
import * as fs from './fs.js'
|
|
55
59
|
import iconsMap from '../sys-menu/icons-map.jsx'
|
|
56
60
|
import { refs, refsStatic } from '../common/ref.js'
|
|
61
|
+
import ExternalLink from '../common/external-link.jsx'
|
|
57
62
|
import createDefaultLogPath from '../../common/default-log-path.js'
|
|
58
63
|
import SearchResultBar from './terminal-search-bar'
|
|
59
64
|
|
|
60
65
|
const e = window.translate
|
|
61
66
|
|
|
62
|
-
const PS1_SETUP_CMD = `\recho $0|grep csh >/dev/null && set prompt_bak="$prompt" && set prompt="$prompt${cwdId}%/${cwdId}"\r
|
|
63
|
-
echo $0|grep zsh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'${cwdId}%d${cwdId}'\r
|
|
64
|
-
echo $0|grep ash >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
|
|
65
|
-
echo $0|grep ksh >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
|
|
66
|
-
echo $0|grep '^sh' >/dev/null && PS1_bak=$PS1&&PS1=$PS1'\`echo ${cwdId}$PWD${cwdId}\`'\r
|
|
67
|
-
clear\r`
|
|
68
|
-
|
|
69
|
-
const PS1_RESTORE_CMD = `\recho $0|grep csh >/dev/null && set prompt="$prompt_bak"\r
|
|
70
|
-
echo $0|grep zsh >/dev/null && PS1="$PS1_bak"\r
|
|
71
|
-
echo $0|grep ash >/dev/null && PS1="$PS1_bak"\r
|
|
72
|
-
echo $0|grep ksh >/dev/null && PS1="$PS1_bak"\r
|
|
73
|
-
echo $0|grep '^sh' >/dev/null && PS1="$PS1_bak"\r
|
|
74
|
-
clear\r`
|
|
75
67
|
class Term extends Component {
|
|
76
68
|
constructor (props) {
|
|
77
69
|
super(props)
|
|
@@ -88,6 +80,10 @@ class Term extends Component {
|
|
|
88
80
|
}
|
|
89
81
|
this.id = `term-${this.props.tab.id}`
|
|
90
82
|
refs.add(this.id, this)
|
|
83
|
+
this.currentInput = ''
|
|
84
|
+
this.shellInjected = false
|
|
85
|
+
this.shellType = null
|
|
86
|
+
this.manualCommandHistory = new Set()
|
|
91
87
|
}
|
|
92
88
|
|
|
93
89
|
domRef = createRef()
|
|
@@ -140,29 +136,6 @@ class Term extends Component {
|
|
|
140
136
|
background: 'rgba(0,0,0,0)'
|
|
141
137
|
}
|
|
142
138
|
}
|
|
143
|
-
|
|
144
|
-
const sftpPathFollowSshChanged = !isEqual(
|
|
145
|
-
this.props.sftpPathFollowSsh,
|
|
146
|
-
prevProps.sftpPathFollowSsh
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
if (sftpPathFollowSshChanged) {
|
|
150
|
-
if (this.props.sftpPathFollowSsh) {
|
|
151
|
-
if (this.attachAddon && this.term) {
|
|
152
|
-
this.attachAddon._sendData(PS1_SETUP_CMD)
|
|
153
|
-
this.term.cwdId = cwdId
|
|
154
|
-
} else {
|
|
155
|
-
console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in componentDidUpdate')
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
if (this.attachAddon) {
|
|
159
|
-
this.attachAddon._sendData(PS1_RESTORE_CMD)
|
|
160
|
-
}
|
|
161
|
-
if (this.term) {
|
|
162
|
-
delete this.term.cwdId
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
139
|
}
|
|
167
140
|
|
|
168
141
|
componentWillUnmount () {
|
|
@@ -247,6 +220,33 @@ class Term extends Component {
|
|
|
247
220
|
}
|
|
248
221
|
}
|
|
249
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
250
|
}
|
|
251
251
|
|
|
252
252
|
timers = {}
|
|
@@ -321,8 +321,7 @@ class Term extends Component {
|
|
|
321
321
|
),
|
|
322
322
|
okText: e('ok'),
|
|
323
323
|
cancelText: e('cancel'),
|
|
324
|
-
onOk: () => this.onPaste(true)
|
|
325
|
-
onCancel: Modal.destroyAll
|
|
324
|
+
onOk: () => this.onPaste(true)
|
|
326
325
|
})
|
|
327
326
|
}
|
|
328
327
|
|
|
@@ -382,7 +381,7 @@ class Term extends Component {
|
|
|
382
381
|
this.attachAddon._sendData(`"${filePath}" `)
|
|
383
382
|
return
|
|
384
383
|
} catch (e) {
|
|
385
|
-
|
|
384
|
+
console.error('Failed to parse fromFile data:', e)
|
|
386
385
|
}
|
|
387
386
|
}
|
|
388
387
|
|
|
@@ -441,12 +440,15 @@ class Term extends Component {
|
|
|
441
440
|
}
|
|
442
441
|
|
|
443
442
|
onzmodemRetract = () => {
|
|
444
|
-
|
|
443
|
+
console.debug('zmodemRetract')
|
|
445
444
|
}
|
|
446
445
|
|
|
447
446
|
writeBanner = (type) => {
|
|
448
|
-
|
|
449
|
-
this.term.write(`\
|
|
447
|
+
const border = '='.repeat(50)
|
|
448
|
+
this.term.write(`\r\n${border}\r\n`)
|
|
449
|
+
this.term.write('\x1b[33m\x1b[1mRecommend use trzsz instead: https://github.com/trzsz/trzsz\x1b[0m\r\n')
|
|
450
|
+
this.term.write(`${border}\r\n\r\n`)
|
|
451
|
+
this.term.write(`\x1b[32m\x1b[1mZMODEM::${type}::START\x1b[0m\r\n`)
|
|
450
452
|
}
|
|
451
453
|
|
|
452
454
|
onReceiveZmodemSession = async () => {
|
|
@@ -626,7 +628,7 @@ class Term extends Component {
|
|
|
626
628
|
}
|
|
627
629
|
const r = []
|
|
628
630
|
for (const filePath of files) {
|
|
629
|
-
const stat = await getLocalFileInfo(filePath)
|
|
631
|
+
const stat = await getLocalFileInfo(filePath)
|
|
630
632
|
r.push({ ...stat, filePath })
|
|
631
633
|
}
|
|
632
634
|
return r
|
|
@@ -885,20 +887,16 @@ class Term extends Component {
|
|
|
885
887
|
}
|
|
886
888
|
|
|
887
889
|
getCwd = () => {
|
|
888
|
-
if
|
|
889
|
-
|
|
890
|
-
this.
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
// It acts as a fallback.
|
|
895
|
-
this.term.cwdId = cwdId
|
|
896
|
-
if (this.attachAddon) {
|
|
897
|
-
this.attachAddon._sendData(PS1_SETUP_CMD)
|
|
898
|
-
} else {
|
|
899
|
-
console.warn('attachAddon not ready for PS1_SETUP_CMD in getCwd fallback')
|
|
890
|
+
// Use shell integration CWD if available
|
|
891
|
+
if (this.cmdAddon && this.cmdAddon.hasShellIntegration()) {
|
|
892
|
+
const cwd = this.cmdAddon.getCwd()
|
|
893
|
+
if (cwd) {
|
|
894
|
+
this.setCwd(cwd)
|
|
895
|
+
return cwd
|
|
900
896
|
}
|
|
901
897
|
}
|
|
898
|
+
// Fallback: no longer needed with shell integration
|
|
899
|
+
return ''
|
|
902
900
|
}
|
|
903
901
|
|
|
904
902
|
setCwd = (cwd) => {
|
|
@@ -942,35 +940,72 @@ class Term extends Component {
|
|
|
942
940
|
?.closeSuggestions()
|
|
943
941
|
}
|
|
944
942
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
943
|
+
openSuggestions = (cursorPos, data) => {
|
|
944
|
+
refsStatic
|
|
945
|
+
.get('terminal-suggestions')
|
|
946
|
+
?.openSuggestions(cursorPos, data)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
getCurrentInput = () => {
|
|
950
|
+
return this.currentInput
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
setCurrentInput = (value) => {
|
|
954
|
+
this.currentInput = value
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
updateCurrentInput = (d) => {
|
|
958
|
+
// Handle backspace (both \x7f and \b)
|
|
959
|
+
if (d === '\x7f' || d === '\b') {
|
|
960
|
+
this.currentInput = this.currentInput.slice(0, -1)
|
|
961
|
+
return
|
|
948
962
|
}
|
|
949
|
-
|
|
950
|
-
if (
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
963
|
+
// Handle Ctrl+U (clear line)
|
|
964
|
+
if (d === '\x15') {
|
|
965
|
+
this.currentInput = ''
|
|
966
|
+
return
|
|
967
|
+
}
|
|
968
|
+
// Handle Ctrl+W (delete word)
|
|
969
|
+
if (d === '\x17') {
|
|
970
|
+
this.currentInput = this.currentInput.replace(/\S*\s*$/, '')
|
|
971
|
+
return
|
|
972
|
+
}
|
|
973
|
+
// Handle Ctrl+C (cancel)
|
|
974
|
+
if (d === '\x03') {
|
|
975
|
+
this.currentInput = ''
|
|
976
|
+
return
|
|
977
|
+
}
|
|
978
|
+
// Handle Enter
|
|
979
|
+
if (d === '\r' || d === '\n') {
|
|
980
|
+
// Add to manual command history if shell integration is not available
|
|
981
|
+
if (this.currentInput.trim() && this.shouldUseManualHistory()) {
|
|
982
|
+
this.manualCommandHistory.add(this.currentInput.trim())
|
|
983
|
+
// Also add to global history for suggestions
|
|
984
|
+
window.store.addCmdHistory(this.currentInput.trim())
|
|
957
985
|
}
|
|
986
|
+
this.currentInput = ''
|
|
987
|
+
return
|
|
988
|
+
}
|
|
989
|
+
// Handle Escape and other control characters
|
|
990
|
+
if (d.charCodeAt(0) < 32 && d !== '\t') {
|
|
991
|
+
return
|
|
992
|
+
}
|
|
993
|
+
// Handle arrow keys and other escape sequences
|
|
994
|
+
if (d.startsWith('\x1b')) {
|
|
995
|
+
return
|
|
996
|
+
}
|
|
997
|
+
// Regular character input - append to buffer
|
|
998
|
+
this.currentInput += d
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
onData = (d) => {
|
|
1002
|
+
this.updateCurrentInput(d)
|
|
1003
|
+
const data = this.getCurrentInput()
|
|
1004
|
+
if (this.props.config.showCmdSuggestions && data) {
|
|
1005
|
+
const cursorPos = this.getCursorPosition()
|
|
1006
|
+
this.openSuggestions(cursorPos, data)
|
|
958
1007
|
} else {
|
|
959
1008
|
this.closeSuggestions()
|
|
960
|
-
if (this.term.buffer.active.type !== 'alternate') {
|
|
961
|
-
this.timers.getCwd = setTimeout(this.getCwd, 200)
|
|
962
|
-
}
|
|
963
|
-
const exitCmds = [
|
|
964
|
-
'exit',
|
|
965
|
-
'logout'
|
|
966
|
-
]
|
|
967
|
-
window.store.addCmdHistory(data)
|
|
968
|
-
if (exitCmds.includes(data)) {
|
|
969
|
-
this.userTypeExit = true
|
|
970
|
-
this.timers.userTypeExit = setTimeout(() => {
|
|
971
|
-
delete this.userTypeExit
|
|
972
|
-
}, 2000)
|
|
973
|
-
}
|
|
974
1009
|
}
|
|
975
1010
|
}
|
|
976
1011
|
|
|
@@ -981,8 +1016,8 @@ class Term extends Component {
|
|
|
981
1016
|
try {
|
|
982
1017
|
term.loadAddon(new WebglAddon())
|
|
983
1018
|
} catch (e) {
|
|
984
|
-
|
|
985
|
-
|
|
1019
|
+
console.error('render with webgl failed, fallback to canvas')
|
|
1020
|
+
console.error(e)
|
|
986
1021
|
term.loadAddon(new CanvasAddon())
|
|
987
1022
|
}
|
|
988
1023
|
}
|
|
@@ -1021,6 +1056,16 @@ class Term extends Component {
|
|
|
1021
1056
|
// term.on('keydown', this.handleEvent)
|
|
1022
1057
|
this.fitAddon = new FitAddon()
|
|
1023
1058
|
this.cmdAddon = new CommandTrackerAddon()
|
|
1059
|
+
// Register callback for shell integration command tracking
|
|
1060
|
+
this.cmdAddon.onCommandExecuted((cmd) => {
|
|
1061
|
+
if (cmd && cmd.trim()) {
|
|
1062
|
+
window.store.addCmdHistory(cmd.trim())
|
|
1063
|
+
}
|
|
1064
|
+
})
|
|
1065
|
+
// Register callback for shell integration CWD tracking
|
|
1066
|
+
this.cmdAddon.onCwdChanged((cwd) => {
|
|
1067
|
+
this.setCwd(cwd)
|
|
1068
|
+
})
|
|
1024
1069
|
this.searchAddon = new SearchAddon()
|
|
1025
1070
|
const ligtureAddon = new LigaturesAddon()
|
|
1026
1071
|
this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
|
|
@@ -1035,7 +1080,6 @@ class Term extends Component {
|
|
|
1035
1080
|
term.onData(this.onData)
|
|
1036
1081
|
this.term = term
|
|
1037
1082
|
term.onSelectionChange(this.onSelectionChange)
|
|
1038
|
-
term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
|
|
1039
1083
|
await this.remoteInit(term)
|
|
1040
1084
|
}
|
|
1041
1085
|
|
|
@@ -1054,49 +1098,163 @@ class Term extends Component {
|
|
|
1054
1098
|
// })
|
|
1055
1099
|
// }
|
|
1056
1100
|
|
|
1057
|
-
runInitScript = () => {
|
|
1101
|
+
runInitScript = async () => {
|
|
1058
1102
|
window.store.triggerResize()
|
|
1059
1103
|
const {
|
|
1060
1104
|
startDirectory,
|
|
1061
1105
|
runScripts
|
|
1062
1106
|
} = this.props.tab
|
|
1107
|
+
|
|
1108
|
+
const scripts = runScripts ? [...runScripts] : []
|
|
1063
1109
|
const startFolder = startDirectory || window.initFolder
|
|
1064
1110
|
if (startFolder) {
|
|
1065
|
-
|
|
1066
|
-
const cmd = `cd "${startFolder}"\r`
|
|
1067
|
-
this.attachAddon._sendData(cmd)
|
|
1068
|
-
} else {
|
|
1069
|
-
console.warn('attachAddon not ready for cd command in runInitScript')
|
|
1070
|
-
}
|
|
1111
|
+
scripts.unshift({ script: `cd "${startFolder}"`, delay: 0 })
|
|
1071
1112
|
}
|
|
1072
1113
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
this.attachAddon._sendData(PS1_SETUP_CMD)
|
|
1076
|
-
this.term.cwdId = cwdId
|
|
1077
|
-
} else {
|
|
1078
|
-
console.warn('Term or attachAddon not ready for PS1_SETUP_CMD in runInitScript')
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1114
|
+
// Create unified execution queue
|
|
1115
|
+
this.executionQueue = []
|
|
1081
1116
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
this.
|
|
1117
|
+
// Add shell integration injection to queue if needed
|
|
1118
|
+
if (this.canInjectShellIntegration()) {
|
|
1119
|
+
this.executionQueue.push({
|
|
1120
|
+
type: 'shell_integration',
|
|
1121
|
+
execute: async () => {
|
|
1122
|
+
await this.injectShellIntegration()
|
|
1123
|
+
}
|
|
1124
|
+
})
|
|
1085
1125
|
}
|
|
1126
|
+
|
|
1127
|
+
// Add delayed scripts to queue
|
|
1128
|
+
scripts.forEach(script => {
|
|
1129
|
+
this.executionQueue.push({
|
|
1130
|
+
type: 'delayed_script',
|
|
1131
|
+
script: script.script,
|
|
1132
|
+
delay: script.delay || 0,
|
|
1133
|
+
execute: () => {
|
|
1134
|
+
if (script.script) {
|
|
1135
|
+
this.attachAddon._sendData(script.script + '\r')
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
})
|
|
1139
|
+
})
|
|
1140
|
+
|
|
1141
|
+
this.processExecutionQueue()
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
shouldUseManualHistory = () => {
|
|
1145
|
+
const useManual = this.props.config.showCmdSuggestions &&
|
|
1146
|
+
(this.shellType === 'sh' || (isWin && this.isLocal()))
|
|
1147
|
+
return useManual
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
canInjectShellIntegration = () => {
|
|
1151
|
+
const { config } = this.props
|
|
1152
|
+
const canInject = (config.showCmdSuggestions || this.props.sftpPathFollowSsh) &&
|
|
1153
|
+
(
|
|
1154
|
+
this.isSsh() ||
|
|
1155
|
+
(this.isLocal() && !isWin)
|
|
1156
|
+
)
|
|
1157
|
+
return canInject
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
isSsh = () => {
|
|
1161
|
+
const { host, type } = this.props.tab
|
|
1162
|
+
return host && (type === 'ssh' || type === undefined)
|
|
1086
1163
|
}
|
|
1087
1164
|
|
|
1088
|
-
|
|
1089
|
-
const {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1165
|
+
isLocal = () => {
|
|
1166
|
+
const { host, type } = this.props.tab
|
|
1167
|
+
return !host &&
|
|
1168
|
+
(type === 'local' || type === undefined)
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* Process the unified execution queue one item at a time
|
|
1173
|
+
*/
|
|
1174
|
+
processExecutionQueue = async () => {
|
|
1175
|
+
if (!this.executionQueue || this.executionQueue.length === 0) {
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
const item = this.executionQueue.shift()
|
|
1180
|
+
|
|
1181
|
+
try {
|
|
1182
|
+
if (item.type === 'shell_integration') {
|
|
1183
|
+
await item.execute()
|
|
1184
|
+
} else if (item.type === 'delayed_script') {
|
|
1185
|
+
item.execute()
|
|
1186
|
+
// Wait for the specified delay before processing next item
|
|
1187
|
+
if (item.delay > 0) {
|
|
1188
|
+
await new Promise(resolve => {
|
|
1189
|
+
this.timers.timerDelay = setTimeout(resolve, item.delay)
|
|
1190
|
+
})
|
|
1191
|
+
}
|
|
1094
1192
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
console.error('[Shell Integration] Error processing queue item:', item.type, error)
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Process next item
|
|
1198
|
+
this.processExecutionQueue()
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Inject shell integration commands from client-side
|
|
1203
|
+
* This replaces the server-side source xxx.xxx approach
|
|
1204
|
+
* Uses output suppression to hide the injection command
|
|
1205
|
+
* Returns a promise that resolves when injection is complete
|
|
1206
|
+
*/
|
|
1207
|
+
injectShellIntegration = async () => {
|
|
1208
|
+
if (this.shellInjected) {
|
|
1209
|
+
return Promise.resolve()
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
let shellType
|
|
1213
|
+
if (this.isLocal()) {
|
|
1214
|
+
const { config } = this.props
|
|
1215
|
+
const localShell = isMac ? config.execMac : config.execLinux
|
|
1216
|
+
shellType = detectShellType(localShell)
|
|
1217
|
+
} else if (this.isSsh()) {
|
|
1218
|
+
shellType = await detectRemoteShell(this.pid)
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
this.shellType = shellType
|
|
1222
|
+
if (shellType === 'fish') {
|
|
1223
|
+
if (this.props.sftpPathFollowSsh) {
|
|
1224
|
+
message.warning(
|
|
1225
|
+
<span>
|
|
1226
|
+
Fish shell is not supported for SFTP follow SSH path. See: <ExternalLink to='https://github.com/electerm/electerm/wiki/Warning-about-sftp-follow-ssh-path-function'>wiki</ExternalLink>
|
|
1227
|
+
</span>
|
|
1228
|
+
, 7)
|
|
1098
1229
|
}
|
|
1230
|
+
return Promise.resolve()
|
|
1099
1231
|
}
|
|
1232
|
+
|
|
1233
|
+
// Don't inject for sh type shells unless sftpPathFollowSsh is true
|
|
1234
|
+
if (shellType === 'sh' && !this.props.sftpPathFollowSsh) {
|
|
1235
|
+
return Promise.resolve()
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const integrationCmd = getShellIntegrationCommand(shellType)
|
|
1239
|
+
|
|
1240
|
+
return new Promise((resolve) => {
|
|
1241
|
+
// Wait for initial data (prompt/banner) to arrive before injecting
|
|
1242
|
+
this.attachAddon.onInitialData(() => {
|
|
1243
|
+
if (this.attachAddon) {
|
|
1244
|
+
// Start suppressing output before sending the integration command
|
|
1245
|
+
// This hides the command and its output until OSC 633 is detected
|
|
1246
|
+
const suppressionTimeout = this.isSsh() ? 5000 : 3000
|
|
1247
|
+
// Pass callback to resolve the promise after suppression ends
|
|
1248
|
+
this.attachAddon.startOutputSuppression(suppressionTimeout, () => {
|
|
1249
|
+
this.shellInjected = true
|
|
1250
|
+
resolve()
|
|
1251
|
+
})
|
|
1252
|
+
this.attachAddon._sendData(integrationCmd)
|
|
1253
|
+
} else {
|
|
1254
|
+
resolve()
|
|
1255
|
+
}
|
|
1256
|
+
})
|
|
1257
|
+
})
|
|
1100
1258
|
}
|
|
1101
1259
|
|
|
1102
1260
|
setStatus = status => {
|
|
@@ -1292,13 +1450,13 @@ class Term extends Component {
|
|
|
1292
1450
|
try {
|
|
1293
1451
|
this.fitAddon.fit()
|
|
1294
1452
|
} catch (e) {
|
|
1295
|
-
|
|
1453
|
+
console.info('resize failed')
|
|
1296
1454
|
}
|
|
1297
1455
|
}
|
|
1298
1456
|
}, 200)
|
|
1299
1457
|
|
|
1300
1458
|
onerrorSocket = err => {
|
|
1301
|
-
|
|
1459
|
+
console.error('onerrorSocket', err)
|
|
1302
1460
|
}
|
|
1303
1461
|
|
|
1304
1462
|
oncloseSocket = () => {
|
|
@@ -1320,7 +1478,7 @@ class Term extends Component {
|
|
|
1320
1478
|
}
|
|
1321
1479
|
this.socketCloseWarning = notification.warning({
|
|
1322
1480
|
key,
|
|
1323
|
-
|
|
1481
|
+
message: e('socketCloseTip'),
|
|
1324
1482
|
duration: 30,
|
|
1325
1483
|
description: (
|
|
1326
1484
|
<div className='pd2y'>
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
ImportOutlined,
|
|
9
9
|
EditOutlined
|
|
10
10
|
} from '@ant-design/icons'
|
|
11
|
-
import { Upload, Button } from 'antd'
|
|
11
|
+
import { Upload, Button, Space } from 'antd'
|
|
12
12
|
|
|
13
13
|
const e = window.translate
|
|
14
14
|
|
|
@@ -32,16 +32,20 @@ export default class BookmarkTransport extends PureComponent {
|
|
|
32
32
|
)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
return
|
|
37
|
-
this.renderEdit(),
|
|
35
|
+
renderExport () {
|
|
36
|
+
return (
|
|
38
37
|
<Button
|
|
39
38
|
icon={<ExportOutlined />}
|
|
40
39
|
onClick={this.handleDownload}
|
|
41
40
|
title={e('export')}
|
|
42
41
|
className='download-bookmark-icon'
|
|
43
42
|
key='export'
|
|
44
|
-
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
renderImport () {
|
|
48
|
+
return (
|
|
45
49
|
<Upload
|
|
46
50
|
beforeUpload={this.beforeUpload}
|
|
47
51
|
fileList={[]}
|
|
@@ -53,6 +57,24 @@ export default class BookmarkTransport extends PureComponent {
|
|
|
53
57
|
title={e('importFromFile')}
|
|
54
58
|
/>
|
|
55
59
|
</Upload>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
render () {
|
|
64
|
+
const edit = this.renderEdit()
|
|
65
|
+
console.log('edit', edit)
|
|
66
|
+
if (edit === null) {
|
|
67
|
+
return (
|
|
68
|
+
<Space.Compact>
|
|
69
|
+
{this.renderExport()}
|
|
70
|
+
{this.renderImport()}
|
|
71
|
+
</Space.Compact>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
return [
|
|
75
|
+
edit,
|
|
76
|
+
this.renderExport(),
|
|
77
|
+
this.renderImport()
|
|
56
78
|
]
|
|
57
79
|
}
|
|
58
80
|
}
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
} from '../../common/constants'
|
|
10
10
|
import {
|
|
11
11
|
Spin,
|
|
12
|
-
message,
|
|
13
12
|
Tag
|
|
14
13
|
} from 'antd'
|
|
14
|
+
import message from '../common/message'
|
|
15
15
|
import Modal from '../common/modal'
|
|
16
16
|
import * as ls from '../../common/safe-local-storage'
|
|
17
17
|
import { copy } from '../../common/clipboard'
|
package/client/store/common.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import handleError from '../common/error-handler'
|
|
6
|
-
import
|
|
6
|
+
import Modal from '../components/common/modal'
|
|
7
7
|
import { debounce, some, get, pickBy } from 'lodash-es'
|
|
8
8
|
import {
|
|
9
9
|
modals,
|
|
@@ -117,7 +117,7 @@ export default Store => {
|
|
|
117
117
|
setTimeout(queue, 3000)
|
|
118
118
|
setTimeout(run, 4000)
|
|
119
119
|
} catch (e) {
|
|
120
|
-
|
|
120
|
+
console.error(e)
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -341,6 +341,9 @@ export default Store => {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
Store.prototype.addCmdHistory = action(function (cmd) {
|
|
344
|
+
if (!cmd || !cmd.trim()) {
|
|
345
|
+
return
|
|
346
|
+
}
|
|
344
347
|
const { terminalCommandHistory } = window.store
|
|
345
348
|
terminalCommandHistory.add(cmd)
|
|
346
349
|
if (terminalCommandHistory.size > 100) {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
infoTabs,
|
|
10
10
|
openedSidebarKey,
|
|
11
11
|
sidebarPinnedKey,
|
|
12
|
+
pinnedQuickCommandBarKey,
|
|
12
13
|
sftpDefaultSortSettingKey,
|
|
13
14
|
batchInputLsKey,
|
|
14
15
|
expandedKeysLsKey,
|
|
@@ -150,7 +151,7 @@ export default () => {
|
|
|
150
151
|
quickCommands: [],
|
|
151
152
|
quickCommandId: '',
|
|
152
153
|
openQuickCommandBar: false,
|
|
153
|
-
pinnedQuickCommandBar:
|
|
154
|
+
pinnedQuickCommandBar: ls.getItem(pinnedQuickCommandBarKey) === 'y',
|
|
154
155
|
qmSortByFrequency: ls.getItem(qmSortByFrequencyKey) === 'yes',
|
|
155
156
|
|
|
156
157
|
// sidebar
|