diff --git a/apps/mobile/modules/t3-terminal/ios/T3TerminalModule.swift b/apps/mobile/modules/t3-terminal/ios/T3TerminalModule.swift index 39e1874d5d7..1c8978d4e5b 100644 --- a/apps/mobile/modules/t3-terminal/ios/T3TerminalModule.swift +++ b/apps/mobile/modules/t3-terminal/ios/T3TerminalModule.swift @@ -7,7 +7,7 @@ public class T3TerminalModule: Module { // Bumped when native hardware-keyboard handling changes; surfaced in the JS debug // logs so a stale native binary is distinguishable from a broken key pipeline. Constants([ - "hardwareKeyRevision": 2, + "hardwareKeyRevision": 3, ]) View(T3TerminalView.self) { diff --git a/apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift b/apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift index 14cdb4b7802..191d08db28d 100644 --- a/apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift +++ b/apps/mobile/modules/t3-terminal/ios/T3TerminalView.swift @@ -119,6 +119,21 @@ private enum TerminalHardwareKeyEncoder { } } +private enum TerminalInputSequence { + /// Terminal Enter is carriage return. Sending line feed instead is Ctrl+J, + /// which raw-mode TUIs may interpret as the literal J key. + static let carriageReturn = "\r" + + static func normalizingReturn(_ input: String) -> String { + switch input { + case "\n", "\r\n": + return carriageReturn + default: + return input + } + } +} + private final class TerminalInputField: UITextField { var onDeleteBackward: (() -> Void)? var onInsert: ((String) -> Void)? @@ -352,7 +367,9 @@ public final class T3TerminalView: ExpoView, UITextFieldDelegate { public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if !string.isEmpty { - emitInput(string) + // Some software keyboards deliver Return through this delegate instead of + // textFieldShouldReturn, so normalize that path too. + emitInput(TerminalInputSequence.normalizingReturn(string)) return false } @@ -360,7 +377,7 @@ public final class T3TerminalView: ExpoView, UITextFieldDelegate { } public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - emitInput("\n") + emitInput(TerminalInputSequence.carriageReturn) textField.text = "" return false } diff --git a/apps/mobile/src/features/terminal/NativeTerminalSurface.tsx b/apps/mobile/src/features/terminal/NativeTerminalSurface.tsx index 68d214193bd..69037514d0d 100644 --- a/apps/mobile/src/features/terminal/NativeTerminalSurface.tsx +++ b/apps/mobile/src/features/terminal/NativeTerminalSurface.tsx @@ -145,7 +145,8 @@ const FallbackTerminalSurface = memo(function FallbackTerminalSurface(props: Ter onSubmitEditing={(event) => { const text = event.nativeEvent.text; if (text.length > 0) { - props.onInput(`${text}\n`); + // Terminal Enter is CR. LF is Ctrl+J and raw-mode TUIs can treat it as J. + props.onInput(`${text}\r`); } }} />