diff --git a/app/tabs/sessions/Sessions.tsx b/app/tabs/sessions/Sessions.tsx index e679592..5e00e12 100644 --- a/app/tabs/sessions/Sessions.tsx +++ b/app/tabs/sessions/Sessions.tsx @@ -2,15 +2,9 @@ import React, { useState, useEffect, useRef, useCallback } from "react"; import { View, Text, - ScrollView, - TouchableOpacity, - StyleSheet, Keyboard, - KeyboardAvoidingView, Platform, TextInput, - TouchableWithoutFeedback, - Pressable, Dimensions, BackHandler, AppState, @@ -40,16 +34,17 @@ import { import TabBar from "@/app/tabs/sessions/navigation/TabBar"; import BottomToolbar from "@/app/tabs/sessions/terminal/keyboard/BottomToolbar"; import KeyboardBar from "@/app/tabs/sessions/terminal/keyboard/KeyboardBar"; -import { ArrowLeft } from "lucide-react-native"; import { useOrientation } from "@/app/utils/orientation"; import { getMaxKeyboardHeight, getTabBarHeight } from "@/app/utils/responsive"; -import { - BACKGROUNDS, - BORDER_COLORS, - BORDERS, -} from "@/app/constants/designTokens"; +import { BACKGROUNDS, BORDER_COLORS } from "@/app/constants/designTokens"; import { addKeyCommandListener } from "@/modules/hardware-keyboard"; +type ActiveModifiers = { + ctrl: boolean; + alt: boolean; + shift: boolean; +}; + export default function Sessions() { const insets = useSafeAreaInsets(); const router = useRouter(); @@ -84,6 +79,7 @@ export default function Sessions() { const [activeModifiers, setActiveModifiers] = useState({ ctrl: false, alt: false, + shift: false, }); const [screenDimensions, setScreenDimensions] = useState( Dimensions.get("window"), @@ -516,12 +512,9 @@ export default function Sessions() { } }; - const handleModifierChange = useCallback( - (modifiers: { ctrl: boolean; alt: boolean }) => { - setActiveModifiers(modifiers); - }, - [], - ); + const handleModifierChange = useCallback((modifiers: ActiveModifiers) => { + setActiveModifiers(modifiers); + }, []); const activeSession = sessions.find( (session) => session.id === activeSessionId, @@ -952,7 +945,7 @@ export default function Sessions() { finalKey = "\x7f"; break; case "Tab": - finalKey = "\t"; + finalKey = activeModifiers.shift ? "\x1b[Z" : "\t"; break; case "Escape": finalKey = "\x1b"; @@ -1025,10 +1018,12 @@ export default function Sessions() { if (activeModifiers.ctrl) { finalKey = String.fromCharCode(key.charCodeAt(0) & 0x1f); } else if (activeModifiers.alt) { - finalKey = `\x1b${key}`; + finalKey = `\x1b${activeModifiers.shift ? key.toUpperCase() : key}`; } else { - finalKey = key; - dictationSentRef.current = hiddenInputValue + key; + finalKey = activeModifiers.shift + ? key.toUpperCase() + : key; + dictationSentRef.current = hiddenInputValue + finalKey; } } } diff --git a/app/tabs/sessions/terminal/keyboard/CustomKeyboard.tsx b/app/tabs/sessions/terminal/keyboard/CustomKeyboard.tsx index 701d70a..e3e38f6 100644 --- a/app/tabs/sessions/terminal/keyboard/CustomKeyboard.tsx +++ b/app/tabs/sessions/terminal/keyboard/CustomKeyboard.tsx @@ -1,11 +1,11 @@ -import React from "react"; +import React, { useState } from "react"; import { View, ScrollView, Text } from "react-native"; import * as Clipboard from "expo-clipboard"; import { TerminalHandle } from "../Terminal"; import KeyboardKey from "./KeyboardKey"; import { useKeyboardCustomization } from "@/app/contexts/KeyboardCustomizationContext"; import { KeyConfig } from "@/types/keyboard"; -import { BORDER_COLORS, SPACING } from "@/app/constants/designTokens"; +import { BORDER_COLORS } from "@/app/constants/designTokens"; interface CustomKeyboardProps { terminalRef: React.RefObject; @@ -17,10 +17,11 @@ interface CustomKeyboardProps { export default function CustomKeyboard({ terminalRef, isVisible, - keyboardHeight, + keyboardHeight: _keyboardHeight, isKeyboardIntentionallyHidden = false, }: CustomKeyboardProps) { const { config } = useKeyboardCustomization(); + const [shiftPressed, setShiftPressed] = useState(false); if (!isVisible) return null; @@ -50,7 +51,13 @@ export default function CustomKeyboard({ case "tab": case "complete": case "comp": - sendKey("\t"); + sendKey(shiftPressed ? "\x1b[Z" : "\t"); + break; + case "shiftTab": + sendKey("\x1b[Z"); + break; + case "shift": + setShiftPressed((current) => !current); break; case "arrowUp": case "history": @@ -98,7 +105,7 @@ export default function CustomKeyboard({ if (clipboardContent) { sendKey(clipboardContent); } - } catch (error) {} + } catch {} }; const { rows } = config.fullKeyboard; @@ -128,8 +135,6 @@ export default function CustomKeyboard({ return baseStyle; }; - const safeKeyboardHeight = Math.max(200, Math.min(keyboardHeight, 500)); - return ( {row.label && ( - + {row.label} )} handleKeyPress(key)} style={getKeyStyle(key)} + isModifier={key.isModifier || key.id === "shift"} + isActive={key.id === "shift" && shiftPressed} keySize={config.settings.keySize} hapticFeedback={config.settings.hapticFeedback} /> @@ -169,7 +176,7 @@ export default function CustomKeyboard({ {rowIndex < visibleRows.length - 1 && ( - + + Customize in Settings diff --git a/app/tabs/sessions/terminal/keyboard/KeyDefinitions.ts b/app/tabs/sessions/terminal/keyboard/KeyDefinitions.ts index 38a5789..5fffebb 100644 --- a/app/tabs/sessions/terminal/keyboard/KeyDefinitions.ts +++ b/app/tabs/sessions/terminal/keyboard/KeyDefinitions.ts @@ -1,6 +1,5 @@ import { KeyConfig, - KeyboardRow, PresetDefinition, TopBarConfig, FullKeyboardConfig, @@ -37,6 +36,14 @@ export const ALL_KEYS: Record = { isModifier: true, description: "Alt modifier (toggle)", }, + shift: { + id: "shift", + label: "Shift", + value: "", + category: "modifier", + isModifier: true, + description: "Shift modifier (toggle)", + }, arrowUp: { id: "arrowUp", @@ -465,6 +472,13 @@ export const ALL_KEYS: Record = { category: "shortcut", description: "Alt+D (delete word)", }, + shiftTab: { + id: "shiftTab", + label: "Shift+Tab", + value: "\x1b[Z", + category: "shortcut", + description: "Shift+Tab (reverse tab)", + }, paste: { id: "paste", @@ -496,6 +510,7 @@ const defaultTopBar: TopBarConfig = { ALL_KEYS.tab, ALL_KEYS.ctrl, ALL_KEYS.alt, + ALL_KEYS.shift, ALL_KEYS.arrowUp, ALL_KEYS.arrowDown, ALL_KEYS.arrowLeft, @@ -654,6 +669,7 @@ const defaultFullKeyboard: FullKeyboardConfig = { ALL_KEYS.ctrlR, ALL_KEYS.ctrlY, ALL_KEYS.altF, + ALL_KEYS.shiftTab, ], }, ], @@ -666,6 +682,7 @@ const minimalTopBar: TopBarConfig = { ALL_KEYS.tab, ALL_KEYS.ctrl, ALL_KEYS.alt, + ALL_KEYS.shift, ALL_KEYS.arrowUp, ALL_KEYS.arrowDown, ALL_KEYS.arrowLeft, @@ -718,7 +735,13 @@ const minimalFullKeyboard: FullKeyboardConfig = { category: "shortcut", label: "Shortcuts", visible: true, - keys: [ALL_KEYS.ctrlC, ALL_KEYS.ctrlD, ALL_KEYS.ctrlL, ALL_KEYS.ctrlZ], + keys: [ + ALL_KEYS.ctrlC, + ALL_KEYS.ctrlD, + ALL_KEYS.ctrlL, + ALL_KEYS.ctrlZ, + ALL_KEYS.shiftTab, + ], }, ], }; @@ -729,6 +752,7 @@ const developerTopBar: TopBarConfig = { ALL_KEYS.tab, ALL_KEYS.ctrl, ALL_KEYS.alt, + ALL_KEYS.shift, ALL_KEYS.arrowUp, ALL_KEYS.arrowDown, ALL_KEYS.arrowLeft, @@ -841,6 +865,7 @@ const developerFullKeyboard: FullKeyboardConfig = { ALL_KEYS.ctrlU, ALL_KEYS.ctrlR, ALL_KEYS.ctrlW, + ALL_KEYS.shiftTab, ], }, ], @@ -853,6 +878,7 @@ const sysadminTopBar: TopBarConfig = { ALL_KEYS.tab, ALL_KEYS.ctrl, ALL_KEYS.alt, + ALL_KEYS.shift, ALL_KEYS.arrowUp, ALL_KEYS.arrowDown, ALL_KEYS.arrowLeft, @@ -940,6 +966,7 @@ const sysadminFullKeyboard: FullKeyboardConfig = { ALL_KEYS.ctrlL, ALL_KEYS.ctrlR, ALL_KEYS.ctrlU, + ALL_KEYS.shiftTab, ], }, ], @@ -952,6 +979,7 @@ const compactTopBar: TopBarConfig = { ALL_KEYS.tab, ALL_KEYS.ctrl, ALL_KEYS.alt, + ALL_KEYS.shift, ALL_KEYS.arrowUp, ALL_KEYS.arrowDown, ALL_KEYS.arrowLeft, @@ -1081,6 +1109,7 @@ const compactFullKeyboard: FullKeyboardConfig = { ALL_KEYS.ctrlU, ALL_KEYS.ctrlW, ALL_KEYS.ctrlR, + ALL_KEYS.shiftTab, ], }, ], diff --git a/app/tabs/sessions/terminal/keyboard/KeyboardBar.tsx b/app/tabs/sessions/terminal/keyboard/KeyboardBar.tsx index 9a44ae1..a144e82 100644 --- a/app/tabs/sessions/terminal/keyboard/KeyboardBar.tsx +++ b/app/tabs/sessions/terminal/keyboard/KeyboardBar.tsx @@ -1,22 +1,21 @@ import React, { useState, useEffect } from "react"; -import { View, ScrollView, Text, Platform } from "react-native"; +import { View, ScrollView } from "react-native"; import * as Clipboard from "expo-clipboard"; import { TerminalHandle } from "../Terminal"; import KeyboardKey from "./KeyboardKey"; import { useKeyboardCustomization } from "@/app/contexts/KeyboardCustomizationContext"; import { KeyConfig } from "@/types/keyboard"; -import { useKeyboard } from "@/app/contexts/KeyboardContext"; import { useOrientation } from "@/app/utils/orientation"; -import { - BORDERS, - BORDER_COLORS, - BACKGROUNDS, -} from "@/app/constants/designTokens"; +import { BORDER_COLORS, BACKGROUNDS } from "@/app/constants/designTokens"; interface KeyboardBarProps { terminalRef: React.RefObject; isVisible: boolean; - onModifierChange?: (modifiers: { ctrl: boolean; alt: boolean }) => void; + onModifierChange?: (modifiers: { + ctrl: boolean; + alt: boolean; + shift: boolean; + }) => void; isKeyboardIntentionallyHidden?: boolean; } @@ -27,12 +26,10 @@ export default function KeyboardBar({ isKeyboardIntentionallyHidden = false, }: KeyboardBarProps) { const { config } = useKeyboardCustomization(); - const { keyboardHeight, isKeyboardVisible } = useKeyboard(); const { isLandscape } = useOrientation(); const [ctrlPressed, setCtrlPressed] = useState(false); const [altPressed, setAltPressed] = useState(false); - - if (!isVisible) return null; + const [shiftPressed, setShiftPressed] = useState(false); const sendKey = (key: string) => { terminalRef.current?.sendInput(key); @@ -48,7 +45,10 @@ export default function KeyboardBar({ case "tab": case "complete": case "comp": - sendKey("\t"); + sendKey(shiftPressed ? "\x1b[Z" : "\t"); + break; + case "shiftTab": + sendKey("\x1b[Z"); break; case "arrowUp": case "history": @@ -78,10 +78,10 @@ export default function KeyboardBar({ if (clipboardContent) { sendKey(clipboardContent); } - } catch (error) {} + } catch {} }; - const toggleModifier = (modifier: "ctrl" | "alt") => { + const toggleModifier = (modifier: "ctrl" | "alt" | "shift") => { switch (modifier) { case "ctrl": setCtrlPressed(!ctrlPressed); @@ -89,20 +89,33 @@ export default function KeyboardBar({ case "alt": setAltPressed(!altPressed); break; + case "shift": + setShiftPressed(!shiftPressed); + break; } }; useEffect(() => { if (onModifierChange) { - onModifierChange({ ctrl: ctrlPressed, alt: altPressed }); + onModifierChange({ + ctrl: ctrlPressed, + alt: altPressed, + shift: shiftPressed, + }); } - }, [ctrlPressed, altPressed]); + }, [ctrlPressed, altPressed, shiftPressed, onModifierChange]); + + if (!isVisible) return null; const renderKey = (keyConfig: KeyConfig, index: number) => { const isModifier = - keyConfig.isModifier || keyConfig.id === "ctrl" || keyConfig.id === "alt"; + keyConfig.isModifier || + keyConfig.id === "ctrl" || + keyConfig.id === "alt" || + keyConfig.id === "shift"; const isCtrl = keyConfig.id === "ctrl"; const isAlt = keyConfig.id === "alt"; + const isShift = keyConfig.id === "shift"; return ( @@ -150,7 +172,7 @@ export default function KeyboardBar({ <> {pinnedKeys.map((key, index) => renderKey(key, index))}