mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 00:04:01 +00:00
fix(terminal): track latest IME placeholder session
This commit is contained in:
+49
-7
@@ -3,7 +3,6 @@ import { Terminal } from '@xterm/xterm'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
installTerminalImeComposerPlaceholderMask,
|
||||
MAX_ACTIVE_COMPOSITION_SESSIONS,
|
||||
TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS
|
||||
} from './terminal-ime-composer-placeholder-mask'
|
||||
import {
|
||||
@@ -200,7 +199,7 @@ describe('terminal IME composer placeholder mask', () => {
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(false)
|
||||
})
|
||||
|
||||
it('resets stale ownership at the session bound while keeping the newest session active', async () => {
|
||||
it('keeps the newest session owner when an older session ends first', async () => {
|
||||
const rig = openTerminal()
|
||||
await rig.write(codexPlaceholderFrame())
|
||||
|
||||
@@ -213,17 +212,60 @@ describe('terminal IME composer placeholder mask', () => {
|
||||
)
|
||||
}
|
||||
|
||||
for (let id = 1; id <= MAX_ACTIVE_COMPOSITION_SESSIONS + 1; id += 1) {
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, id)
|
||||
}
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, 1)
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, 2)
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, 1)
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(true)
|
||||
|
||||
for (let id = 1; id <= MAX_ACTIVE_COMPOSITION_SESSIONS; id += 1) {
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, 2)
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(false)
|
||||
})
|
||||
|
||||
it('clears the visible owner on a current end and ignores a late older end', async () => {
|
||||
const rig = openTerminal()
|
||||
await rig.write(codexPlaceholderFrame())
|
||||
|
||||
const dispatchSession = (type: string, id: number): void => {
|
||||
rig.element.dispatchEvent(
|
||||
new CustomEvent(type, {
|
||||
bubbles: true,
|
||||
detail: { id }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, 11)
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, 12)
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, 12)
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(false)
|
||||
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, 11)
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps ownership bounded across repeated starts and recovers on the newest end', async () => {
|
||||
const rig = openTerminal()
|
||||
await rig.write(codexPlaceholderFrame())
|
||||
|
||||
const dispatchSession = (type: string, id: number): void => {
|
||||
rig.element.dispatchEvent(
|
||||
new CustomEvent(type, {
|
||||
bubbles: true,
|
||||
detail: { id }
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const newestId = 2048
|
||||
for (let id = 1; id <= newestId; id += 1) {
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_START_EVENT, id)
|
||||
}
|
||||
for (let id = 1; id < newestId; id += 1) {
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, id)
|
||||
}
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(true)
|
||||
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, MAX_ACTIVE_COMPOSITION_SESSIONS + 1)
|
||||
dispatchSession(XTERM_COMPOSITION_SESSION_END_EVENT, newestId)
|
||||
expect(rig.element.classList.contains(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
+11
-14
@@ -7,9 +7,6 @@ import {
|
||||
} from './terminal-ime-composition-route'
|
||||
|
||||
export const TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS = 'orca-ime-composer-placeholder'
|
||||
// One live composition and a few delayed turnovers are enough for xterm's lifecycle; a missing
|
||||
// session-end event must not retain ownership forever.
|
||||
export const MAX_ACTIVE_COMPOSITION_SESSIONS = 8
|
||||
|
||||
function compositionSessionId(event: Event): number | null {
|
||||
if (!(event instanceof CustomEvent)) {
|
||||
@@ -25,10 +22,12 @@ export function installTerminalImeComposerPlaceholderMask(terminal: Terminal): I
|
||||
return { dispose: () => undefined }
|
||||
}
|
||||
|
||||
const activeSessions = new Set<number>()
|
||||
// xterm renders one composition view; a newer transaction supersedes any older pending one.
|
||||
// Keeping only its id makes malformed/repeated starts bounded without evicting live ownership.
|
||||
let activeSessionId: number | null = null
|
||||
const syncPlaceholderOwnership = (): void => {
|
||||
const ownsPlaceholder =
|
||||
activeSessions.size > 0 &&
|
||||
activeSessionId !== null &&
|
||||
hasTerminalComposerPlaceholder(readTerminalCursorLineContext(terminal, terminal.rows))
|
||||
element.classList.toggle(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS, ownsPlaceholder)
|
||||
}
|
||||
@@ -37,11 +36,7 @@ export function installTerminalImeComposerPlaceholderMask(terminal: Terminal): I
|
||||
if (id === null) {
|
||||
return
|
||||
}
|
||||
if (!activeSessions.has(id) && activeSessions.size >= MAX_ACTIVE_COMPOSITION_SESSIONS) {
|
||||
// Drop stale ownership before adopting the newest transaction.
|
||||
activeSessions.clear()
|
||||
}
|
||||
activeSessions.add(id)
|
||||
activeSessionId = id
|
||||
syncPlaceholderOwnership()
|
||||
}
|
||||
const handleSessionEnd = (event: Event): void => {
|
||||
@@ -49,11 +44,13 @@ export function installTerminalImeComposerPlaceholderMask(terminal: Terminal): I
|
||||
if (id === null) {
|
||||
return
|
||||
}
|
||||
activeSessions.delete(id)
|
||||
if (activeSessionId === id) {
|
||||
activeSessionId = null
|
||||
}
|
||||
syncPlaceholderOwnership()
|
||||
}
|
||||
const handleBlur = (): void => {
|
||||
activeSessions.clear()
|
||||
activeSessionId = null
|
||||
syncPlaceholderOwnership()
|
||||
}
|
||||
|
||||
@@ -61,14 +58,14 @@ export function installTerminalImeComposerPlaceholderMask(terminal: Terminal): I
|
||||
element.addEventListener(XTERM_COMPOSITION_SESSION_END_EVENT, handleSessionEnd)
|
||||
element.addEventListener('blur', handleBlur, true)
|
||||
const renderDisposable = terminal.onRender(() => {
|
||||
if (activeSessions.size > 0) {
|
||||
if (activeSessionId !== null) {
|
||||
syncPlaceholderOwnership()
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
activeSessions.clear()
|
||||
activeSessionId = null
|
||||
element.classList.remove(TERMINAL_IME_COMPOSER_PLACEHOLDER_CLASS)
|
||||
element.removeEventListener(XTERM_COMPOSITION_SESSION_START_EVENT, handleSessionStart)
|
||||
element.removeEventListener(XTERM_COMPOSITION_SESSION_END_EVENT, handleSessionEnd)
|
||||
|
||||
Reference in New Issue
Block a user