@electerm/electerm-react 2.5.16 → 2.7.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.
@@ -8,10 +8,8 @@ import {
8
8
  } from '../../common/constants'
9
9
  import {
10
10
  Spin,
11
- // Button,
12
11
  Select
13
12
  } from 'antd'
14
- import { notification } from '../common/notification'
15
13
  import {
16
14
  ReloadOutlined,
17
15
  EditOutlined
@@ -22,6 +20,25 @@ import resolutions from './resolutions'
22
20
 
23
21
  const { Option } = Select
24
22
 
23
+ // IronRDP WASM module imports — loaded dynamically
24
+ async function loadWasmModule () {
25
+ if (window.ironRdp) return
26
+ console.debug('[RDP-CLIENT] Loading IronRDP WASM module...')
27
+ const mod = await import('ironrdp-wasm')
28
+ window.ironRdp = {
29
+ wasmInit: mod.default,
30
+ wasmSetup: mod.setup,
31
+ SessionBuilder: mod.SessionBuilder,
32
+ DesktopSize: mod.DesktopSize,
33
+ InputTransaction: mod.InputTransaction,
34
+ DeviceEvent: mod.DeviceEvent,
35
+ Extension: mod.Extension
36
+ }
37
+ await window.ironRdp.wasmInit()
38
+ window.ironRdp.wasmSetup('info')
39
+ console.debug('[RDP-CLIENT] IronRDP WASM module loaded and initialized')
40
+ }
41
+
25
42
  export default class RdpSession extends PureComponent {
26
43
  constructor (props) {
27
44
  const id = `rdp-reso-${props.tab.host}`
@@ -30,10 +47,9 @@ export default class RdpSession extends PureComponent {
30
47
  this.canvasRef = createRef()
31
48
  this.state = {
32
49
  loading: false,
33
- bitmapProps: {},
34
- aspectRatio: 4 / 3,
35
50
  ...resObj
36
51
  }
52
+ this.session = null
37
53
  }
38
54
 
39
55
  componentDidMount () {
@@ -41,14 +57,24 @@ export default class RdpSession extends PureComponent {
41
57
  }
42
58
 
43
59
  componentWillUnmount () {
44
- this.socket && this.socket.close()
45
- delete this.socket
60
+ this.cleanup()
46
61
  }
47
62
 
48
- runInitScript = () => {
49
-
63
+ cleanup = () => {
64
+ console.debug('[RDP-CLIENT] cleanup() called')
65
+ if (this.session) {
66
+ try {
67
+ this.session.shutdown()
68
+ console.debug('[RDP-CLIENT] session.shutdown() called')
69
+ } catch (e) {
70
+ console.debug('[RDP-CLIENT] session.shutdown() error:', e)
71
+ }
72
+ this.session = null
73
+ }
50
74
  }
51
75
 
76
+ runInitScript = () => {}
77
+
52
78
  setStatus = status => {
53
79
  const id = this.props.tab?.id
54
80
  this.props.editTab(id, {
@@ -56,7 +82,7 @@ export default class RdpSession extends PureComponent {
56
82
  })
57
83
  }
58
84
 
59
- remoteInit = async (term = this.term) => {
85
+ remoteInit = async () => {
60
86
  this.setState({
61
87
  loading: true
62
88
  })
@@ -80,237 +106,279 @@ export default class RdpSession extends PureComponent {
80
106
  termType: type,
81
107
  ...tab
82
108
  })
109
+
110
+ console.debug('[RDP-CLIENT] Creating RDP session term, host=', tab.host, 'port=', tab.port)
83
111
  const r = await createTerm(opts)
84
112
  .catch(err => {
85
113
  const text = err.message
86
114
  handleErr({ message: text })
87
115
  })
88
- this.setState({
89
- loading: false
90
- })
91
116
  if (!r) {
117
+ this.setState({ loading: false })
92
118
  this.setStatus(statusMap.error)
119
+ console.error('[RDP-CLIENT] createTerm failed')
93
120
  return
94
121
  }
95
- this.setStatus(statusMap.success)
122
+
96
123
  const {
97
124
  pid, port
98
125
  } = r
99
126
  this.pid = pid
127
+ console.debug('[RDP-CLIENT] Term created, pid=', pid, 'port=', port)
128
+
129
+ // Build the WebSocket proxy address for IronRDP WASM
100
130
  const hs = server
101
131
  ? server.replace(/https?:\/\//, '')
102
132
  : `${host}:${port}`
103
133
  const pre = server.startsWith('https') ? 'wss' : 'ws'
104
134
  const { width, height } = this.state
105
- const wsUrl = `${pre}://${hs}/rdp/${pid}?&token=${tokenElecterm}&width=${width}&height=${height}`
106
- const socket = new WebSocket(wsUrl)
107
- socket.onclose = this.oncloseSocket
108
- socket.onerror = this.onerrorSocket
109
- this.socket = socket
110
- socket.onopen = this.runInitScript
111
- socket.onmessage = this.onData
112
- }
113
-
114
- decompress = (bitmap) => {
115
- let fName = null
116
- switch (bitmap.bitsPerPixel) {
117
- case 15:
118
- fName = 'bitmap_decompress_15'
119
- break
120
- case 16:
121
- fName = 'bitmap_decompress_16'
122
- break
123
- case 24:
124
- fName = 'bitmap_decompress_24'
125
- break
126
- case 32:
127
- fName = 'bitmap_decompress_32'
128
- break
129
- default:
130
- throw new Error('invalid bitmap data format')
135
+ // IronRDP connects to the proxy address, which then proxies via RDCleanPath
136
+ // The WebSocket URL includes the pid and token for auth
137
+ const proxyAddress = `${pre}://${hs}/rdp/${pid}?token=${tokenElecterm}&width=${width}&height=${height}`
138
+ console.debug('[RDP-CLIENT] Proxy address:', proxyAddress)
139
+
140
+ // Load WASM module if not already loaded
141
+ try {
142
+ await loadWasmModule()
143
+ } catch (e) {
144
+ console.error('[RDP-CLIENT] Failed to load WASM module:', e)
145
+ this.setState({ loading: false })
146
+ this.setStatus(statusMap.error)
147
+ return
131
148
  }
132
- const rle = window.Module
133
- const input = new Uint8Array(bitmap.data.data)
134
- const inputPtr = rle._malloc(input.length)
135
- const inputHeap = new Uint8Array(rle.HEAPU8.buffer, inputPtr, input.length)
136
- inputHeap.set(input)
137
-
138
- const outputWidth = bitmap.destRight - bitmap.destLeft + 1
139
- const outputHeight = bitmap.destBottom - bitmap.destTop + 1
140
- const ouputSize = outputWidth * outputHeight * 4
141
- const outputPtr = rle._malloc(ouputSize)
142
-
143
- const outputHeap = new Uint8Array(rle.HEAPU8.buffer, outputPtr, ouputSize)
144
-
145
- rle.ccall(fName,
146
- 'number',
147
- ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
148
- [outputHeap.byteOffset, outputWidth, outputHeight, bitmap.width, bitmap.height, inputHeap.byteOffset, input.length]
149
- )
150
149
 
151
- const output = new Uint8ClampedArray(outputHeap.buffer, outputHeap.byteOffset, ouputSize)
150
+ this.setStatus(statusMap.success)
152
151
 
153
- rle._free(inputPtr)
154
- rle._free(outputPtr)
152
+ // Connect using IronRDP SessionBuilder
153
+ try {
154
+ const canvas = this.canvasRef.current
155
+ if (!canvas) {
156
+ console.error('[RDP-CLIENT] Canvas ref not available')
157
+ this.setState({ loading: false })
158
+ return
159
+ }
155
160
 
156
- return { width: outputWidth, height: outputHeight, arr: output }
157
- }
161
+ const rdpHost = tab.host
162
+ const rdpPort = tab.port || 3389
163
+ const destination = `${rdpHost}:${rdpPort}`
164
+ const username = tab.username || ''
165
+ const password = tab.password || ''
166
+
167
+ console.debug('[RDP-CLIENT] Building IronRDP session...')
168
+ console.debug('[RDP-CLIENT] destination:', destination)
169
+ console.debug('[RDP-CLIENT] username:', username)
170
+ console.debug('[RDP-CLIENT] proxyAddress:', proxyAddress)
171
+ console.debug('[RDP-CLIENT] desktopSize:', width, 'x', height)
172
+
173
+ const desktopSize = new window.ironRdp.DesktopSize(width, height)
174
+ const enableCredsspExt = new window.ironRdp.Extension('enable_credssp', false)
175
+
176
+ const builder = new window.ironRdp.SessionBuilder()
177
+ builder.username(username)
178
+ builder.password(password)
179
+ builder.destination(destination)
180
+ builder.proxyAddress(proxyAddress)
181
+ builder.authToken('none')
182
+ builder.desktopSize(desktopSize)
183
+ builder.renderCanvas(canvas)
184
+ builder.extension(enableCredsspExt)
185
+
186
+ // Cursor style callback
187
+ builder.setCursorStyleCallbackContext(canvas)
188
+ builder.setCursorStyleCallback(function (style) {
189
+ canvas.style.cursor = style || 'default'
190
+ })
158
191
 
159
- output = (data) => {
160
- if (!data.isCompress) {
161
- return {
162
- width: data.width,
163
- height: data.height,
164
- arr: new Uint8ClampedArray(data.data)
165
- }
166
- }
167
- return this.decompress(data)
168
- }
192
+ console.debug('[RDP-CLIENT] Calling builder.connect()...')
193
+ this.session = await builder.connect()
194
+
195
+ const ds = this.session.desktopSize()
196
+ console.debug('[RDP-CLIENT] Connected! Desktop:', ds.width, 'x', ds.height)
197
+
198
+ // Update canvas size to match actual desktop size
199
+ canvas.width = ds.width
200
+ canvas.height = ds.height
201
+
202
+ this.setState({
203
+ loading: false
204
+ })
169
205
 
170
- getButtonCode (button) {
171
- if (button === 0) {
172
- return 1
173
- } else if (button === 2) {
174
- return 2
175
- } else {
176
- return 0
206
+ canvas.focus()
207
+
208
+ // Run the session event loop (renders frames, handles protocol)
209
+ console.debug('[RDP-CLIENT] Starting session.run() event loop')
210
+ this.session.run().then((info) => {
211
+ console.debug('[RDP-CLIENT] Session ended:', info.reason())
212
+ this.onSessionEnd()
213
+ }).catch((e) => {
214
+ console.error('[RDP-CLIENT] Session error:', this.formatError(e))
215
+ this.onSessionEnd()
216
+ })
217
+ } catch (e) {
218
+ console.error('[RDP-CLIENT] Connection failed:', this.formatError(e))
219
+ this.setState({ loading: false })
220
+ this.setStatus(statusMap.error)
177
221
  }
178
222
  }
179
223
 
180
- getKeyCode (event) {
181
- const { type, button } = event
182
- if (type.startsWith('key')) {
183
- return scanCode(event)
184
- } else if (type === 'mousemove') {
185
- return 0
186
- } else {
187
- return this.getButtonCode(button)
224
+ formatError = (e) => {
225
+ if (e && typeof e === 'object' && '__wbg_ptr' in e) {
226
+ try {
227
+ const kindNames = {
228
+ 0: 'General',
229
+ 1: 'WrongPassword',
230
+ 2: 'LogonFailure',
231
+ 3: 'AccessDenied',
232
+ 4: 'RDCleanPath',
233
+ 5: 'ProxyConnect',
234
+ 6: 'NegotiationFailure'
235
+ }
236
+ const kind = e.kind ? e.kind() : 'Unknown'
237
+ const bt = e.backtrace ? e.backtrace() : ''
238
+ return `[${kindNames[kind] || kind}] ${bt}`
239
+ } catch (_) {}
188
240
  }
241
+ return e?.message || e?.toString() || String(e)
189
242
  }
190
243
 
191
- handleCanvasEvent = e => {
192
- const {
193
- type,
194
- clientX,
195
- clientY
196
- } = e
197
- const {
198
- left,
199
- top
200
- } = e.target.getBoundingClientRect()
201
- const x = clientX - left
202
- const y = clientY - top
203
- const keyCode = this.getKeyCode(e)
204
- const action = type.startsWith('key')
205
- ? 'sendKeyEventScancode'
206
- : type === 'mousewheel'
207
- ? 'sendWheelEvent'
208
- : 'sendPointerEvent'
209
- const pressed = type === 'mousedown' || type === 'keydown'
210
- let params = []
211
- if (type.startsWith('mouse') || type.startsWith('context')) {
212
- params = [x, y, keyCode, pressed]
213
- } else if (type === 'wheel') {
214
- const isHorizontal = false
215
- const delta = isHorizontal ? e.deltaX : e.deltaY
216
- const step = Math.round(Math.abs(delta) * 15 / 8)
217
- params = [x, y, step, delta > 0, isHorizontal]
218
- } else if (type === 'keydown' || type === 'keyup') {
219
- params = [keyCode, pressed]
220
- }
221
- this.socket.send(JSON.stringify({
222
- action,
223
- params
224
- }))
244
+ onSessionEnd = () => {
245
+ console.debug('[RDP-CLIENT] onSessionEnd called')
246
+ this.session = null
247
+ this.setStatus(statusMap.error)
225
248
  }
226
249
 
227
- onData = async (msg) => {
228
- let { data } = msg
229
- data = JSON.parse(data)
230
- if (data.action === 'session-rdp-connected') {
231
- return this.setState({
232
- loading: false
233
- })
234
- }
250
+ setupInputHandlers = () => {
235
251
  const canvas = this.canvasRef.current
236
- const ctx = canvas.getContext('2d')
237
- const {
238
- width,
239
- height,
240
- arr
241
- } = this.output(data)
242
- const imageData = ctx.createImageData(width, height)
243
- imageData.data.set(arr)
244
- ctx.putImageData(imageData, data.destLeft, data.destTop)
245
- }
252
+ if (!canvas || this._inputHandlersSetup) return
253
+ this._inputHandlersSetup = true
254
+ console.debug('[RDP-CLIENT] Setting up input handlers')
255
+
256
+ canvas.addEventListener('keydown', (e) => {
257
+ e.preventDefault()
258
+ e.stopPropagation()
259
+ if (!this.session) return
260
+ const scancode = this.getScancode(e.code)
261
+ if (scancode === null) return
262
+ try {
263
+ const event = window.ironRdp.DeviceEvent.keyPressed(scancode)
264
+ const tx = new window.ironRdp.InputTransaction()
265
+ tx.addEvent(event)
266
+ this.session.applyInputs(tx)
267
+ } catch (err) {
268
+ console.error('[RDP-CLIENT] Key press error:', err)
269
+ }
270
+ })
246
271
 
247
- onerrorSocket = err => {
248
- this.setStatus(statusMap.error)
249
- console.error('socket error', err)
272
+ canvas.addEventListener('keyup', (e) => {
273
+ e.preventDefault()
274
+ e.stopPropagation()
275
+ if (!this.session) return
276
+ const scancode = this.getScancode(e.code)
277
+ if (scancode === null) return
278
+ try {
279
+ const event = window.ironRdp.DeviceEvent.keyReleased(scancode)
280
+ const tx = new window.ironRdp.InputTransaction()
281
+ tx.addEvent(event)
282
+ this.session.applyInputs(tx)
283
+ } catch (err) {
284
+ console.error('[RDP-CLIENT] Key release error:', err)
285
+ }
286
+ })
287
+
288
+ canvas.addEventListener('mousemove', (e) => {
289
+ if (!this.session) return
290
+ try {
291
+ const rect = canvas.getBoundingClientRect()
292
+ const scaleX = canvas.width / rect.width
293
+ const scaleY = canvas.height / rect.height
294
+ const x = Math.round((e.clientX - rect.left) * scaleX)
295
+ const y = Math.round((e.clientY - rect.top) * scaleY)
296
+ const event = window.ironRdp.DeviceEvent.mouseMove(x, y)
297
+ const tx = new window.ironRdp.InputTransaction()
298
+ tx.addEvent(event)
299
+ this.session.applyInputs(tx)
300
+ } catch (err) {
301
+ // suppress frequent mouse errors
302
+ }
303
+ })
304
+
305
+ canvas.addEventListener('mousedown', (e) => {
306
+ e.preventDefault()
307
+ canvas.focus()
308
+ if (!this.session) return
309
+ try {
310
+ const event = window.ironRdp.DeviceEvent.mouseButtonPressed(e.button)
311
+ const tx = new window.ironRdp.InputTransaction()
312
+ tx.addEvent(event)
313
+ this.session.applyInputs(tx)
314
+ } catch (err) {
315
+ console.error('[RDP-CLIENT] Mouse down error:', err)
316
+ }
317
+ })
318
+
319
+ canvas.addEventListener('mouseup', (e) => {
320
+ e.preventDefault()
321
+ if (!this.session) return
322
+ try {
323
+ const event = window.ironRdp.DeviceEvent.mouseButtonReleased(e.button)
324
+ const tx = new window.ironRdp.InputTransaction()
325
+ tx.addEvent(event)
326
+ this.session.applyInputs(tx)
327
+ } catch (err) {
328
+ console.error('[RDP-CLIENT] Mouse up error:', err)
329
+ }
330
+ })
331
+
332
+ canvas.addEventListener('wheel', (e) => {
333
+ e.preventDefault()
334
+ if (!this.session) return
335
+ try {
336
+ if (e.deltaY !== 0) {
337
+ const amount = e.deltaY > 0 ? -1 : 1
338
+ const event = window.ironRdp.DeviceEvent.wheelRotations(true, amount, 1)
339
+ const tx = new window.ironRdp.InputTransaction()
340
+ tx.addEvent(event)
341
+ this.session.applyInputs(tx)
342
+ }
343
+ if (e.deltaX !== 0) {
344
+ const amount = e.deltaX > 0 ? -1 : 1
345
+ const event = window.ironRdp.DeviceEvent.wheelRotations(false, amount, 1)
346
+ const tx = new window.ironRdp.InputTransaction()
347
+ tx.addEvent(event)
348
+ this.session.applyInputs(tx)
349
+ }
350
+ } catch (err) {
351
+ console.error('[RDP-CLIENT] Wheel error:', err)
352
+ }
353
+ }, { passive: false })
354
+
355
+ canvas.addEventListener('contextmenu', (e) => e.preventDefault())
250
356
  }
251
357
 
252
- closeMsg = () => {
253
- notification.destroy(this.warningKey)
358
+ // Get PS/2 scancode from keyboard event code using existing code-scan module
359
+ getScancode = (code) => {
360
+ const sc = scanCode({ code })
361
+ return sc !== undefined ? sc : null
254
362
  }
255
363
 
256
- handleClickClose = () => {
257
- this.closeMsg()
258
- this.handleReInit()
364
+ handleEditResolutions = () => {
365
+ window.store.toggleResolutionEdit()
259
366
  }
260
367
 
261
368
  handleReInit = () => {
262
- this.socket.send(JSON.stringify({
263
- action: 'reload',
264
- params: [
265
- this.state.width,
266
- this.state.height
267
- ]
268
- }))
269
- this.setState({
270
- loading: true
271
- })
369
+ console.debug('[RDP-CLIENT] handleReInit called')
370
+ this.cleanup()
371
+ this.props.reloadTab(
372
+ this.props.tab
373
+ )
272
374
  }
273
375
 
274
376
  handleReload = () => {
275
- this.closeMsg()
276
377
  this.props.reloadTab(
277
378
  this.props.tab
278
379
  )
279
380
  }
280
381
 
281
- handleEditResolutions = () => {
282
- window.store.toggleResolutionEdit()
283
- }
284
-
285
- oncloseSocket = () => {
286
- // this.setStatus(
287
- // statusMap.error
288
- // )
289
- // this.warningKey = `open${Date.now()}`
290
- // notification.warning({
291
- // key: this.warningKey,
292
- // message: e('socketCloseTip'),
293
- // duration: 30,
294
- // description: (
295
- // <div className='pd2y'>
296
- // <Button
297
- // className='mg1r'
298
- // type='primary'
299
- // onClick={this.handleClickClose}
300
- // >
301
- // {m('close')}
302
- // </Button>
303
- // <Button
304
- // icon={<ReloadOutlined />}
305
- // onClick={this.handleReload}
306
- // >
307
- // {m('reload')}
308
- // </Button>
309
- // </div>
310
- // )
311
- // })
312
- }
313
-
314
382
  getAllRes = () => {
315
383
  return [
316
384
  ...this.props.resolutions,
@@ -389,6 +457,11 @@ export default class RdpSession extends PureComponent {
389
457
  )
390
458
  }
391
459
 
460
+ componentDidUpdate () {
461
+ // Set up native input handlers after canvas is rendered
462
+ this.setupInputHandlers()
463
+ }
464
+
392
465
  render () {
393
466
  const { width: w, height: h } = this.props
394
467
  const rdpProps = {
@@ -401,13 +474,6 @@ export default class RdpSession extends PureComponent {
401
474
  const canvasProps = {
402
475
  width,
403
476
  height,
404
- onMouseDown: this.handleCanvasEvent,
405
- onMouseUp: this.handleCanvasEvent,
406
- onMouseMove: this.handleCanvasEvent,
407
- onKeyDown: this.handleCanvasEvent,
408
- onKeyUp: this.handleCanvasEvent,
409
- onWheel: this.handleCanvasEvent,
410
- onContextMenu: this.handleCanvasEvent,
411
477
  tabIndex: 0
412
478
  }
413
479
  return (
@@ -167,7 +167,6 @@
167
167
 
168
168
  .sftp-has-pager
169
169
  .sftp-table-content
170
- position static
171
170
  padding-bottom 42px
172
171
  .pager-wrap
173
172
  position absolute
@@ -175,6 +174,7 @@
175
174
  left 0
176
175
  width 100%
177
176
  background var(--main)
177
+ height 40px
178
178
  border-top 1px solid var(--main-darker)
179
179
 
180
180
  .file-header-context-menu
@@ -3,6 +3,87 @@ import shortcutsDefaultsGen from './shortcuts-defaults.js'
3
3
  import {
4
4
  isMacJs
5
5
  } from '../../common/constants'
6
+ import keyControlPressed from '../../common/key-control-pressed.js'
7
+
8
+ function sendInputData (ctx, data) {
9
+ if (!data) return
10
+ if (ctx.attachAddon && ctx.attachAddon._sendData) {
11
+ ctx.attachAddon._sendData(data)
12
+ }
13
+ // if (!ctx.onData) return
14
+ // if (splitChars) {
15
+ // for (const ch of data) {
16
+ // ctx.onData(ch)
17
+ // }
18
+ // return
19
+ // }
20
+ // ctx.onData(data)
21
+ }
22
+
23
+ function getSelectionReplaceInfo (term) {
24
+ if (!term || !term.hasSelection()) return null
25
+ if (term.buffer?.active?.type === 'alternate') return null
26
+ const getPos = term.getSelectionPosition?.bind(term)
27
+ if (!getPos) return null
28
+ const pos = getPos()
29
+ if (!pos || !pos.start || !pos.end) return null
30
+ const buffer = term.buffer.active
31
+ const cursorY = buffer.cursorY
32
+ if (pos.start.y !== cursorY || pos.end.y !== cursorY) return null
33
+ const startX = Math.min(pos.start.x, pos.end.x)
34
+ const endX = Math.max(pos.start.x, pos.end.x)
35
+ if (startX === endX) return null
36
+ return {
37
+ startX,
38
+ endX,
39
+ cursorX: buffer.cursorX
40
+ }
41
+ }
42
+
43
+ export function handleTerminalSelectionReplace (event, ctx) {
44
+ if (
45
+ !ctx.term ||
46
+ !ctx.term.hasSelection() ||
47
+ keyControlPressed(event)
48
+ ) {
49
+ return false
50
+ }
51
+ const { key } = event
52
+ const isBackspace = key === 'Backspace'
53
+ const isDelete = key === 'Delete'
54
+ const isPrintable = key && key.length === 1
55
+ if (!isBackspace && !isDelete && !isPrintable) return false
56
+
57
+ if (event && event.preventDefault) {
58
+ event.preventDefault()
59
+ }
60
+ if (event && event.stopPropagation) {
61
+ event.stopPropagation()
62
+ }
63
+
64
+ const info = getSelectionReplaceInfo(ctx.term)
65
+ if (!info) return false
66
+
67
+ const { startX, endX, cursorX } = info
68
+ const move = startX - cursorX
69
+ if (move > 0) {
70
+ sendInputData(ctx, '\x1b[C'.repeat(move))
71
+ } else if (move < 0) {
72
+ sendInputData(ctx, '\x1b[D'.repeat(-move))
73
+ }
74
+
75
+ const delCount = endX - startX
76
+ if (delCount > 0) {
77
+ sendInputData(ctx, '\x1b[3~'.repeat(delCount))
78
+ }
79
+
80
+ if (isPrintable) {
81
+ sendInputData(ctx, key)
82
+ }
83
+
84
+ ctx.term.clearSelection()
85
+ return true
86
+ }
6
87
 
7
88
  function buildConfig (config, filter = d => d) {
8
89
  const defs = shortcutsDefaultsGen().filter(filter)
@@ -65,6 +146,9 @@ export function shortcutExtend (Cls) {
65
146
  if (isInAntdInput()) {
66
147
  return
67
148
  }
149
+ if (handleTerminalSelectionReplace(event, this)) {
150
+ return false
151
+ }
68
152
  if (
69
153
  this.term &&
70
154
  key === 'Backspace' &&