키바인딩 시스템

터미널 바이트부터 타입된 액션까지 — 키 입력을 명령으로 변환, 코드 시퀀스, 사용자 오버라이드, 예약된 단축키 보호.

01

개요

터미널에서 사용자가 키를 누르면 바이트 시퀀스가 생성됩니다. 이 바이트를 의미 있는 액션으로 변환하는 것이 키바인딩 시스템의 역할입니다. 단순한 매핑 테이블이 아니라, 터미널 프로토콜 해석, 코드 시퀀스 해석, 사용자 오버라이드, 보호 키 관리를 포함하는 5단계 파이프라인입니다.

다루는 소스 파일: keybindings/decoder.tskeybindings/config.tskeybindings/matcher.tskeybindings/chords.tskeybindings/dispatch.ts

5단계 파이프라인:

02

터미널 디코딩과 3 키보드 프로토콜

터미널 에뮬레이터마다 키 입력을 바이트로 인코딩하는 프로토콜이 다릅니다. 키바인딩 시스템은 3가지 프로토콜을 지원합니다.

Legacy VT 프로토콜

가장 오래된 프로토콜로, 대부분의 터미널이 지원합니다. 수정자 키(Ctrl, Alt)가 바이트 값에 직접 인코딩되어 정보가 제한적입니다. 예를 들어 Ctrl+C는 0x03(ETX)이지만, Ctrl+Shift+C를 구분할 수 없습니다.

// Legacy VT: Ctrl+키는 0x01-0x1A로 인코딩
function decodeLegacyVT(byte: number): KeyEvent | null {
  if (byte >= 0x01 && byte <= 0x1A) {
    return {
      key: String.fromCharCode(byte + 0x60),  // 'a'-'z'
      ctrl: true,
      shift: false,  // 구분 불가
      alt: false
    }
  }
  return null
}

CSI u 프로토콜

모던 터미널(kitty, ghostty, WezTerm 등)이 지원하는 확장 프로토콜입니다. CSI codepoint ; modifiers u 형식으로 키를 정확하게 인코딩하여 Ctrl+Shift+키 같은 조합도 구분할 수 있습니다.

// CSI u: \x1b[codepoint;modifiers u
function decodeCsiU(seq: string): KeyEvent | null {
  const match = seq.match(/\x1b\[(\d+);(\d+)u/)
  if (!match) return null
  const codepoint = parseInt(match[1])
  const modifiers = parseInt(match[2]) - 1
  return {
    key: String.fromCodePoint(codepoint),
    shift: !!(modifiers & 1),
    alt:   !!(modifiers & 2),
    ctrl:  !!(modifiers & 4),
  }
}

xterm 프로토콜

xterm 호환 터미널에서 사용하는 중간 단계 프로토콜입니다. 기능 키(F1-F12), 화살표 키 등을 CSI 시퀀스로 인코딩합니다.

03

18 키바인딩 컨텍스트

같은 키가 다른 상황에서 다른 동작을 해야 합니다. 예를 들어 Escape는 입력 중에는 Vim NORMAL 모드 전환, 대화상자에서는 닫기, 자동완성 중에는 목록 숨기기를 수행합니다. 이를 위해 18개의 키바인딩 컨텍스트가 정의되어 있습니다.

// 키바인딩 컨텍스트 — 상황에 따라 다른 동작
type KeybindingContext =
  | 'global'           // 어디서든 동작
  | 'input'            // 텍스트 입력 중
  | 'input.vim.normal' // Vim NORMAL 모드
  | 'input.vim.insert' // Vim INSERT 모드
  | 'dialog'           // 대화상자 열림
  | 'autocomplete'     // 자동완성 목록 표시 중
  | 'permission'       // 권한 요청 대화상자
  | 'select'           // 선택 메뉴
  | 'streaming'        // AI 응답 스트리밍 중
  | 'idle'             // 입력 대기
  // ... 8개 추가 컨텍스트

키 입력이 들어오면 현재 활성화된 컨텍스트 스택의 위에서부터 매칭을 시도합니다. 가장 구체적인 컨텍스트의 바인딩이 우선합니다.

04

코드 시퀀스와 5가지 결과 타입

코드 시퀀스(chord sequence)는 두 개 이상의 키를 순서대로 눌러 하나의 명령을 실행하는 방식입니다. 예를 들어 Ctrl+K, Ctrl+C는 "주석 토글"을 의미할 수 있습니다.

// 코드 시퀀스 해석 — 5가지 결과 타입
type ChordResult =
  | { type: 'match'; action: Action }       // 완전 매칭 → 액션 실행
  | { type: 'partial' }                    // 부분 매칭 → 다음 키 대기
  | { type: 'noMatch' }                    // 매칭 없음 → 키 통과
  | { type: 'ambiguous'; candidates: Binding[] }  // 모호 → 타임아웃 대기
  | { type: 'timeout' }                    // 타임아웃 → 부분 매칭 취소

코드 시퀀스 해석의 핵심 과제는 ambiguous(모호) 상태입니다. 예를 들어 Ctrl+K가 단독 바인딩이면서 동시에 Ctrl+K, Ctrl+C 코드의 시작이라면, 시스템은 짧은 타임아웃 동안 다음 키를 기다립니다. 타임아웃 내에 다음 키가 오면 코드로, 오지 않으면 단독 바인딩으로 처리합니다.

05

사용자 오버라이드와 보호 키

~/.claude/keybindings.json 핫 리로드

사용자는 ~/.claude/keybindings.json 파일에서 키바인딩을 커스터마이징할 수 있습니다. 이 파일은 핫 리로드를 지원하여 Claude Code를 재시작하지 않고도 변경 사항이 즉시 적용됩니다.

// ~/.claude/keybindings.json 예시
{
  "bindings": [
    {
      "keys": ["ctrl+shift+p"],
      "action": "command-palette",
      "context": "global"
    },
    {
      "keys": ["ctrl+k", "ctrl+c"],
      "action": "toggle-comment",
      "context": "input"
    }
  ]
}

3 보호 키 카테고리

일부 키바인딩은 사용자가 오버라이드할 수 없도록 보호됩니다. 오버라이드를 허용하면 사용자가 도구를 사용할 수 없게 되거나 안전 문제가 발생할 수 있기 때문입니다.

주의사항

사용자가 보호된 키를 오버라이드하려고 하면 keybindings.json은 조용히 무시됩니다. 에러 메시지를 표시하지 않으므로, 커스텀 바인딩이 동작하지 않을 때 보호 키 목록을 먼저 확인해야 합니다.

06

핵심 요약

핵심 포인트

  • 키바인딩 시스템은 5단계 파이프라인입니다: Terminal Decode → Binding Config → Key Matching → Chord Resolution → React Dispatch
  • 3가지 키보드 프로토콜(Legacy VT, CSI u, xterm)을 지원하여 모든 터미널과 호환됩니다
  • 18개 키바인딩 컨텍스트가 같은 키에 상황별 다른 동작을 부여합니다
  • 코드 시퀀스 해석에는 5가지 결과 타입(match, partial, noMatch, ambiguous, timeout)이 있습니다
  • ~/.claude/keybindings.json은 핫 리로드를 지원하여 재시작 없이 키바인딩을 변경할 수 있습니다
  • 3 보호 키 카테고리(시스템 필수, 안전 필수, 터미널 예약)는 사용자 오버라이드가 불가능합니다
07

지식 확인

퀴즈 — 5문제

Q1. CSI u 프로토콜이 Legacy VT보다 나은 점은 무엇인가요?

  • A) 더 적은 바이트로 키를 인코딩하여 대역폭을 절약한다
  • B) 모든 터미널에서 지원된다
  • C) 코드포인트와 수정자를 정확히 인코딩하여 Ctrl+Shift+키 같은 조합을 구분할 수 있다
  • D) 마우스 이벤트도 함께 인코딩할 수 있다
