| 1 | """ |
| 2 | Key sequence definitions for terminal input simulation. |
| 3 | |
| 4 | These escape sequences follow standard xterm/VT100 conventions. |
| 5 | """ |
| 6 | |
| 7 | # Arrow keys (CSI sequences) |
| 8 | ARROW_UP = "\x1b[A" |
| 9 | ARROW_DOWN = "\x1b[B" |
| 10 | ARROW_RIGHT = "\x1b[C" |
| 11 | ARROW_LEFT = "\x1b[D" |
| 12 | |
| 13 | # Shift-modified arrow / nav keys (xterm modifier 2 = Shift) |
| 14 | # Used for native text selection (shift phase, Sprint 1+). |
| 15 | SHIFT_ARROW_UP = "\x1b[1;2A" |
| 16 | SHIFT_ARROW_DOWN = "\x1b[1;2B" |
| 17 | SHIFT_ARROW_RIGHT = "\x1b[1;2C" |
| 18 | SHIFT_ARROW_LEFT = "\x1b[1;2D" |
| 19 | SHIFT_HOME = "\x1b[1;2H" |
| 20 | SHIFT_END = "\x1b[1;2F" |
| 21 | |
| 22 | # Ctrl+Shift arrow (xterm modifier 6) — word-wise selection extension |
| 23 | CTRL_SHIFT_ARROW_RIGHT = "\x1b[1;6C" |
| 24 | CTRL_SHIFT_ARROW_LEFT = "\x1b[1;6D" |
| 25 | |
| 26 | # Alt+Shift letter (ESC + uppercase) — emacs-native word-wise selection |
| 27 | ALT_SHIFT_B = "\x1bB" |
| 28 | ALT_SHIFT_F = "\x1bF" |
| 29 | |
| 30 | # Alt+Shift arrow (xterm modifier 4) — fortsh binds these to dir history |
| 31 | ALT_SHIFT_UP = "\x1b[1;4A" |
| 32 | ALT_SHIFT_DOWN = "\x1b[1;4B" |
| 33 | ALT_SHIFT_LEFT = "\x1b[1;4D" |
| 34 | ALT_SHIFT_RIGHT = "\x1b[1;4C" |
| 35 | |
| 36 | # Control keys (ASCII control characters) |
| 37 | CTRL_A = "\x01" # Beginning of line |
| 38 | CTRL_B = "\x02" # Back one character |
| 39 | CTRL_C = "\x03" # Interrupt (SIGINT) |
| 40 | CTRL_D = "\x04" # Delete char / EOF |
| 41 | CTRL_E = "\x05" # End of line |
| 42 | CTRL_F = "\x06" # Forward one character |
| 43 | CTRL_G = "\x07" # Cancel |
| 44 | CTRL_H = "\x08" # Backspace (alternative) |
| 45 | CTRL_I = "\x09" # Tab |
| 46 | CTRL_J = "\x0a" # Newline |
| 47 | CTRL_K = "\x0b" # Kill to end of line |
| 48 | CTRL_L = "\x0c" # Clear screen |
| 49 | CTRL_M = "\x0d" # Carriage return (Enter) |
| 50 | CTRL_N = "\x0e" # Next history |
| 51 | CTRL_O = "\x0f" # Operate and get next |
| 52 | CTRL_P = "\x10" # Previous history |
| 53 | CTRL_Q = "\x11" # Resume output |
| 54 | CTRL_R = "\x12" # Reverse search |
| 55 | CTRL_S = "\x13" # Forward search / Suspend output |
| 56 | CTRL_T = "\x14" # Transpose characters |
| 57 | CTRL_U = "\x15" # Kill to beginning of line |
| 58 | CTRL_V = "\x16" # Paste from clipboard / kill buffer |
| 59 | CTRL_W = "\x17" # Kill word backward / cut selection |
| 60 | CTRL_X = "\x18" # Cut selection / process kill mode |
| 61 | CTRL_Y = "\x19" # Yank |
| 62 | CTRL_Z = "\x1a" # Suspend (SIGTSTP) |
| 63 | |
| 64 | # Alt/Meta keys (ESC + key) |
| 65 | ALT_B = "\x1bb" # Back one word |
| 66 | ALT_C = "\x1bc" # Capitalize word |
| 67 | ALT_D = "\x1bd" # Delete word forward |
| 68 | ALT_F = "\x1bf" # Forward one word |
| 69 | ALT_L = "\x1bl" # Lowercase word |
| 70 | ALT_T = "\x1bt" # Transpose words |
| 71 | ALT_U = "\x1bu" # Uppercase word |
| 72 | ALT_W = "\x1bw" # Copy selection (shift phase) / Accept word from suggestion |
| 73 | ALT_Y = "\x1by" # Yank pop |
| 74 | ALT_DOT = "\x1b." # Insert last argument |
| 75 | ALT_BACKSPACE = "\x1b\x7f" # Delete word backward |
| 76 | |
| 77 | # Special keys |
| 78 | TAB = "\t" |
| 79 | ENTER = "\r" |
| 80 | NEWLINE = "\n" |
| 81 | BACKSPACE = "\x7f" |
| 82 | ESCAPE = "\x1b" |
| 83 | |
| 84 | # Extended keys (xterm sequences) |
| 85 | DELETE = "\x1b[3~" |
| 86 | HOME = "\x1b[H" |
| 87 | END = "\x1b[F" |
| 88 | PAGE_UP = "\x1b[5~" |
| 89 | PAGE_DOWN = "\x1b[6~" |
| 90 | INSERT = "\x1b[2~" |
| 91 | |
| 92 | # Alternative Home/End sequences (some terminals use these) |
| 93 | HOME_ALT = "\x1b[1~" |
| 94 | END_ALT = "\x1b[4~" |
| 95 | |
| 96 | # Function keys |
| 97 | F1 = "\x1bOP" |
| 98 | F2 = "\x1bOQ" |
| 99 | F3 = "\x1bOR" |
| 100 | F4 = "\x1bOS" |
| 101 | F5 = "\x1b[15~" |
| 102 | F6 = "\x1b[17~" |
| 103 | F7 = "\x1b[18~" |
| 104 | F8 = "\x1b[19~" |
| 105 | F9 = "\x1b[20~" |
| 106 | F10 = "\x1b[21~" |
| 107 | F11 = "\x1b[23~" |
| 108 | F12 = "\x1b[24~" |
| 109 | |
| 110 | # Key name to sequence mapping (for YAML test specs) |
| 111 | KEYS = { |
| 112 | # Arrow keys |
| 113 | "Up": ARROW_UP, |
| 114 | "Down": ARROW_DOWN, |
| 115 | "Right": ARROW_RIGHT, |
| 116 | "Left": ARROW_LEFT, |
| 117 | |
| 118 | # Shift-modified navigation (for native text selection) |
| 119 | "S-Up": SHIFT_ARROW_UP, |
| 120 | "S-Down": SHIFT_ARROW_DOWN, |
| 121 | "S-Right": SHIFT_ARROW_RIGHT, |
| 122 | "S-Left": SHIFT_ARROW_LEFT, |
| 123 | "S-Home": SHIFT_HOME, |
| 124 | "S-End": SHIFT_END, |
| 125 | |
| 126 | # Ctrl+Shift navigation (word-wise selection) |
| 127 | "C-S-Right": CTRL_SHIFT_ARROW_RIGHT, |
| 128 | "C-S-Left": CTRL_SHIFT_ARROW_LEFT, |
| 129 | |
| 130 | # Alt+Shift letter (emacs word-wise selection) |
| 131 | "M-B": ALT_SHIFT_B, |
| 132 | "M-F": ALT_SHIFT_F, |
| 133 | |
| 134 | # Alt+Shift arrows (fortsh directory history, NOT selection) |
| 135 | "M-S-Up": ALT_SHIFT_UP, |
| 136 | "M-S-Down": ALT_SHIFT_DOWN, |
| 137 | "M-S-Left": ALT_SHIFT_LEFT, |
| 138 | "M-S-Right": ALT_SHIFT_RIGHT, |
| 139 | |
| 140 | # Control keys |
| 141 | "C-a": CTRL_A, |
| 142 | "C-b": CTRL_B, |
| 143 | "C-c": CTRL_C, |
| 144 | "C-d": CTRL_D, |
| 145 | "C-e": CTRL_E, |
| 146 | "C-f": CTRL_F, |
| 147 | "C-g": CTRL_G, |
| 148 | "C-h": CTRL_H, |
| 149 | "C-k": CTRL_K, |
| 150 | "C-l": CTRL_L, |
| 151 | "C-n": CTRL_N, |
| 152 | "C-p": CTRL_P, |
| 153 | "C-r": CTRL_R, |
| 154 | "C-s": CTRL_S, |
| 155 | "C-t": CTRL_T, |
| 156 | "C-u": CTRL_U, |
| 157 | "C-v": CTRL_V, |
| 158 | "C-w": CTRL_W, |
| 159 | "C-x": CTRL_X, |
| 160 | "C-y": CTRL_Y, |
| 161 | "C-z": CTRL_Z, |
| 162 | |
| 163 | # Alt/Meta keys |
| 164 | "M-b": ALT_B, |
| 165 | "M-c": ALT_C, |
| 166 | "M-d": ALT_D, |
| 167 | "M-f": ALT_F, |
| 168 | "M-l": ALT_L, |
| 169 | "M-t": ALT_T, |
| 170 | "M-u": ALT_U, |
| 171 | "M-w": ALT_W, |
| 172 | "M-y": ALT_Y, |
| 173 | "M-.": ALT_DOT, |
| 174 | "M-Backspace": ALT_BACKSPACE, |
| 175 | |
| 176 | # Special keys |
| 177 | "Tab": TAB, |
| 178 | "Enter": ENTER, |
| 179 | "Return": ENTER, |
| 180 | "Backspace": BACKSPACE, |
| 181 | "Delete": DELETE, |
| 182 | "Home": HOME, |
| 183 | "End": END, |
| 184 | "PageUp": PAGE_UP, |
| 185 | "PageDown": PAGE_DOWN, |
| 186 | "Insert": INSERT, |
| 187 | "Escape": ESCAPE, |
| 188 | "Esc": ESCAPE, |
| 189 | |
| 190 | # Function keys |
| 191 | "F1": F1, |
| 192 | "F2": F2, |
| 193 | "F3": F3, |
| 194 | "F4": F4, |
| 195 | "F5": F5, |
| 196 | "F6": F6, |
| 197 | "F7": F7, |
| 198 | "F8": F8, |
| 199 | "F9": F9, |
| 200 | "F10": F10, |
| 201 | "F11": F11, |
| 202 | "F12": F12, |
| 203 | } |
| 204 | |
| 205 | |
| 206 | def get_key(name: str) -> str: |
| 207 | """ |
| 208 | Get the escape sequence for a key name. |
| 209 | |
| 210 | Args: |
| 211 | name: Key name (e.g., "Up", "C-a", "M-f", "Enter") |
| 212 | |
| 213 | Returns: |
| 214 | The escape sequence for that key |
| 215 | |
| 216 | Raises: |
| 217 | KeyError: If the key name is not recognized |
| 218 | """ |
| 219 | if name not in KEYS: |
| 220 | raise KeyError(f"Unknown key: {name}. Available keys: {sorted(KEYS.keys())}") |
| 221 | return KEYS[name] |
| 222 | |
| 223 | |
| 224 | def key_sequence(*keys: str) -> str: |
| 225 | """ |
| 226 | Build a sequence of multiple keys. |
| 227 | |
| 228 | Args: |
| 229 | *keys: Key names to combine |
| 230 | |
| 231 | Returns: |
| 232 | Combined escape sequence string |
| 233 | |
| 234 | Example: |
| 235 | key_sequence("C-a", "C-k") # Move to beginning, kill to end |
| 236 | """ |
| 237 | return "".join(get_key(k) for k in keys) |