@electerm/electerm-react 5.3.15 → 5.5.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/README.md +246 -12
- package/client/common/choose-save-folder.js +21 -2
- package/client/common/constants.js +23 -6
- package/client/common/create-title.jsx +6 -0
- package/client/common/default-setting.js +14 -0
- package/client/common/init-setting-item.js +5 -0
- package/client/common/platform.js +24 -0
- package/client/components/ai/agent-tool-call-card.jsx +10 -2
- package/client/components/ai/agent.js +4 -1
- package/client/components/ai/ai-attachments.js +87 -0
- package/client/components/ai/ai-chat-empty.jsx +43 -0
- package/client/components/ai/ai-chat-entry.jsx +13 -4
- package/client/components/ai/ai-chat-history-item.jsx +28 -4
- package/client/components/ai/ai-chat-history.jsx +2 -1
- package/client/components/ai/ai-chat.jsx +104 -13
- package/client/components/ai/ai-config-props.js +4 -130
- package/client/components/ai/ai-config.jsx +135 -15
- package/client/components/ai/ai-presets.js +173 -0
- package/client/components/ai/ai.styl +83 -0
- package/client/components/bg/css-overwrite.jsx +11 -0
- package/client/components/bookmark-form/bookmark-schema.js +4 -0
- package/client/components/bookmark-form/common/fields.jsx +3 -0
- package/client/components/bookmark-form/config/common-fields.js +12 -0
- package/client/components/bookmark-form/config/local.js +3 -2
- package/client/components/bookmark-form/config/serial.js +3 -2
- package/client/components/bookmark-form/config/ssh.js +2 -1
- package/client/components/bookmark-form/config/telnet.js +3 -2
- package/client/components/bookmark-form/index.jsx +10 -8
- package/client/components/common/item-filter.jsx +76 -0
- package/client/components/common/item-filter.styl +54 -0
- package/client/components/common/lazy-boundary.jsx +34 -0
- package/client/components/common/opacity.jsx +3 -1
- package/client/components/footer/footer-entry.jsx +35 -5
- package/client/components/footer/footer.styl +8 -1
- package/client/components/layout/layout.jsx +23 -31
- package/client/components/layout/pixed.js +3 -0
- package/client/components/main/main.jsx +80 -28
- package/client/components/main/term-fullscreen.styl +9 -3
- package/client/components/quick-commands/qm.styl +3 -2
- package/client/components/rdp/file-transfer.js +4 -4
- package/client/components/remote-monitor/monitor-details.jsx +458 -0
- package/client/components/remote-monitor/monitor-details.styl +87 -0
- package/client/components/remote-monitor/monitor-model.js +764 -0
- package/client/components/remote-monitor/remote-monitor-bar-entry.jsx +18 -0
- package/client/components/remote-monitor/remote-monitor-bar.jsx +344 -0
- package/client/components/remote-monitor/remote-monitor-bar.styl +106 -0
- package/client/components/remote-monitor/session-monitor.js +361 -0
- package/client/components/remote-monitor/use-monitor-details.js +10 -0
- package/client/components/remote-monitor/visibility.js +18 -0
- package/client/components/session/session-control.jsx +29 -2
- package/client/components/session/session.jsx +40 -18
- package/client/components/session/sessions.jsx +5 -2
- package/client/components/setting-panel/col.jsx +32 -6
- package/client/components/setting-panel/list.styl +12 -0
- package/client/components/setting-panel/setting-common.jsx +25 -56
- package/client/components/setting-panel/setting-mobile-breadcrumb.jsx +29 -0
- package/client/components/setting-panel/setting-modal.jsx +64 -44
- package/client/components/setting-panel/setting-terminal.jsx +7 -2
- package/client/components/setting-panel/setting-wrap.jsx +0 -3
- package/client/components/setting-panel/setting-wrap.styl +80 -11
- package/client/components/setting-panel/setting.styl +1 -1
- package/client/components/setting-panel/tab-triggers.jsx +71 -0
- package/client/components/sftp/file-compare-modal.jsx +6 -3
- package/client/components/sftp/file-item.jsx +5 -3
- package/client/components/sftp/owner-list.js +11 -2
- package/client/components/side-panel-r/side-panel-r.jsx +2 -2
- package/client/components/sidebar/index.jsx +28 -21
- package/client/components/sidebar/left-sidebar-icons.jsx +1 -1
- package/client/components/sidebar/sidebar.styl +16 -0
- package/client/components/sys-menu/menu-btn.jsx +30 -1
- package/client/components/tabs/index.jsx +2 -2
- package/client/components/tabs/tab-title.jsx +3 -2
- package/client/components/tabs/tab.jsx +5 -1
- package/client/components/tabs/tabs.styl +4 -0
- package/client/components/terminal/attach-addon-custom.js +295 -49
- package/client/components/terminal/automation/index.js +64 -0
- package/client/components/terminal/automation/keys.js +49 -0
- package/client/components/terminal/automation/stream-matcher.js +82 -0
- package/client/components/terminal/automation/strip-ansi.js +19 -0
- package/client/components/terminal/automation/trigger-engine.js +233 -0
- package/client/components/terminal/automation/trigger-presets.js +110 -0
- package/client/components/terminal/command-tracker-addon.js +31 -1
- package/client/components/terminal/mixins/index.js +27 -0
- package/client/components/terminal/mixins/term-attach.js +123 -0
- package/client/components/terminal/mixins/term-config.jsx +84 -0
- package/client/components/terminal/mixins/term-content.js +88 -0
- package/client/components/terminal/mixins/term-context-menu.jsx +291 -0
- package/client/components/terminal/mixins/term-file-drop.js +149 -0
- package/client/components/terminal/mixins/term-init.js +122 -0
- package/client/components/terminal/mixins/term-log.jsx +96 -0
- package/client/components/terminal/mixins/term-resize.js +87 -0
- package/client/components/terminal/mixins/term-socket.js +433 -0
- package/client/components/terminal/mixins/term-suggestions.js +224 -0
- package/client/components/terminal/mixins/term-theme.js +190 -0
- package/client/components/terminal/mixins/term-touch.js +136 -0
- package/client/components/terminal/shell-detect.js +37 -0
- package/client/components/terminal/shell.js +0 -28
- package/client/components/terminal/shortcut-bar-entry.jsx +32 -27
- package/client/components/terminal/shortcut-bar.jsx +4 -6
- package/client/components/terminal/shortcut-bar.styl +4 -0
- package/client/components/terminal/ssh-reload-state.js +56 -0
- package/client/components/terminal/startup-queue.js +379 -0
- package/client/components/terminal/terminal-interactive.jsx +32 -0
- package/client/components/terminal/terminal.jsx +102 -2007
- package/client/components/terminal/transfer-client-base.js +5 -29
- package/client/components/terminal/trzsz-client.js +1 -3
- package/client/components/terminal/xmodem-client.js +1 -3
- package/client/components/terminal/xterm-loader.js +7 -0
- package/client/components/terminal/zmodem-client.js +1 -3
- package/client/components/terminal-info/base.jsx +18 -70
- package/client/components/terminal-info/terminal-info-entry.jsx +13 -5
- package/client/components/terminal-info/terminal-info.jsx +50 -59
- package/client/components/terminal-info/terminal-info.styl +6 -49
- package/client/components/text-editor/open-text-editor.js +30 -0
- package/client/components/text-editor/text-editor-entry.jsx +17 -6
- package/client/components/text-editor/text-editor.jsx +14 -0
- package/client/components/theme/theme-form.jsx +22 -0
- package/client/components/tree-list/bookmark-transport.jsx +7 -31
- package/client/components/tree-list/tree-list.styl +13 -0
- package/client/components/triggers/trigger-editor.jsx +246 -0
- package/client/components/triggers/trigger-form.jsx +200 -0
- package/client/components/triggers/trigger-session-modal.jsx +199 -0
- package/client/components/triggers/trigger-setting-list.jsx +89 -0
- package/client/components/triggers/trigger-transport.jsx +60 -0
- package/client/components/triggers/triggers-field.jsx +19 -0
- package/client/components/widgets/widgets-list.jsx +11 -1
- package/client/css/mobile.styl +9 -1
- package/client/store/common.js +12 -1
- package/client/store/init-state.js +14 -0
- package/client/store/mcp-handler.js +158 -30
- package/client/store/setting.js +20 -0
- package/client/store/sidebar.js +12 -0
- package/client/store/store.js +2 -0
- package/client/store/sync.js +45 -9
- package/client/store/system-menu.js +2 -0
- package/client/store/tab.js +41 -3
- package/client/store/trigger.js +118 -0
- package/client/store/watch.js +13 -5
- package/client/views/index.pug +3 -0
- package/package.json +1 -1
- package/client/components/terminal-info/activity.jsx +0 -57
- package/client/components/terminal-info/data-cols-parser.jsx +0 -54
- package/client/components/terminal-info/disk.jsx +0 -45
- package/client/components/terminal-info/format-disks.js +0 -45
- package/client/components/terminal-info/network.jsx +0 -121
- package/client/components/terminal-info/resource.jsx +0 -99
- package/client/components/terminal-info/run-cmd.jsx +0 -263
- package/client/components/terminal-info/sys-info.jsx +0 -56
- package/client/components/terminal-info/up.jsx +0 -17
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading back what is on screen: search (xterm search addon + the result
|
|
3
|
+
* bar), the normal-buffer overlay used when an app owns the alt buffer, and
|
|
4
|
+
* plain text export of the current buffer.
|
|
5
|
+
*/
|
|
6
|
+
export const contentMixin = {
|
|
7
|
+
toggleSearch () {
|
|
8
|
+
window.store.toggleTerminalSearch()
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
onSearchResultsChange ({ resultIndex, resultCount }) {
|
|
12
|
+
window.store.storeAssign({
|
|
13
|
+
termSearchMatchCount: resultCount,
|
|
14
|
+
termSearchMatchIndex: resultIndex
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
this.updateSearchResults(resultIndex)
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
updateSearchResults (resultIndex) {
|
|
21
|
+
const matches = this.searchAddon._resultTracker.searchResults.map((result, i) => {
|
|
22
|
+
return result.row
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
this.setState({
|
|
26
|
+
searchResults: matches,
|
|
27
|
+
matchIndex: resultIndex,
|
|
28
|
+
totalLines: this.term.buffer.active.length
|
|
29
|
+
})
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
searchPrev (searchInput, options) {
|
|
33
|
+
this.searchAddon.findPrevious(
|
|
34
|
+
searchInput, options
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
searchNext (searchInput, options) {
|
|
39
|
+
this.searchAddon.findNext(
|
|
40
|
+
searchInput, options
|
|
41
|
+
)
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
openNormalBuffer () {
|
|
45
|
+
const normal = this.term.buffer.normal
|
|
46
|
+
const len = normal.length
|
|
47
|
+
const lines = new Array(len).fill('').map((x, i) => {
|
|
48
|
+
return normal.getLine(i).translateToString(false)
|
|
49
|
+
})
|
|
50
|
+
this.setState({
|
|
51
|
+
lines
|
|
52
|
+
})
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
closeNormalBuffer () {
|
|
56
|
+
this.setState({
|
|
57
|
+
lines: []
|
|
58
|
+
})
|
|
59
|
+
this.term.focus()
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
onBufferChange (buf) {
|
|
63
|
+
this.bufferMode = buf.type
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
getTerminalBufferText () {
|
|
67
|
+
const { addTimeStampToTermLog } = this.state
|
|
68
|
+
const buffer = this.term.buffer.active
|
|
69
|
+
const len = buffer.length
|
|
70
|
+
const rawLines = []
|
|
71
|
+
for (let i = 0; i < len; i++) {
|
|
72
|
+
const line = buffer.getLine(i)
|
|
73
|
+
rawLines.push(line ? line.translateToString(false) : '')
|
|
74
|
+
}
|
|
75
|
+
// trim trailing blank lines before applying timestamps
|
|
76
|
+
while (rawLines.length && !rawLines[rawLines.length - 1].trim()) {
|
|
77
|
+
rawLines.pop()
|
|
78
|
+
}
|
|
79
|
+
if (!addTimeStampToTermLog) {
|
|
80
|
+
return rawLines.join('\n')
|
|
81
|
+
}
|
|
82
|
+
return rawLines.map(text => {
|
|
83
|
+
const now = new Date()
|
|
84
|
+
const ts = `[${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}.${String(now.getMilliseconds()).padStart(3, '0')}] `
|
|
85
|
+
return ts + text
|
|
86
|
+
}).join('\n')
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import Modal from '../../common/modal'
|
|
2
|
+
import { readClipboardAsync, readClipboard, copy } from '../../../common/clipboard.js'
|
|
3
|
+
import { isWin, isMac, isMacJs, connectionMap } from '../../../common/constants.js'
|
|
4
|
+
import iconsMap from '../../sys-menu/icons-map.jsx'
|
|
5
|
+
import { refsStatic } from '../../common/ref.js'
|
|
6
|
+
import AIIcon from '../../icons/ai-icon.jsx'
|
|
7
|
+
import { isAIDisabled } from '../../../common/ai-feature.js'
|
|
8
|
+
|
|
9
|
+
const e = window.translate
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The right-click (and long-press) menu plus the clipboard actions behind it.
|
|
13
|
+
* Menu item `key`s are method names, so `onContextMenu` just dispatches.
|
|
14
|
+
*/
|
|
15
|
+
export const contextMenuMixin = {
|
|
16
|
+
renderContextMenu () {
|
|
17
|
+
const { hasSelection, recording } = this.state
|
|
18
|
+
const copyed = true
|
|
19
|
+
const copyShortcut = this.getShortcut('terminal_copy')
|
|
20
|
+
const pasteShortcut = this.getShortcut('terminal_paste')
|
|
21
|
+
const clearShortcut = this.getShortcut('terminal_clear')
|
|
22
|
+
const searchShortcut = this.getShortcut('terminal_search')
|
|
23
|
+
const selectAllShortcut = isMacJs ? 'meta+a' : 'ctrl+shift+a'
|
|
24
|
+
const isSerial = this.props.tab?.type === connectionMap.serial
|
|
25
|
+
const items = [
|
|
26
|
+
{
|
|
27
|
+
key: 'onCopy',
|
|
28
|
+
icon: <iconsMap.CopyOutlined />,
|
|
29
|
+
label: e('copy'),
|
|
30
|
+
disabled: !hasSelection,
|
|
31
|
+
extra: copyShortcut
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
key: 'onPaste',
|
|
35
|
+
icon: <iconsMap.SwitcherOutlined />,
|
|
36
|
+
label: e('paste'),
|
|
37
|
+
disabled: !copyed,
|
|
38
|
+
extra: pasteShortcut
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
key: 'onPasteSelected',
|
|
42
|
+
icon: <iconsMap.SwitcherOutlined />,
|
|
43
|
+
label: e('pasteSelected'),
|
|
44
|
+
disabled: !hasSelection
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
|
|
48
|
+
key: 'onSelectAll',
|
|
49
|
+
icon: <iconsMap.CheckSquareOutlined />,
|
|
50
|
+
label: e('selectall'),
|
|
51
|
+
extra: selectAllShortcut
|
|
52
|
+
},
|
|
53
|
+
...(
|
|
54
|
+
isAIDisabled()
|
|
55
|
+
? []
|
|
56
|
+
: [{
|
|
57
|
+
key: 'explainWithAi',
|
|
58
|
+
icon: <AIIcon />,
|
|
59
|
+
label: e('explainWithAi'),
|
|
60
|
+
disabled: !hasSelection
|
|
61
|
+
}]
|
|
62
|
+
),
|
|
63
|
+
{
|
|
64
|
+
key: 'onClear',
|
|
65
|
+
icon: <iconsMap.ReloadOutlined />,
|
|
66
|
+
label: e('clear'),
|
|
67
|
+
extra: clearShortcut
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: 'toggleSearch',
|
|
71
|
+
icon: <iconsMap.SearchOutlined />,
|
|
72
|
+
label: e('search'),
|
|
73
|
+
extra: searchShortcut
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: 'onSaveTerminalLog',
|
|
77
|
+
icon: <iconsMap.SaveOutlined />,
|
|
78
|
+
label: e('saveTerminalLogToFile')
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: recording ? 'onStopRecord' : 'onRecord',
|
|
82
|
+
icon: recording ? <iconsMap.StopOutlined /> : <iconsMap.PlayCircleFilled />,
|
|
83
|
+
label: e(recording ? 'stopRecord' : 'record')
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
if (isSerial) {
|
|
87
|
+
items.push(
|
|
88
|
+
{
|
|
89
|
+
type: 'divider'
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: 'onXmodemSend',
|
|
93
|
+
icon: <iconsMap.CloudUploadOutlined />,
|
|
94
|
+
label: 'XMODEM Send'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
key: 'onXmodemReceive',
|
|
98
|
+
icon: <iconsMap.CloudDownloadOutlined />,
|
|
99
|
+
label: 'XMODEM Receive'
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
return items
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
onContextMenu ({ key }) {
|
|
107
|
+
this[key]()
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
onContextMenuInner (e) {
|
|
111
|
+
e.preventDefault()
|
|
112
|
+
if (this.state.loading) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (this.props.config.pasteWhenContextMenu) {
|
|
116
|
+
return this.onPaste()
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
onSelection () {
|
|
121
|
+
if (
|
|
122
|
+
!this.props.config.copyWhenSelect ||
|
|
123
|
+
window.store.onOperation
|
|
124
|
+
) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
this.copySelectionToClipboard()
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
copySelectionToClipboard () {
|
|
131
|
+
const txt = this.term.getSelection()
|
|
132
|
+
if (txt) {
|
|
133
|
+
copy(txt)
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
tryInsertSelected () {
|
|
138
|
+
const txt = this.term.getSelection()
|
|
139
|
+
if (txt) {
|
|
140
|
+
this.attachAddon._sendData(txt)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
onCopy () {
|
|
145
|
+
const selected = this.term.getSelection()
|
|
146
|
+
copy(selected)
|
|
147
|
+
this.term.focus()
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
onSelectAll () {
|
|
151
|
+
this.term.selectAll()
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
pasteTextTooLong () {
|
|
155
|
+
if (window.et.isWebApp) {
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
const text = readClipboard()
|
|
159
|
+
return text.length > 500
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
askUserConfirm () {
|
|
163
|
+
Modal.confirm({
|
|
164
|
+
title: e('paste'),
|
|
165
|
+
content: (
|
|
166
|
+
<div>
|
|
167
|
+
<p>{e('paste')}:</p>
|
|
168
|
+
<div className='paste-text'>
|
|
169
|
+
<pre>
|
|
170
|
+
<code>{readClipboard()}</code>
|
|
171
|
+
</pre>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
),
|
|
175
|
+
okText: e('ok'),
|
|
176
|
+
cancelText: e('cancel'),
|
|
177
|
+
onOk: () => this.onPaste(true)
|
|
178
|
+
})
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
async onPaste (skipTextLengthCheck) {
|
|
182
|
+
let selected = await readClipboardAsync()
|
|
183
|
+
if (!skipTextLengthCheck && selected.length > 500) {
|
|
184
|
+
return this.askUserConfirm()
|
|
185
|
+
}
|
|
186
|
+
if (isWin && this.isRemote()) {
|
|
187
|
+
selected = selected.replace(/\r\n/g, '\n')
|
|
188
|
+
}
|
|
189
|
+
this.term.paste(selected || '')
|
|
190
|
+
this.term.focus()
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
onPasteSelected () {
|
|
194
|
+
const selected = this.term.getSelection()
|
|
195
|
+
this.term.paste(selected || '')
|
|
196
|
+
this.term.focus()
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
onClear () {
|
|
200
|
+
const shouldClear = this.searchAddon &&
|
|
201
|
+
window.store.termSearchOpen &&
|
|
202
|
+
window.store.termSearch
|
|
203
|
+
if (
|
|
204
|
+
shouldClear
|
|
205
|
+
) {
|
|
206
|
+
this.searchAddon.clearDecorations()
|
|
207
|
+
}
|
|
208
|
+
this.term.clear()
|
|
209
|
+
this.term.focus()
|
|
210
|
+
if (shouldClear) {
|
|
211
|
+
this.searchAddon._lineCache.clear()
|
|
212
|
+
this.timers.clearSearchTimer = setTimeout(() => {
|
|
213
|
+
refsStatic.get('term-search')?.next()
|
|
214
|
+
}, 100)
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
onXmodemSend () {
|
|
219
|
+
if (this.xmodemClient) {
|
|
220
|
+
this.xmodemClient.initiateSend()
|
|
221
|
+
}
|
|
222
|
+
this.term.focus()
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
onXmodemReceive () {
|
|
226
|
+
if (this.xmodemClient) {
|
|
227
|
+
this.xmodemClient.initiateReceive()
|
|
228
|
+
}
|
|
229
|
+
this.term.focus()
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
explainWithAi () {
|
|
233
|
+
window.store.explainWithAi(
|
|
234
|
+
this.term.getSelection()
|
|
235
|
+
)
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
// ---- shortcut handlers, wired up by shortcutExtend ----
|
|
239
|
+
clearShortcut (e) {
|
|
240
|
+
e.stopPropagation()
|
|
241
|
+
this.onClear()
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// selectAllShortcut = (e) => {
|
|
245
|
+
// e.stopPropagation()
|
|
246
|
+
// this.term.selectAll()
|
|
247
|
+
// }
|
|
248
|
+
|
|
249
|
+
copyShortcut (e) {
|
|
250
|
+
const sel = this.term.getSelection()
|
|
251
|
+
if (sel) {
|
|
252
|
+
e.stopPropagation()
|
|
253
|
+
this.copySelectionToClipboard()
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
pasteSelectedShortcut (e) {
|
|
259
|
+
e.stopPropagation()
|
|
260
|
+
this.tryInsertSelected()
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
searchShortcut (e) {
|
|
264
|
+
e.stopPropagation()
|
|
265
|
+
this.toggleSearch()
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
pasteShortcut (e) {
|
|
269
|
+
if (this.pasteTextTooLong()) {
|
|
270
|
+
this.askUserConfirm()
|
|
271
|
+
e.preventDefault()
|
|
272
|
+
e.stopPropagation()
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
if (isMac) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
if (!this.isRemote()) {
|
|
279
|
+
return true
|
|
280
|
+
}
|
|
281
|
+
if (this.term.buffer.active.type !== 'alternate') {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
return true
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
showNormalBufferShortcut (e) {
|
|
288
|
+
e.stopPropagation()
|
|
289
|
+
this.openNormalBuffer()
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import resolve from '../../../common/resolve.js'
|
|
2
|
+
import message from '../../common/message'
|
|
3
|
+
import { getFilePath, isUnsafeFilename } from '../../../common/file-drop-utils.js'
|
|
4
|
+
import { connectionMap } from '../../../common/constants.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Files dropped onto the terminal: either quoted paths typed into the shell,
|
|
8
|
+
* or an upload through trzsz / zmodem / xmodem for ssh and serial sessions.
|
|
9
|
+
*/
|
|
10
|
+
export const fileDropMixin = {
|
|
11
|
+
onDrop (e) {
|
|
12
|
+
const dt = e.dataTransfer
|
|
13
|
+
const fromFile = dt.getData('fromFile')
|
|
14
|
+
const notSafeMsg = 'File name contains unsafe characters'
|
|
15
|
+
const isSshTerminal = this.props.tab.type === connectionMap.ssh
|
|
16
|
+
const isSerialTerminal = this.props.tab.type === connectionMap.serial
|
|
17
|
+
|
|
18
|
+
if (fromFile) {
|
|
19
|
+
try {
|
|
20
|
+
const fileData = JSON.parse(fromFile)
|
|
21
|
+
const filePath = resolve(fileData.path, fileData.name)
|
|
22
|
+
if (isUnsafeFilename(filePath)) {
|
|
23
|
+
message.error(notSafeMsg)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
if (isSshTerminal) {
|
|
27
|
+
const behavior = this.props.config.dragDropBehavior || 'ask'
|
|
28
|
+
if (behavior === 'ask') {
|
|
29
|
+
this.setState({
|
|
30
|
+
dropFileModalVisible: true,
|
|
31
|
+
droppedFiles: [{ path: filePath, isRemote: true }]
|
|
32
|
+
})
|
|
33
|
+
} else {
|
|
34
|
+
this.handleDropFileAction(behavior, [{ path: filePath, isRemote: true }])
|
|
35
|
+
}
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
if (isSerialTerminal) {
|
|
39
|
+
this.setState({
|
|
40
|
+
dropFileModalVisible: true,
|
|
41
|
+
droppedFiles: [{ path: filePath, isRemote: false }]
|
|
42
|
+
})
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
this.attachAddon._sendData(`"${filePath}" `)
|
|
46
|
+
return
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error('Failed to parse fromFile data:', e)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const files = dt.files
|
|
53
|
+
if (files && files.length) {
|
|
54
|
+
const arr = Array.from(files)
|
|
55
|
+
const filePaths = arr.map(f => getFilePath(f))
|
|
56
|
+
|
|
57
|
+
const hasUnsafeFilename = filePaths.some(path => isUnsafeFilename(path))
|
|
58
|
+
if (hasUnsafeFilename) {
|
|
59
|
+
message.error(notSafeMsg)
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (isSshTerminal) {
|
|
64
|
+
const behavior = this.props.config.dragDropBehavior || 'ask'
|
|
65
|
+
if (behavior === 'ask') {
|
|
66
|
+
this.setState({
|
|
67
|
+
dropFileModalVisible: true,
|
|
68
|
+
droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
|
|
69
|
+
})
|
|
70
|
+
} else {
|
|
71
|
+
this.handleDropFileAction(behavior, filePaths.map(path => ({ path, isRemote: false })))
|
|
72
|
+
}
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (isSerialTerminal) {
|
|
77
|
+
this.setState({
|
|
78
|
+
dropFileModalVisible: true,
|
|
79
|
+
droppedFiles: filePaths.map(path => ({ path, isRemote: false }))
|
|
80
|
+
})
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const filesAll = filePaths.map(path => `"${path}"`).join(' ')
|
|
85
|
+
this.attachAddon._sendData(filesAll)
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
handleDropFileModalCancel () {
|
|
90
|
+
this.setState({
|
|
91
|
+
dropFileModalVisible: false,
|
|
92
|
+
droppedFiles: []
|
|
93
|
+
})
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
handleDropFileAction (action, filesOverride) {
|
|
97
|
+
const droppedFiles = filesOverride || this.state.droppedFiles
|
|
98
|
+
if (!droppedFiles || !droppedFiles.length) {
|
|
99
|
+
this.handleDropFileModalCancel()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const filePaths = droppedFiles.map(f => f.path)
|
|
104
|
+
|
|
105
|
+
switch (action) {
|
|
106
|
+
case 'trz': {
|
|
107
|
+
if (this.trzszClient && this.trzszClient.isActive) {
|
|
108
|
+
message.warning('A transfer is already in progress')
|
|
109
|
+
this.handleDropFileModalCancel()
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
window._apiControlSelectFile = filePaths
|
|
113
|
+
this.attachAddon._sendData('trz\r')
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
case 'rz':{
|
|
117
|
+
if (this.zmodemClient && this.zmodemClient.isActive) {
|
|
118
|
+
message.warning('A transfer is already in progress')
|
|
119
|
+
this.handleDropFileModalCancel()
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
window._apiControlSelectFile = filePaths
|
|
123
|
+
this.attachAddon._sendData('rz\r')
|
|
124
|
+
break
|
|
125
|
+
}
|
|
126
|
+
case 'xmodem': {
|
|
127
|
+
if (this.xmodemClient && this.xmodemClient.isActive) {
|
|
128
|
+
message.warning('A transfer is already in progress')
|
|
129
|
+
this.handleDropFileModalCancel()
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
// Use XMODEM send with the dropped files
|
|
133
|
+
window._apiControlSelectFile = filePaths
|
|
134
|
+
if (this.xmodemClient) {
|
|
135
|
+
this.xmodemClient.initiateSend()
|
|
136
|
+
}
|
|
137
|
+
break
|
|
138
|
+
}
|
|
139
|
+
case 'inputOnly':
|
|
140
|
+
default: {
|
|
141
|
+
const filesAll = filePaths.map(path => `"${path}"`).join(' ')
|
|
142
|
+
this.attachAddon._sendData(filesAll)
|
|
143
|
+
break
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.handleDropFileModalCancel()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadTerminal,
|
|
3
|
+
loadFitAddon,
|
|
4
|
+
loadSerializeAddon,
|
|
5
|
+
loadSearchAddon,
|
|
6
|
+
loadLigaturesAddon,
|
|
7
|
+
loadUnicode11Addon,
|
|
8
|
+
loadImageAddon
|
|
9
|
+
} from '../xterm-loader.js'
|
|
10
|
+
import { CommandTrackerAddon } from '../command-tracker-addon.js'
|
|
11
|
+
import { Osc52Addon } from '../osc52-addon.js'
|
|
12
|
+
import { rendererTypes } from '../../../common/constants.js'
|
|
13
|
+
import { refsStatic } from '../../common/ref.js'
|
|
14
|
+
import keyControlPressed from '../../../common/key-control-pressed.js'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Terminal bootstrap: create the xterm instance, load every addon and wire
|
|
18
|
+
* the handlers that must exist before the connection is opened. Connection
|
|
19
|
+
* setup continues in `socketMixin.remoteInit`.
|
|
20
|
+
*/
|
|
21
|
+
export const initMixin = {
|
|
22
|
+
async initTerminal () {
|
|
23
|
+
const { themeConfig, tab = {}, config = {} } = this.props
|
|
24
|
+
const tc = this.getRendererThemeConfig(themeConfig)
|
|
25
|
+
const Terminal = await loadTerminal()
|
|
26
|
+
const term = new Terminal({
|
|
27
|
+
allowProposedApi: true,
|
|
28
|
+
scrollback: config.scrollback,
|
|
29
|
+
rightClickSelectsWord: config.rightClickSelectsWord || false,
|
|
30
|
+
fontFamily: tab.fontFamily || config.fontFamily,
|
|
31
|
+
theme: tc,
|
|
32
|
+
allowTransparency: true,
|
|
33
|
+
wordSeparator: config.terminalWordSeparator,
|
|
34
|
+
cursorStyle: config.cursorStyle,
|
|
35
|
+
cursorBlink: config.cursorBlink,
|
|
36
|
+
fontSize: tab.fontSize || config.fontSize,
|
|
37
|
+
screenReaderMode: config.screenReaderMode
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
term.parent = this
|
|
41
|
+
term.onSelectionChange(this.onSelection)
|
|
42
|
+
term.open(this.domRef.current, true)
|
|
43
|
+
this.registerTerminalColorQueryHandlers(term, themeConfig)
|
|
44
|
+
await this.loadRenderer(term, config)
|
|
45
|
+
this.fixSelectionColors(term)
|
|
46
|
+
// Re-apply the theme after the renderer is loaded. The Terminal was
|
|
47
|
+
// constructed before UiTheme's useEffect ran, so the initial theme
|
|
48
|
+
// may have a stale background. Pass `term` directly because this.term
|
|
49
|
+
// is not assigned yet. Use deferred=true so a second repaint picks up
|
|
50
|
+
// any CSS --main changes applied by UiTheme's useEffect.
|
|
51
|
+
if (config.rendererType === rendererTypes.webGL) {
|
|
52
|
+
this.applyTerminalTheme(true, term)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const FitAddon = await loadFitAddon()
|
|
56
|
+
this.fitAddon = new FitAddon()
|
|
57
|
+
this.cmdAddon = new CommandTrackerAddon()
|
|
58
|
+
const SerializeAddon = await loadSerializeAddon()
|
|
59
|
+
this.serializeAddon = new SerializeAddon()
|
|
60
|
+
this.cmdAddon.onCommandExecuted((cmd) => {
|
|
61
|
+
if (cmd && cmd.trim()) {
|
|
62
|
+
window.store.addCmdHistory(cmd.trim())
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
this.cmdAddon.onCwdChanged((cwd) => {
|
|
66
|
+
this.setCwd(cwd)
|
|
67
|
+
})
|
|
68
|
+
const SearchAddon = await loadSearchAddon()
|
|
69
|
+
this.searchAddon = new SearchAddon()
|
|
70
|
+
const LigaturesAddon = await loadLigaturesAddon()
|
|
71
|
+
const ligtureAddon = new LigaturesAddon()
|
|
72
|
+
this.searchAddon.onDidChangeResults(this.onSearchResultsChange)
|
|
73
|
+
const Unicode11Addon = await loadUnicode11Addon()
|
|
74
|
+
const unicode11Addon = new Unicode11Addon()
|
|
75
|
+
term.loadAddon(unicode11Addon)
|
|
76
|
+
term.loadAddon(ligtureAddon)
|
|
77
|
+
term.unicode.activeVersion = '11'
|
|
78
|
+
term.loadAddon(this.fitAddon)
|
|
79
|
+
term.loadAddon(this.searchAddon)
|
|
80
|
+
term.loadAddon(this.cmdAddon)
|
|
81
|
+
term.loadAddon(this.serializeAddon)
|
|
82
|
+
this.osc52Addon = new Osc52Addon()
|
|
83
|
+
term.loadAddon(this.osc52Addon)
|
|
84
|
+
if (tab.enableTerminalImage) {
|
|
85
|
+
const ImageAddon = await loadImageAddon()
|
|
86
|
+
this.imageAddon = new ImageAddon({
|
|
87
|
+
pixelLimit: 33554432
|
|
88
|
+
})
|
|
89
|
+
term.loadAddon(this.imageAddon)
|
|
90
|
+
}
|
|
91
|
+
term.onData(this.onData)
|
|
92
|
+
this.term = term
|
|
93
|
+
term.onSelectionChange(this.onSelectionChange)
|
|
94
|
+
term.attachCustomKeyEventHandler(this.handleKeyboardEvent.bind(this))
|
|
95
|
+
// 容器不可见(隐藏标签 display:none)时不 fit,避免算出 0 列把 shell
|
|
96
|
+
// 提示符逐字符错误换行;待标签激活可见后由 fitAndRefresh 重新适配。
|
|
97
|
+
if (this.isElementVisible()) {
|
|
98
|
+
this.fitAddon.fit()
|
|
99
|
+
}
|
|
100
|
+
await this.restoreReloadScreen(term)
|
|
101
|
+
await this.remoteInit(term)
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
onSelectionChange () {
|
|
105
|
+
const hasSelection = this.term.hasSelection()
|
|
106
|
+
const txt = hasSelection ? this.term.getSelection().trim() : ''
|
|
107
|
+
this.setState({ hasSelection })
|
|
108
|
+
refsStatic.get('unix-timestamp-tooltip')?.onSelection(txt)
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
webLinkHandler (event, url) {
|
|
112
|
+
if (event?.button === 2) {
|
|
113
|
+
return false
|
|
114
|
+
}
|
|
115
|
+
if (!this.props.config.ctrlOrMetaOpenTerminalLink) {
|
|
116
|
+
return window.openLink(url, '_blank')
|
|
117
|
+
}
|
|
118
|
+
if (keyControlPressed(event)) {
|
|
119
|
+
window.openLink(url, '_blank')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|