mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix: support Android terminal IME and scroll gestures (#5844)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -103,6 +103,10 @@ import {
|
||||
isTerminalLiveInputWithinByteLimit,
|
||||
scheduleTerminalLiveInputFocus
|
||||
} from '../../../../src/terminal/terminal-live-input'
|
||||
import {
|
||||
getTerminalCommandKeyboardType,
|
||||
getTerminalLiveInputKeyboardType
|
||||
} from '../../../../src/terminal/terminal-keyboard-type'
|
||||
import { normalizeTerminalTextInput } from '../../../../src/terminal/terminal-text-input-normalization'
|
||||
import { countTerminalGestureInputSequences } from '../../../../src/terminal/terminal-gesture-input'
|
||||
import { MobileBrowserPane } from '../../../../src/browser/MobileBrowserPane'
|
||||
@@ -4824,7 +4828,7 @@ export default function SessionScreen() {
|
||||
autoCorrect={false}
|
||||
spellCheck={false}
|
||||
smartInsertDelete={false}
|
||||
keyboardType={Platform.OS === 'ios' ? 'ascii-capable' : 'visible-password'}
|
||||
keyboardType={getTerminalLiveInputKeyboardType(Platform.OS)}
|
||||
returnKeyType="default"
|
||||
blurOnSubmit={false}
|
||||
editable={canSend}
|
||||
@@ -4855,15 +4859,12 @@ export default function SessionScreen() {
|
||||
autoCorrect={autocompleteEnabled}
|
||||
spellCheck={autocompleteEnabled}
|
||||
smartInsertDelete={false}
|
||||
// Why: the default keyboard exposes autocomplete/autocorrect;
|
||||
// ascii-capable (iOS) / visible-password (Android) suppress it.
|
||||
keyboardType={
|
||||
// Why: Android's default keyboard is required for CJK IME
|
||||
// composition; iOS can still use ASCII when autocomplete is off.
|
||||
keyboardType={getTerminalCommandKeyboardType(
|
||||
Platform.OS,
|
||||
autocompleteEnabled
|
||||
? 'default'
|
||||
: Platform.OS === 'ios'
|
||||
? 'ascii-capable'
|
||||
: 'visible-password'
|
||||
}
|
||||
)}
|
||||
returnKeyType="send"
|
||||
editable={canSend}
|
||||
onSubmitEditing={() => void handleSend()}
|
||||
|
||||
@@ -402,6 +402,9 @@ export const TerminalWebView = forwardRef<TerminalWebViewHandle, Props>(function
|
||||
originWhitelist={['*']}
|
||||
javaScriptEnabled
|
||||
scrollEnabled={false}
|
||||
// Why: Android parent gesture containers can intercept vertical drags
|
||||
// before the injected xterm scroll router sees them.
|
||||
nestedScrollEnabled
|
||||
scalesPageToFit={false}
|
||||
// Why: Android WebView defaults textZoom to the system font scale, inflating
|
||||
// xterm's DOM glyphs past its canvas-measured cell grid (#4579). iOS ignores it.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getTerminalCommandKeyboardType,
|
||||
getTerminalLiveInputKeyboardType
|
||||
} from './terminal-keyboard-type'
|
||||
|
||||
describe('terminal keyboard type', () => {
|
||||
it('uses the Android system keyboard for live terminal input', () => {
|
||||
expect(getTerminalLiveInputKeyboardType('android')).toBe('default')
|
||||
})
|
||||
|
||||
it('uses the Android system keyboard for buffered command input', () => {
|
||||
expect(getTerminalCommandKeyboardType('android', false)).toBe('default')
|
||||
expect(getTerminalCommandKeyboardType('android', true)).toBe('default')
|
||||
})
|
||||
|
||||
it('keeps the iOS ASCII keyboard when terminal autocomplete is disabled', () => {
|
||||
expect(getTerminalLiveInputKeyboardType('ios')).toBe('ascii-capable')
|
||||
expect(getTerminalCommandKeyboardType('ios', false)).toBe('ascii-capable')
|
||||
expect(getTerminalCommandKeyboardType('ios', true)).toBe('default')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
export type TerminalKeyboardPlatform = 'android' | 'ios' | 'web' | 'windows' | 'macos'
|
||||
export type TerminalKeyboardType = 'ascii-capable' | 'default'
|
||||
|
||||
export function getTerminalLiveInputKeyboardType(
|
||||
platform: TerminalKeyboardPlatform
|
||||
): TerminalKeyboardType {
|
||||
// Why: Android CJK IMEs need the normal system keyboard; password-style
|
||||
// input types suppress composition and break Chinese terminal input.
|
||||
return platform === 'ios' ? 'ascii-capable' : 'default'
|
||||
}
|
||||
|
||||
export function getTerminalCommandKeyboardType(
|
||||
platform: TerminalKeyboardPlatform,
|
||||
autocompleteEnabled: boolean
|
||||
): TerminalKeyboardType {
|
||||
if (autocompleteEnabled) {
|
||||
return 'default'
|
||||
}
|
||||
return platform === 'ios' ? 'ascii-capable' : 'default'
|
||||
}
|
||||
@@ -27,6 +27,26 @@ function sliceBetween(startPattern: string, endPattern: string): string {
|
||||
}
|
||||
|
||||
describe('TerminalWebView scroll routing', () => {
|
||||
it('keeps Android touch drags inside the terminal WebView', () => {
|
||||
expect(source).toContain('nestedScrollEnabled')
|
||||
})
|
||||
|
||||
it('maps a downward pull at the bottom to older scrollback rows', () => {
|
||||
expect(source).toContain('var deltaY = ts.lastY - y;')
|
||||
expect(source).toContain('smoothScrollOffsetY -= deltaY;')
|
||||
expect(source).toContain('var lines = Math.trunc(-smoothScrollOffsetY / effectiveCellH);')
|
||||
|
||||
const nextViewportY = simulateNormalBufferPull({
|
||||
baseY: 120,
|
||||
viewportY: 120,
|
||||
startY: 300,
|
||||
endY: 340,
|
||||
cellHeight: 20
|
||||
})
|
||||
|
||||
expect(nextViewportY).toBe(118)
|
||||
})
|
||||
|
||||
it('routes alternate-screen and mouse-aware scroll before smooth normal scroll', () => {
|
||||
expect(source).toContain(
|
||||
'return isWheelMouseTrackingMode(getMouseTrackingMode()) || isAlternateBufferActive();'
|
||||
@@ -233,3 +253,26 @@ describe('TerminalWebView scroll routing', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
function simulateNormalBufferPull({
|
||||
baseY,
|
||||
viewportY,
|
||||
startY,
|
||||
endY,
|
||||
cellHeight
|
||||
}: {
|
||||
baseY: number
|
||||
viewportY: number
|
||||
startY: number
|
||||
endY: number
|
||||
cellHeight: number
|
||||
}): number {
|
||||
const deltaY = startY - endY
|
||||
if (deltaY > 0 ? viewportY >= baseY : viewportY <= 0) {
|
||||
return viewportY
|
||||
}
|
||||
const smoothScrollOffsetY = -deltaY
|
||||
const lines = Math.trunc(-smoothScrollOffsetY / cellHeight)
|
||||
const applied = Math.max(lines, -viewportY)
|
||||
return viewportY + applied
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user