mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
Fix duplicate and lost IME terminal commits (#9235)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
File diff suppressed because one or more lines are too long
+2
-1
@@ -273,7 +273,8 @@
|
||||
"node-pty@1.1.0": "config/patches/node-pty@1.1.0.patch",
|
||||
"@xterm/addon-ligatures@0.11.0-beta.287": "config/patches/@xterm__addon-ligatures@0.11.0-beta.287.patch",
|
||||
"@xterm/addon-webgl@0.20.0-beta.286": "config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch",
|
||||
"@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch"
|
||||
"@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch",
|
||||
"@xterm/xterm@6.1.0-beta.287": "config/patches/@xterm__xterm@6.1.0-beta.287.patch"
|
||||
}
|
||||
},
|
||||
"reactDoctor": {
|
||||
|
||||
Generated
+26
-23
@@ -17,6 +17,9 @@ patchedDependencies:
|
||||
'@xterm/addon-webgl@0.20.0-beta.286':
|
||||
hash: 6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258
|
||||
path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch
|
||||
'@xterm/xterm@6.1.0-beta.287':
|
||||
hash: 9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c
|
||||
path: config/patches/@xterm__xterm@6.1.0-beta.287.patch
|
||||
node-pty@1.1.0:
|
||||
hash: 407ae07e1e0e2ff2e8b58696449c54c31e51d87535bc6aa4a7a7b0b561407282
|
||||
path: config/patches/node-pty@1.1.0.patch
|
||||
@@ -42,7 +45,7 @@ importers:
|
||||
version: 2.5.6
|
||||
'@xterm/addon-serialize':
|
||||
specifier: 0.15.0-beta.287
|
||||
version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/headless':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287
|
||||
@@ -202,25 +205,25 @@ importers:
|
||||
version: 5.2.0(vite@7.3.6(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))
|
||||
'@xterm/addon-fit':
|
||||
specifier: 0.12.0-beta.287
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/addon-ligatures':
|
||||
specifier: 0.11.0-beta.287
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/addon-search':
|
||||
specifier: 0.17.0-beta.287
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/addon-unicode11':
|
||||
specifier: 0.10.0-beta.287
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/addon-web-links':
|
||||
specifier: 0.13.0-beta.287
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/addon-webgl':
|
||||
specifier: 0.20.0-beta.286
|
||||
version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))
|
||||
'@xterm/xterm':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -9580,39 +9583,39 @@ snapshots:
|
||||
|
||||
'@xmldom/xmldom@0.9.10': {}
|
||||
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
lru-cache: 11.5.1
|
||||
opentype.js: 2.0.0
|
||||
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=6da7d7770b6427246f2a0d057d97da418040e498068b41d0c2d3c6b20bf49258)(@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)
|
||||
|
||||
'@xterm/headless@6.1.0-beta.287': {}
|
||||
|
||||
'@xterm/xterm@6.1.0-beta.287': {}
|
||||
'@xterm/xterm@6.1.0-beta.287(patch_hash=9c1de9931d86864923ff53bc9d64474a86478085ac81ea4e432648c7e23d702c)': {}
|
||||
|
||||
abbrev@4.0.0: {}
|
||||
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { Terminal } from '@xterm/xterm'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
function nextEventLoop(): Promise<void> {
|
||||
return new Promise((resolve) => window.setTimeout(resolve, 0))
|
||||
}
|
||||
|
||||
function openTerminal(): {
|
||||
emitted: string[]
|
||||
terminal: Terminal
|
||||
textarea: HTMLTextAreaElement
|
||||
} {
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const terminal = new Terminal()
|
||||
terminal.open(container)
|
||||
const textarea = terminal.textarea
|
||||
if (!textarea) {
|
||||
throw new Error('xterm helper textarea was not created')
|
||||
}
|
||||
const emitted: string[] = []
|
||||
terminal.onData((data) => emitted.push(data))
|
||||
return { emitted, terminal, textarea }
|
||||
}
|
||||
|
||||
function startComposition(textarea: HTMLTextAreaElement, text: string): void {
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionstart', { bubbles: true }))
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionupdate', { data: text, bubbles: true }))
|
||||
textarea.value = text
|
||||
}
|
||||
|
||||
function dispatchKeypress(textarea: HTMLTextAreaElement, text: string): void {
|
||||
const keypress = new KeyboardEvent('keypress', { key: text, bubbles: true })
|
||||
// happy-dom omits Chromium's legacy charCode field that xterm still reads.
|
||||
Object.defineProperty(keypress, 'charCode', { value: text.charCodeAt(0) })
|
||||
textarea.dispatchEvent(keypress)
|
||||
}
|
||||
|
||||
describe('xterm IME composition de-duplication', () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({
|
||||
measureText: () => ({ width: 10 })
|
||||
} as unknown as CanvasRenderingContext2D)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
document.body.replaceChildren()
|
||||
})
|
||||
|
||||
it('emits a post-composition IBus Hangul keypress only once', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
// Why: IBus clears at compositionend, then restores the same commit after
|
||||
// xterm's keypress path has already emitted it.
|
||||
textarea.value = ''
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
const compositionHelper = (
|
||||
terminal as unknown as {
|
||||
_core: {
|
||||
_compositionHelper: {
|
||||
_isSendingComposition: boolean
|
||||
_pendingKeypressData: string
|
||||
}
|
||||
}
|
||||
}
|
||||
)._core._compositionHelper
|
||||
expect(compositionHelper._isSendingComposition).toBe(true)
|
||||
dispatchKeypress(textarea, '한')
|
||||
expect(compositionHelper._pendingKeypressData).toBe('한')
|
||||
expect(emitted).toEqual([])
|
||||
textarea.value = '한'
|
||||
textarea.dispatchEvent(
|
||||
new InputEvent('input', { data: '한', inputType: 'insertText', bubbles: true })
|
||||
)
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('한')
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('preserves composition-first order when keypress overlaps its suffix', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '가한')
|
||||
await nextEventLoop()
|
||||
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
dispatchKeypress(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('가한')
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('emits unmatched keypress before propagated composition text', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionupdate', { data: 'a', bubbles: true }))
|
||||
textarea.value = 'a'
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
dispatchKeypress(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('한a')
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('does not repeat keypress contained before propagated composition text', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
textarea.dispatchEvent(
|
||||
new CompositionEvent('compositionupdate', { data: '한a', bubbles: true })
|
||||
)
|
||||
textarea.value = '한a'
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
dispatchKeypress(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('한a')
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('merges multiple deferred keypresses with partial textarea overlap', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
dispatchKeypress(textarea, 'a')
|
||||
dispatchKeypress(textarea, 'b')
|
||||
textarea.value = '한a'
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('한ab')
|
||||
terminal.dispose()
|
||||
})
|
||||
|
||||
it('reconciles buffered keypress before immediate keydown finalization', async () => {
|
||||
const { emitted, terminal, textarea } = openTerminal()
|
||||
startComposition(textarea, '한')
|
||||
await nextEventLoop()
|
||||
|
||||
textarea.value = ''
|
||||
textarea.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }))
|
||||
dispatchKeypress(textarea, '한')
|
||||
textarea.value = '한'
|
||||
const keydown = new KeyboardEvent('keydown', { key: 'a', code: 'KeyA', bubbles: true })
|
||||
Object.defineProperty(keydown, 'keyCode', { value: 65 })
|
||||
textarea.dispatchEvent(keydown)
|
||||
await nextEventLoop()
|
||||
|
||||
expect(emitted.join('')).toBe('한a')
|
||||
terminal.dispose()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user