Legacy VT에서 Ctrl+키는 0x01-0x1A로 인코딩되어 Shift 수정자를 구분할 수 없습니다. CSI u는 \x1b[codepoint;modifiers u 형식으로 키와 수정자를 별도로 인코딩하여 모든 조합을 정확히 표현합니다.

Q2. 코드 시퀀스에서 ambiguous 결과가 발생하는 상황은?

  • A) 키가 어떤 바인딩과도 매칭되지 않을 때
  • B) 키가 단독 바인딩이면서 동시에 코드 시퀀스의 시작일 때
  • C) 같은 키에 여러 액션이 등록되어 있을 때
  • D) 사용자 바인딩이 시스템 바인딩과 충돌할 때
ambiguous는 입력된 키가 단독 바인딩이면서 동시에 더 긴 코드 시퀀스의 접두사일 때 발생합니다. 시스템은 짧은 타임아웃 동안 다음 키를 기다려 코드인지 단독 키인지 결정합니다.

Q3. 18개 키바인딩 컨텍스트가 스택으로 관리되는 이유는?

  • A) 가장 구체적인(위에 있는) 컨텍스트의 바인딩이 우선하여 상황에 맞는 동작을 보장하기 위해
  • B) 메모리 효율성을 위해
  • C) 비동기 키 처리를 위해
  • D) React의 컴포넌트 트리와 1:1 대응시키기 위해
컨텍스트 스택은 현재 활성화된 모든 컨텍스트를 구체적인 것부터 일반적인 것 순으로 유지합니다. 키 매칭 시 스택의 위에서부터 검색하여 "대화상자 > 입력 > 전역" 순으로 가장 적합한 바인딩을 찾습니다.

Q4. Ctrl+C가 보호 키로 지정된 이유는?

  • A) 터미널에서 복사 기능으로 사용되기 때문
  • B) Vim 모드와 충돌하기 때문
  • C) 사용 빈도가 매우 높기 때문
  • D) 프로세스 인터럽트(SIGINT)에 필수적이어서 오버라이드하면 프로세스를 중단할 수 없기 때문
Ctrl+C는 실행 중인 프로세스에 SIGINT를 보내는 시스템 필수 키입니다. 이를 오버라이드하면 사용자가 무한 루프에 빠진 프로세스를 중단할 수 없게 되어 위험합니다. 따라서 보호 키로 지정되어 사용자 오버라이드가 불가능합니다.

Q5. keybindings.json의 핫 리로드는 어떻게 동작하나요?

  • A) 파일을 저장할 때마다 Claude Code가 자동으로 재시작된다
  • B) 사용자가 /reload-keybindings 커맨드를 실행해야 한다
  • C) 파일 시스템 감시자가 파일 변경을 감지하고 바인딩 설정을 즉시 다시 로드한다
  • D) 다음 키 입력 시 파일을 다시 읽는다
keybindings.json에 대한 파일 시스템 감시자(watcher)가 설정되어 있어, 파일이 수정되면 즉시 감지하고 바인딩 설정을 다시 로드합니다. 사용자는 파일을 저장하기만 하면 변경 사항이 즉시 적용됩니다.
0 / 5