@electerm/electerm-react 3.9.5 → 3.9.15
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/sftp/list-table-ui.jsx +5 -1
- package/client/components/tabs/tab.jsx +38 -19
- package/client/components/tabs/tabs.styl +8 -17
- package/client/components/terminal/attach-addon-custom.js +7 -3
- package/client/components/terminal/terminal-command-dropdown.jsx +1 -1
- package/client/components/terminal/terminal.jsx +2 -0
- package/client/components/text-editor/edit-with-custom-editor.jsx +22 -3
- package/client/components/text-editor/text-editor.jsx +21 -0
- package/client/components/tree-list/tree-list-item.jsx +6 -12
- package/client/components/tree-list/tree-list-row.jsx +5 -0
- package/client/components/tree-list/tree-list-rows.js +3 -1
- package/client/store/mcp-handler.js +83 -10
- package/client/store/tab.js +14 -0
- package/package.json +1 -1
|
@@ -28,7 +28,11 @@ export default class FileListTable extends Component {
|
|
|
28
28
|
containerRef = createRef()
|
|
29
29
|
|
|
30
30
|
componentDidUpdate (prevProps) {
|
|
31
|
-
|
|
31
|
+
const prevList = prevProps.fileList
|
|
32
|
+
const nextList = this.props.fileList
|
|
33
|
+
const contentChanged = prevList.length !== nextList.length ||
|
|
34
|
+
prevList.some((f, i) => f.id !== nextList[i].id)
|
|
35
|
+
if (contentChanged) {
|
|
32
36
|
if (this.containerRef.current) {
|
|
33
37
|
this.containerRef.current.scrollTop = 0
|
|
34
38
|
}
|
|
@@ -8,7 +8,8 @@ import { refsTabs } from '../common/ref'
|
|
|
8
8
|
import {
|
|
9
9
|
CloseOutlined,
|
|
10
10
|
Loading3QuartersOutlined,
|
|
11
|
-
BorderlessTableOutlined
|
|
11
|
+
BorderlessTableOutlined,
|
|
12
|
+
LockOutlined
|
|
12
13
|
} from '@ant-design/icons'
|
|
13
14
|
import {
|
|
14
15
|
Tooltip,
|
|
@@ -33,7 +34,7 @@ class Tab extends Component {
|
|
|
33
34
|
constructor (props) {
|
|
34
35
|
super(props)
|
|
35
36
|
this.state = {
|
|
36
|
-
terminalOnData:
|
|
37
|
+
terminalOnData: ''
|
|
37
38
|
}
|
|
38
39
|
this.id = 'tab-' + this.props.tab.id
|
|
39
40
|
refsTabs.add(this.id, this)
|
|
@@ -48,19 +49,38 @@ class Tab extends Component {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
notifyOnData = () => {
|
|
52
|
+
if (this.state.terminalOnData === 'password') {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
51
55
|
if (this.timer) {
|
|
52
56
|
clearTimeout(this.timer)
|
|
53
57
|
this.timer = null
|
|
54
58
|
}
|
|
55
59
|
this.setState({
|
|
56
|
-
terminalOnData:
|
|
60
|
+
terminalOnData: 'feed'
|
|
57
61
|
})
|
|
58
62
|
this.timer = setTimeout(this.clearTerminalOnData, 4000)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
clearTerminalOnData = () => {
|
|
62
66
|
this.setState({
|
|
63
|
-
terminalOnData:
|
|
67
|
+
terminalOnData: ''
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
notifyPasswordPrompt = () => {
|
|
72
|
+
if (this.timer) {
|
|
73
|
+
clearTimeout(this.timer)
|
|
74
|
+
this.timer = null
|
|
75
|
+
}
|
|
76
|
+
this.setState({
|
|
77
|
+
terminalOnData: 'password'
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
clearPasswordPrompt = () => {
|
|
82
|
+
this.setState({
|
|
83
|
+
terminalOnData: ''
|
|
64
84
|
})
|
|
65
85
|
}
|
|
66
86
|
|
|
@@ -424,13 +444,7 @@ class Tab extends Component {
|
|
|
424
444
|
{
|
|
425
445
|
'tab-last': isLast
|
|
426
446
|
},
|
|
427
|
-
status
|
|
428
|
-
{
|
|
429
|
-
'is-terminal-active': terminalOnData
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
'is-transporting': isTransporting
|
|
433
|
-
}
|
|
447
|
+
status
|
|
434
448
|
)
|
|
435
449
|
const title = createName(tab)
|
|
436
450
|
let tooltipTitle = title
|
|
@@ -477,15 +491,19 @@ class Tab extends Component {
|
|
|
477
491
|
>
|
|
478
492
|
<Dropdown {...dropdownProps}>
|
|
479
493
|
<div
|
|
480
|
-
className='tab-title elli
|
|
494
|
+
className='tab-title elli'
|
|
481
495
|
onClick={this.handleClick}
|
|
482
496
|
onDoubleClick={this.handleDup}
|
|
483
497
|
>
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
498
|
+
{
|
|
499
|
+
status === 'error' && (
|
|
500
|
+
<Loading3QuartersOutlined
|
|
501
|
+
className='pointer tab-reload mg1r'
|
|
502
|
+
onClick={this.handleReloadTab}
|
|
503
|
+
title={e('reload')}
|
|
504
|
+
/>
|
|
505
|
+
)
|
|
506
|
+
}
|
|
489
507
|
<span className='tab-title'>
|
|
490
508
|
<span className='iblock mg1r tab-count' style={styleTag}>{tabCount}</span>
|
|
491
509
|
<span className='mg1r'>{title}</span>
|
|
@@ -493,8 +511,9 @@ class Tab extends Component {
|
|
|
493
511
|
</div>
|
|
494
512
|
</Dropdown>
|
|
495
513
|
<div className={'tab-status ' + status} />
|
|
496
|
-
<div className='tab-traffic' />
|
|
497
|
-
<BorderlessTableOutlined className='tab-terminal-feed' />
|
|
514
|
+
{isTransporting && <div className='tab-traffic' />}
|
|
515
|
+
{terminalOnData === 'feed' && <BorderlessTableOutlined className='tab-terminal-feed' />}
|
|
516
|
+
{terminalOnData === 'password' && <LockOutlined className='tab-terminal-feed password' />}
|
|
498
517
|
{
|
|
499
518
|
this.renderCloseIcon()
|
|
500
519
|
}
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
vertical-align middle
|
|
37
37
|
cursor pointer
|
|
38
38
|
position relative
|
|
39
|
+
padding 0 14px
|
|
39
40
|
min-width 100px
|
|
40
41
|
max-width 200px
|
|
41
42
|
line-height 36px
|
|
@@ -46,7 +47,6 @@
|
|
|
46
47
|
color var(--text-dark)
|
|
47
48
|
&.tab-last
|
|
48
49
|
margin-right 5px
|
|
49
|
-
.tab-reload
|
|
50
50
|
.tab-close
|
|
51
51
|
display none
|
|
52
52
|
&.active
|
|
@@ -68,10 +68,6 @@
|
|
|
68
68
|
width 1px
|
|
69
69
|
border 1px dashed var(--text-dark)
|
|
70
70
|
height 36px
|
|
71
|
-
&.error
|
|
72
|
-
.tab-reload
|
|
73
|
-
display inline-block
|
|
74
|
-
color var(--text-light)
|
|
75
71
|
@keyframes blink
|
|
76
72
|
0%
|
|
77
73
|
background-color #e0e0e0
|
|
@@ -96,30 +92,25 @@
|
|
|
96
92
|
background-color var(--error)
|
|
97
93
|
&.processing
|
|
98
94
|
background-color var(--primary)
|
|
99
|
-
.is-transporting .tab-traffic
|
|
100
|
-
display block
|
|
101
|
-
animation blink 2s infinite
|
|
102
|
-
/* Remove opacity animation, use background-color */
|
|
103
95
|
.tab-traffic
|
|
104
|
-
display none
|
|
105
96
|
left 10px
|
|
106
97
|
width 5px
|
|
107
98
|
border-radius 0
|
|
108
99
|
background-color var(--success)
|
|
109
|
-
.is-terminal-active .tab-terminal-feed
|
|
110
|
-
display block
|
|
111
100
|
animation blink 2s infinite
|
|
112
|
-
background-color transparent !important
|
|
113
|
-
/* Remove opacity animation, use background-color */
|
|
114
101
|
.tab-terminal-feed
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
width 12px
|
|
103
|
+
height 12px
|
|
117
104
|
border-radius 0
|
|
118
105
|
color var(--success)
|
|
119
|
-
font-size
|
|
106
|
+
font-size 12px
|
|
120
107
|
left 2px
|
|
121
108
|
top 24px
|
|
122
109
|
background none
|
|
110
|
+
background-color transparent !important
|
|
111
|
+
animation blink 2s infinite
|
|
112
|
+
&.password
|
|
113
|
+
color var(--warn)
|
|
123
114
|
.tab-close
|
|
124
115
|
position absolute
|
|
125
116
|
right 5px
|
|
@@ -223,6 +223,7 @@ export default class AttachAddonCustom {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
if (typeof data === 'string') {
|
|
226
|
+
term?.parent?.notifyOnData()
|
|
226
227
|
return term.write(data)
|
|
227
228
|
}
|
|
228
229
|
data = new Uint8Array(data)
|
|
@@ -242,13 +243,16 @@ export default class AttachAddonCustom {
|
|
|
242
243
|
sendToServer = (data) => {
|
|
243
244
|
this._lastInputTime = Date.now()
|
|
244
245
|
// Start echo detection when password prompt is suspected
|
|
245
|
-
if (this._passwordPromptDetected && !this._pendingEchoCheck && data !== '\r' && data !== '\n') {
|
|
246
|
+
if (this._passwordPromptDetected && !this._pendingEchoCheck && data !== '\r' && data !== '\n' && data !== '\x03') {
|
|
246
247
|
this._pendingEchoCheck = { char: data, time: Date.now() }
|
|
247
248
|
clearTimeout(this._echoCheckTimer)
|
|
248
249
|
this._echoCheckTimer = setTimeout(this._onEchoCheckTimeout, 200)
|
|
249
250
|
}
|
|
250
|
-
// Reset password state on Enter
|
|
251
|
-
if (data === '\r' || data === '\n') {
|
|
251
|
+
// Reset password state on Enter or Ctrl+C
|
|
252
|
+
if (data === '\r' || data === '\n' || data === '\x03') {
|
|
253
|
+
if (this._passwordPromptDetected) {
|
|
254
|
+
this.term?.parent?.onPasswordPromptCancelled?.()
|
|
255
|
+
}
|
|
252
256
|
this._passwordPromptDetected = false
|
|
253
257
|
this._lastOutputLine = ''
|
|
254
258
|
this._pendingEchoCheck = null
|
|
@@ -278,7 +278,7 @@ export default class TerminalCmdSuggestions extends Component {
|
|
|
278
278
|
id: uid(),
|
|
279
279
|
command: b.password,
|
|
280
280
|
type: 'PW',
|
|
281
|
-
hint: [b.username, b.host].filter(Boolean).join('@')
|
|
281
|
+
hint: [b.username, [b.host, b.port].filter(Boolean).join(':')].filter(Boolean).join('@')
|
|
282
282
|
})
|
|
283
283
|
}
|
|
284
284
|
}
|
|
@@ -830,6 +830,7 @@ class Term extends Component {
|
|
|
830
830
|
}
|
|
831
831
|
|
|
832
832
|
onPasswordPromptDetected = () => {
|
|
833
|
+
window.store.notifyTabPasswordPrompt(this.props.tab.id)
|
|
833
834
|
if (!this.props.config.showCmdSuggestions) {
|
|
834
835
|
return
|
|
835
836
|
}
|
|
@@ -842,6 +843,7 @@ class Term extends Component {
|
|
|
842
843
|
}
|
|
843
844
|
|
|
844
845
|
onPasswordPromptCancelled = () => {
|
|
846
|
+
window.store.clearTabPasswordPrompt(this.props.tab.id)
|
|
845
847
|
const suggestions = refsStatic.get('terminal-suggestions')
|
|
846
848
|
if (suggestions?.state?.passwordMode) {
|
|
847
849
|
suggestions.closeSuggestions()
|
|
@@ -6,18 +6,30 @@ import { useState } from 'react'
|
|
|
6
6
|
import { Button, Input, Space } from 'antd'
|
|
7
7
|
import { safeGetItem, safeSetItem } from '../../common/safe-local-storage.js'
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
export const CUSTOM_EDITOR_COMMAND_LS_KEY = 'customEditorCommand'
|
|
10
|
+
export const CUSTOM_EDITOR_AUTO_OPEN_LS_KEY = 'customEditorAutoOpen'
|
|
10
11
|
const e = window.translate
|
|
11
12
|
|
|
12
13
|
export default function EditWithCustomEditor ({ loading, editWithCustom }) {
|
|
13
14
|
const [editorCommand, setEditorCommand] = useState(
|
|
14
|
-
() => safeGetItem(
|
|
15
|
+
() => safeGetItem(CUSTOM_EDITOR_COMMAND_LS_KEY) || ''
|
|
15
16
|
)
|
|
17
|
+
const [autoOpen, setAutoOpen] = useState(
|
|
18
|
+
() => safeGetItem(CUSTOM_EDITOR_AUTO_OPEN_LS_KEY) === 'true'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const autoOpenLabel = e('autoOpen')
|
|
16
22
|
|
|
17
23
|
function handleChange (ev) {
|
|
18
24
|
const val = ev.target.value
|
|
19
25
|
setEditorCommand(val)
|
|
20
|
-
safeSetItem(
|
|
26
|
+
safeSetItem(CUSTOM_EDITOR_COMMAND_LS_KEY, val)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleToggleAutoOpen () {
|
|
30
|
+
const next = !autoOpen
|
|
31
|
+
setAutoOpen(next)
|
|
32
|
+
safeSetItem(CUSTOM_EDITOR_AUTO_OPEN_LS_KEY, String(next))
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
function handleClick () {
|
|
@@ -45,6 +57,13 @@ export default function EditWithCustomEditor ({ loading, editWithCustom }) {
|
|
|
45
57
|
onChange={handleChange}
|
|
46
58
|
disabled={loading}
|
|
47
59
|
/>
|
|
60
|
+
<Button
|
|
61
|
+
type={autoOpen ? 'primary' : 'default'}
|
|
62
|
+
disabled={loading}
|
|
63
|
+
onClick={handleToggleAutoOpen}
|
|
64
|
+
>
|
|
65
|
+
{autoOpenLabel}: {autoOpen ? 'On' : 'Off'}
|
|
66
|
+
</Button>
|
|
48
67
|
</Space.Compact>
|
|
49
68
|
)
|
|
50
69
|
}
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import { PureComponent } from 'react'
|
|
6
6
|
import TextEditorForm from './text-editor-form'
|
|
7
|
+
import {
|
|
8
|
+
CUSTOM_EDITOR_AUTO_OPEN_LS_KEY,
|
|
9
|
+
CUSTOM_EDITOR_COMMAND_LS_KEY
|
|
10
|
+
} from './edit-with-custom-editor'
|
|
7
11
|
import { Spin } from 'antd'
|
|
8
12
|
import Modal from '../common/modal'
|
|
9
13
|
import resolve from '../../common/resolve'
|
|
14
|
+
import { safeGetItem } from '../../common/safe-local-storage.js'
|
|
10
15
|
import { refsStatic, refs } from '../common/ref'
|
|
11
16
|
|
|
12
17
|
const e = window.translate
|
|
@@ -69,12 +74,28 @@ export default class TextEditor extends PureComponent {
|
|
|
69
74
|
return
|
|
70
75
|
}
|
|
71
76
|
const text = await fileRef.fetchEditorText(p, type)
|
|
77
|
+
const editorCommand = this.getAutoOpenCustomEditorCommand()
|
|
72
78
|
this.setStateProxy({
|
|
73
79
|
text,
|
|
74
80
|
loading: false
|
|
81
|
+
}, () => {
|
|
82
|
+
if (editorCommand) {
|
|
83
|
+
this.editWithCustom(editorCommand)
|
|
84
|
+
}
|
|
75
85
|
})
|
|
76
86
|
}
|
|
77
87
|
|
|
88
|
+
getAutoOpenCustomEditorCommand = () => {
|
|
89
|
+
if (window.et.isWebApp) {
|
|
90
|
+
return ''
|
|
91
|
+
}
|
|
92
|
+
const autoOpen = safeGetItem(CUSTOM_EDITOR_AUTO_OPEN_LS_KEY) === 'true'
|
|
93
|
+
if (!autoOpen) {
|
|
94
|
+
return ''
|
|
95
|
+
}
|
|
96
|
+
return safeGetItem(CUSTOM_EDITOR_COMMAND_LS_KEY).trim()
|
|
97
|
+
}
|
|
98
|
+
|
|
78
99
|
doSubmit = () => {
|
|
79
100
|
this.handleSubmit({
|
|
80
101
|
text: this.state.text
|
|
@@ -3,17 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { memo } from 'react'
|
|
6
|
-
import
|
|
6
|
+
import { createTitleTag } from '../../common/create-title'
|
|
7
7
|
import classnames from 'classnames'
|
|
8
8
|
import highlight from '../common/highlight'
|
|
9
9
|
import uid from '../../common/uid'
|
|
10
10
|
|
|
11
|
-
function getItemLabel (item, isGroup) {
|
|
12
|
-
return isGroup
|
|
13
|
-
? item?.title || ''
|
|
14
|
-
: createName(item)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
11
|
function areEqual (prevProps, nextProps) {
|
|
18
12
|
const prevSelected = prevProps.selectedItemId === prevProps.item.id
|
|
19
13
|
const nextSelected = nextProps.selectedItemId === nextProps.item.id
|
|
@@ -27,10 +21,10 @@ function areEqual (prevProps, nextProps) {
|
|
|
27
21
|
prevSelected === nextSelected &&
|
|
28
22
|
prevSearchSelected === nextSearchSelected &&
|
|
29
23
|
prevProps.item.id === nextProps.item.id &&
|
|
30
|
-
prevProps.
|
|
31
|
-
prevProps.
|
|
32
|
-
prevProps.
|
|
33
|
-
|
|
24
|
+
prevProps.itemLevel === nextProps.itemLevel &&
|
|
25
|
+
prevProps.itemColor === nextProps.itemColor &&
|
|
26
|
+
prevProps.itemDescription === nextProps.itemDescription &&
|
|
27
|
+
prevProps.itemLabel === nextProps.itemLabel
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
function TreeListItem (props) {
|
|
@@ -87,7 +81,7 @@ function TreeListItem (props) {
|
|
|
87
81
|
: null
|
|
88
82
|
const title = isGroup
|
|
89
83
|
? item.title
|
|
90
|
-
:
|
|
84
|
+
: props.itemLabel
|
|
91
85
|
const titleAll = title + (item.description ? ' - ' + item.description : '')
|
|
92
86
|
const titleHighlight = isGroup
|
|
93
87
|
? item.title || 'no title'
|
|
@@ -2,6 +2,7 @@ import TreeExpander from './tree-expander'
|
|
|
2
2
|
import TreeListItem from './tree-list-item'
|
|
3
3
|
import TreeItemOp from './tree-item-op'
|
|
4
4
|
import { treeLevelIndent } from './tree-list-layout'
|
|
5
|
+
import createName from '../../common/create-title'
|
|
5
6
|
|
|
6
7
|
export default function TreeListRow (props) {
|
|
7
8
|
const {
|
|
@@ -38,6 +39,10 @@ export default function TreeListRow (props) {
|
|
|
38
39
|
item,
|
|
39
40
|
isGroup,
|
|
40
41
|
parentId,
|
|
42
|
+
itemLabel: isGroup ? (item?.title || '') : createName(item),
|
|
43
|
+
itemColor: item?.color,
|
|
44
|
+
itemDescription: item?.description,
|
|
45
|
+
itemLevel: item?.level,
|
|
41
46
|
leftSidebarWidth,
|
|
42
47
|
staticList,
|
|
43
48
|
selectedItemId: activeItemId,
|
|
@@ -26,7 +26,9 @@ export function buildVisibleTreeRows ({
|
|
|
26
26
|
const item = bookmarksMap.get(bookmarkId)
|
|
27
27
|
const matched = Boolean(
|
|
28
28
|
item &&
|
|
29
|
-
(!lowerKeyword ||
|
|
29
|
+
(!lowerKeyword ||
|
|
30
|
+
createName(item).toLowerCase().includes(lowerKeyword) ||
|
|
31
|
+
(item.description || '').toLowerCase().includes(lowerKeyword))
|
|
30
32
|
)
|
|
31
33
|
bookmarkMatchCache.set(bookmarkId, matched)
|
|
32
34
|
return matched
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import uid from '../common/uid'
|
|
7
7
|
import { settingMap } from '../common/constants'
|
|
8
|
-
import { refs } from '../components/common/ref'
|
|
8
|
+
import { refs, refsTabs } from '../components/common/ref'
|
|
9
9
|
import deepCopy from 'json-deep-copy'
|
|
10
10
|
import {
|
|
11
11
|
getLocalFileInfo,
|
|
@@ -107,6 +107,9 @@ export default Store => {
|
|
|
107
107
|
case 'get_terminal_output':
|
|
108
108
|
result = store.mcpGetTerminalOutput(args)
|
|
109
109
|
break
|
|
110
|
+
case 'wait_for_terminal_idle':
|
|
111
|
+
result = await store.mcpWaitForTerminalIdle(args)
|
|
112
|
+
break
|
|
110
113
|
|
|
111
114
|
// SFTP operations
|
|
112
115
|
case 'sftp_list':
|
|
@@ -331,15 +334,18 @@ export default Store => {
|
|
|
331
334
|
|
|
332
335
|
Store.prototype.mcpListTabs = function () {
|
|
333
336
|
const { store } = window
|
|
334
|
-
return store.tabs.map(t =>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
337
|
+
return store.tabs.map(t => {
|
|
338
|
+
return {
|
|
339
|
+
id: t.id,
|
|
340
|
+
title: t.title,
|
|
341
|
+
host: t.host,
|
|
342
|
+
type: t.type || 'local',
|
|
343
|
+
status: t.status,
|
|
344
|
+
isTransporting: t.isTransporting,
|
|
345
|
+
onData: refsTabs.get('tab-' + t.id)?.state.terminalOnData,
|
|
346
|
+
batch: t.batch
|
|
347
|
+
}
|
|
348
|
+
})
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
Store.prototype.mcpGetActiveTab = function () {
|
|
@@ -527,6 +533,73 @@ export default Store => {
|
|
|
527
533
|
}
|
|
528
534
|
}
|
|
529
535
|
|
|
536
|
+
Store.prototype.mcpWaitForTerminalIdle = async function (args) {
|
|
537
|
+
const { store } = window
|
|
538
|
+
const tabId = args.tabId || store.activeTabId
|
|
539
|
+
const timeout = Math.min(args.timeout || 30000, 120000)
|
|
540
|
+
const pollInterval = 500
|
|
541
|
+
const minWait = args.minWait !== undefined ? args.minWait : 1000
|
|
542
|
+
const lineCountToFetch = args.lines || 50
|
|
543
|
+
|
|
544
|
+
if (!tabId) {
|
|
545
|
+
throw new Error('No active terminal')
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const start = Date.now()
|
|
549
|
+
|
|
550
|
+
// Brief initial wait so the command has time to start producing output
|
|
551
|
+
if (minWait > 0) {
|
|
552
|
+
await new Promise(resolve => setTimeout(resolve, minWait))
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const collectOutput = () => {
|
|
556
|
+
const term = refs.get('term-' + tabId)
|
|
557
|
+
if (!term || !term.term) return { output: '', lineCount: 0 }
|
|
558
|
+
const buffer = term.term.buffer.active
|
|
559
|
+
if (!buffer) return { output: '', lineCount: 0 }
|
|
560
|
+
const cursorY = buffer.cursorY || 0
|
|
561
|
+
const baseY = buffer.baseY || 0
|
|
562
|
+
const totalLines = buffer.length || 0
|
|
563
|
+
const actualContentEnd = baseY + cursorY + 1
|
|
564
|
+
const startLine = Math.max(0, actualContentEnd - lineCountToFetch)
|
|
565
|
+
const endLine = Math.min(totalLines, actualContentEnd)
|
|
566
|
+
const lines = []
|
|
567
|
+
for (let i = startLine; i < endLine; i++) {
|
|
568
|
+
const line = buffer.getLine(i)
|
|
569
|
+
if (line) lines.push(line.translateToString(true))
|
|
570
|
+
}
|
|
571
|
+
return { output: lines.join('\n'), lineCount: lines.length }
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Poll until onData becomes false (4s idle debounce in tab.jsx)
|
|
575
|
+
while (Date.now() - start < timeout) {
|
|
576
|
+
const tabRef = refsTabs.get('tab-' + tabId)
|
|
577
|
+
const onData = tabRef?.state.terminalOnData
|
|
578
|
+
if (!onData) {
|
|
579
|
+
const { output, lineCount } = collectOutput()
|
|
580
|
+
return {
|
|
581
|
+
tabId,
|
|
582
|
+
elapsed: Date.now() - start,
|
|
583
|
+
timedOut: false,
|
|
584
|
+
output,
|
|
585
|
+
lineCount
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Timeout reached — return whatever is currently in the buffer
|
|
592
|
+
const { output, lineCount } = collectOutput()
|
|
593
|
+
return {
|
|
594
|
+
tabId,
|
|
595
|
+
elapsed: Date.now() - start,
|
|
596
|
+
timedOut: true,
|
|
597
|
+
message: `Terminal still active after ${timeout}ms`,
|
|
598
|
+
output,
|
|
599
|
+
lineCount
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
530
603
|
// ==================== Settings APIs ====================
|
|
531
604
|
|
|
532
605
|
Store.prototype.mcpGetSettings = function () {
|
package/client/store/tab.js
CHANGED
|
@@ -622,6 +622,20 @@ export default Store => {
|
|
|
622
622
|
}
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
Store.prototype.notifyTabPasswordPrompt = function (tabId) {
|
|
626
|
+
const tab = refsTabs.get('tab-' + tabId)
|
|
627
|
+
if (tab) {
|
|
628
|
+
tab.notifyPasswordPrompt()
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
Store.prototype.clearTabPasswordPrompt = function (tabId) {
|
|
633
|
+
const tab = refsTabs.get('tab-' + tabId)
|
|
634
|
+
if (tab) {
|
|
635
|
+
tab.clearPasswordPrompt()
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
625
639
|
Store.prototype.remoteList = function (tabId) {
|
|
626
640
|
const sftp = refs.get('sftp-' + tabId)
|
|
627
641
|
if (sftp) {
|