source dump of claude code
at main 319 lines 8.7 kB view raw
1/** 2 * CSI (Control Sequence Introducer) Types 3 * 4 * Enums and types for CSI command parameters. 5 */ 6 7import { ESC, ESC_TYPE, SEP } from './ansi.js' 8 9export const CSI_PREFIX = ESC + String.fromCharCode(ESC_TYPE.CSI) 10 11/** 12 * CSI parameter byte ranges 13 */ 14export const CSI_RANGE = { 15 PARAM_START: 0x30, 16 PARAM_END: 0x3f, 17 INTERMEDIATE_START: 0x20, 18 INTERMEDIATE_END: 0x2f, 19 FINAL_START: 0x40, 20 FINAL_END: 0x7e, 21} as const 22 23/** Check if a byte is a CSI parameter byte */ 24export function isCSIParam(byte: number): boolean { 25 return byte >= CSI_RANGE.PARAM_START && byte <= CSI_RANGE.PARAM_END 26} 27 28/** Check if a byte is a CSI intermediate byte */ 29export function isCSIIntermediate(byte: number): boolean { 30 return ( 31 byte >= CSI_RANGE.INTERMEDIATE_START && byte <= CSI_RANGE.INTERMEDIATE_END 32 ) 33} 34 35/** Check if a byte is a CSI final byte (@ through ~) */ 36export function isCSIFinal(byte: number): boolean { 37 return byte >= CSI_RANGE.FINAL_START && byte <= CSI_RANGE.FINAL_END 38} 39 40/** 41 * Generate a CSI sequence: ESC [ p1;p2;...;pN final 42 * Single arg: treated as raw body 43 * Multiple args: last is final byte, rest are params joined by ; 44 */ 45export function csi(...args: (string | number)[]): string { 46 if (args.length === 0) return CSI_PREFIX 47 if (args.length === 1) return `${CSI_PREFIX}${args[0]}` 48 const params = args.slice(0, -1) 49 const final = args[args.length - 1] 50 return `${CSI_PREFIX}${params.join(SEP)}${final}` 51} 52 53/** 54 * CSI final bytes - the command identifier 55 */ 56export const CSI = { 57 // Cursor movement 58 CUU: 0x41, // A - Cursor Up 59 CUD: 0x42, // B - Cursor Down 60 CUF: 0x43, // C - Cursor Forward 61 CUB: 0x44, // D - Cursor Back 62 CNL: 0x45, // E - Cursor Next Line 63 CPL: 0x46, // F - Cursor Previous Line 64 CHA: 0x47, // G - Cursor Horizontal Absolute 65 CUP: 0x48, // H - Cursor Position 66 CHT: 0x49, // I - Cursor Horizontal Tab 67 VPA: 0x64, // d - Vertical Position Absolute 68 HVP: 0x66, // f - Horizontal Vertical Position 69 70 // Erase 71 ED: 0x4a, // J - Erase in Display 72 EL: 0x4b, // K - Erase in Line 73 ECH: 0x58, // X - Erase Character 74 75 // Insert/Delete 76 IL: 0x4c, // L - Insert Lines 77 DL: 0x4d, // M - Delete Lines 78 ICH: 0x40, // @ - Insert Characters 79 DCH: 0x50, // P - Delete Characters 80 81 // Scroll 82 SU: 0x53, // S - Scroll Up 83 SD: 0x54, // T - Scroll Down 84 85 // Modes 86 SM: 0x68, // h - Set Mode 87 RM: 0x6c, // l - Reset Mode 88 89 // SGR 90 SGR: 0x6d, // m - Select Graphic Rendition 91 92 // Other 93 DSR: 0x6e, // n - Device Status Report 94 DECSCUSR: 0x71, // q - Set Cursor Style (with space intermediate) 95 DECSTBM: 0x72, // r - Set Top and Bottom Margins 96 SCOSC: 0x73, // s - Save Cursor Position 97 SCORC: 0x75, // u - Restore Cursor Position 98 CBT: 0x5a, // Z - Cursor Backward Tabulation 99} as const 100 101/** 102 * Erase in Display regions (ED command parameter) 103 */ 104export const ERASE_DISPLAY = ['toEnd', 'toStart', 'all', 'scrollback'] as const 105 106/** 107 * Erase in Line regions (EL command parameter) 108 */ 109export const ERASE_LINE_REGION = ['toEnd', 'toStart', 'all'] as const 110 111/** 112 * Cursor styles (DECSCUSR) 113 */ 114export type CursorStyle = 'block' | 'underline' | 'bar' 115 116export const CURSOR_STYLES: Array<{ style: CursorStyle; blinking: boolean }> = [ 117 { style: 'block', blinking: true }, // 0 - default 118 { style: 'block', blinking: true }, // 1 119 { style: 'block', blinking: false }, // 2 120 { style: 'underline', blinking: true }, // 3 121 { style: 'underline', blinking: false }, // 4 122 { style: 'bar', blinking: true }, // 5 123 { style: 'bar', blinking: false }, // 6 124] 125 126// Cursor movement generators 127 128/** Move cursor up n lines (CSI n A) */ 129export function cursorUp(n = 1): string { 130 return n === 0 ? '' : csi(n, 'A') 131} 132 133/** Move cursor down n lines (CSI n B) */ 134export function cursorDown(n = 1): string { 135 return n === 0 ? '' : csi(n, 'B') 136} 137 138/** Move cursor forward n columns (CSI n C) */ 139export function cursorForward(n = 1): string { 140 return n === 0 ? '' : csi(n, 'C') 141} 142 143/** Move cursor back n columns (CSI n D) */ 144export function cursorBack(n = 1): string { 145 return n === 0 ? '' : csi(n, 'D') 146} 147 148/** Move cursor to column n (1-indexed) (CSI n G) */ 149export function cursorTo(col: number): string { 150 return csi(col, 'G') 151} 152 153/** Move cursor to column 1 (CSI G) */ 154export const CURSOR_LEFT = csi('G') 155 156/** Move cursor to row, col (1-indexed) (CSI row ; col H) */ 157export function cursorPosition(row: number, col: number): string { 158 return csi(row, col, 'H') 159} 160 161/** Move cursor to home position (CSI H) */ 162export const CURSOR_HOME = csi('H') 163 164/** 165 * Move cursor relative to current position 166 * Positive x = right, negative x = left 167 * Positive y = down, negative y = up 168 */ 169export function cursorMove(x: number, y: number): string { 170 let result = '' 171 // Horizontal first (matches ansi-escapes behavior) 172 if (x < 0) { 173 result += cursorBack(-x) 174 } else if (x > 0) { 175 result += cursorForward(x) 176 } 177 // Then vertical 178 if (y < 0) { 179 result += cursorUp(-y) 180 } else if (y > 0) { 181 result += cursorDown(y) 182 } 183 return result 184} 185 186// Save/restore cursor position 187 188/** Save cursor position (CSI s) */ 189export const CURSOR_SAVE = csi('s') 190 191/** Restore cursor position (CSI u) */ 192export const CURSOR_RESTORE = csi('u') 193 194// Erase generators 195 196/** Erase from cursor to end of line (CSI K) */ 197export function eraseToEndOfLine(): string { 198 return csi('K') 199} 200 201/** Erase from cursor to start of line (CSI 1 K) */ 202export function eraseToStartOfLine(): string { 203 return csi(1, 'K') 204} 205 206/** Erase entire line (CSI 2 K) */ 207export function eraseLine(): string { 208 return csi(2, 'K') 209} 210 211/** Erase entire line - constant form */ 212export const ERASE_LINE = csi(2, 'K') 213 214/** Erase from cursor to end of screen (CSI J) */ 215export function eraseToEndOfScreen(): string { 216 return csi('J') 217} 218 219/** Erase from cursor to start of screen (CSI 1 J) */ 220export function eraseToStartOfScreen(): string { 221 return csi(1, 'J') 222} 223 224/** Erase entire screen (CSI 2 J) */ 225export function eraseScreen(): string { 226 return csi(2, 'J') 227} 228 229/** Erase entire screen - constant form */ 230export const ERASE_SCREEN = csi(2, 'J') 231 232/** Erase scrollback buffer (CSI 3 J) */ 233export const ERASE_SCROLLBACK = csi(3, 'J') 234 235/** 236 * Erase n lines starting from cursor line, moving cursor up 237 * This erases each line and moves up, ending at column 1 238 */ 239export function eraseLines(n: number): string { 240 if (n <= 0) return '' 241 let result = '' 242 for (let i = 0; i < n; i++) { 243 result += ERASE_LINE 244 if (i < n - 1) { 245 result += cursorUp(1) 246 } 247 } 248 result += CURSOR_LEFT 249 return result 250} 251 252// Scroll 253 254/** Scroll up n lines (CSI n S) */ 255export function scrollUp(n = 1): string { 256 return n === 0 ? '' : csi(n, 'S') 257} 258 259/** Scroll down n lines (CSI n T) */ 260export function scrollDown(n = 1): string { 261 return n === 0 ? '' : csi(n, 'T') 262} 263 264/** Set scroll region (DECSTBM, CSI top;bottom r). 1-indexed, inclusive. */ 265export function setScrollRegion(top: number, bottom: number): string { 266 return csi(top, bottom, 'r') 267} 268 269/** Reset scroll region to full screen (DECSTBM, CSI r). Homes the cursor. */ 270export const RESET_SCROLL_REGION = csi('r') 271 272// Bracketed paste markers (input from terminal, not output) 273// These are sent by the terminal to delimit pasted content when 274// bracketed paste mode is enabled (via DEC mode 2004) 275 276/** Sent by terminal before pasted content (CSI 200 ~) */ 277export const PASTE_START = csi('200~') 278 279/** Sent by terminal after pasted content (CSI 201 ~) */ 280export const PASTE_END = csi('201~') 281 282// Focus event markers (input from terminal, not output) 283// These are sent by the terminal when focus changes while 284// focus events mode is enabled (via DEC mode 1004) 285 286/** Sent by terminal when it gains focus (CSI I) */ 287export const FOCUS_IN = csi('I') 288 289/** Sent by terminal when it loses focus (CSI O) */ 290export const FOCUS_OUT = csi('O') 291 292// Kitty keyboard protocol (CSI u) 293// Enables enhanced key reporting with modifier information 294// See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/ 295 296/** 297 * Enable Kitty keyboard protocol with basic modifier reporting 298 * CSI > 1 u - pushes mode with flags=1 (disambiguate escape codes) 299 * This makes Shift+Enter send CSI 13;2 u instead of just CR 300 */ 301export const ENABLE_KITTY_KEYBOARD = csi('>1u') 302 303/** 304 * Disable Kitty keyboard protocol 305 * CSI < u - pops the keyboard mode stack 306 */ 307export const DISABLE_KITTY_KEYBOARD = csi('<u') 308 309/** 310 * Enable xterm modifyOtherKeys level 2. 311 * tmux accepts this (not the kitty stack) to enable extended keys — when 312 * extended-keys-format is csi-u, tmux then emits keys in kitty format. 313 */ 314export const ENABLE_MODIFY_OTHER_KEYS = csi('>4;2m') 315 316/** 317 * Disable xterm modifyOtherKeys (reset to default). 318 */ 319export const DISABLE_MODIFY_OTHER_KEYS = csi('>4m')