Ghostel은 libghostty-vt를 기반으로 한 Emacs용 터미널 에뮬레이터로, 네이티브 모듈을 통해 터미널 상태, 렌더링, 로컬 PTY I/O를 처리하고 Elisp로 키맵, 버퍼, 명령, 원격 프로세스 통합을 관리합니다.
Ghostel은 libghostty-vt로 구동되는 Emacs 터미널 에뮬레이터입니다. 이것은 Ghostty 터미널을 구동하는 것과 같은 VT 엔진입니다. Zig로 작성된 네이티브 동적 모듈이 터미널 상태, 렌더링, 로컬 PTY I/O를 처리하고, Elisp는 키맵, 버퍼, 명령, 원격 프로세스 통합을 관리합니다.
Ghostel은 emacs-libvterm에서 영감을 받았으며 동일한 2계층 설계를 따르지만, libvterm 대신 Ghostty의 현대적인 VT 엔진을 사용합니다. 덕분에 Kitty 키보드 및 그래픽 프로토콜, 풍부한 밑줄 스타일, OSC 8 하이퍼링크, OSC 4/10/11 색상 질의, 동기화된 출력(DEC 2026)을 사용할 수 있으며, libvterm은 이들 중 어느 것도 지원하지 않습니다. 자세한 비교는 Ghostel vs vterm and eat를 참고하세요.
네이티브 모듈은 처음 사용할 때 자동으로 다운로드되므로 일반적인 경우에는 툴체인이 필요하지 않습니다. M-x ghostel로 터미널을 여세요.
문서 검색
문서를 검색하려면 입력하세요. 결과 사이를 이동하려면 화살표 키를 사용하세요.
(use-package ghostel
:bind (("C-x m" . ghostel)
:map ghostel-semi-char-mode-map
("C-s" . consult-line)
("M-<backspace>" . ghostel-backward-kill-word)
("M-p" . (lambda () (interactive) (ghostel-send-key "p" "ctrl")))
("M-n" . (lambda () (interactive) (ghostel-send-key "n" "ctrl")))
:map project-prefix-map
("m" . ghostel-project)
("M" . ghostel-project-list-buffers))
:config
(defun ghostel-send-C-k-and-kill ()
"ghostel로 C-k'를 보냅니다. 일반 Emacs C-k'처럼 동작합니다. 줄 끝까지 잘라 kill-ring에 넣습니다."
(interactive)
(kill-ring-save (point) (line-end-position))
(ghostel-send-key "k" "ctrl"))
(add-to-list 'project-switch-commands '(ghostel-project "Ghostel") t) (add-to-list 'project-switch-commands '(ghostel-project-list-buffers "Ghostel buffers") t) (add-to-list 'ghostel-eval-cmds '("magit-status-setup-buffer" magit-status-setup-buffer)))
(use-package ghostel-eshell :hook (eshell-load . ghostel-eshell-visual-command-mode))
(use-package ghostel-compile :hook (after-init . ghostel-compile-global-mode))
(use-package ghostel-comint :hook (after-init . ghostel-comint-global-mode))
Evil 사용자라면 evil-ghostel 확장을 설치할 수 있습니다:
(use-package evil-ghostel :after (ghostel evil) :hook (ghostel-mode . evil-ghostel-mode))
디렉터리 추적과 프롬프트 탐색은 로컬 bash, zsh, fish 세션에서 기본적으로 자동 활성화됩니다. TRAMP 지원과 추가 내용은 shell integration을 참고하세요.
셸에서 Emacs 함수를 호출하려면 ghostel-eval-cmds 허용 목록에 추가한 다음, bashrc에 다음과 같은 내용을 넣어야 합니다:
if [[ "$INSIDE_EMACS" = 'ghostel' ]]; then e() { ghostel_cmd find-file-other-window "$@"; }
dow() { ghostel_cmd dired-other-window "$@"; }
gst() { ghostel_cmd magit-status-setup-buffer "$(pwd)"; }
fi
Ghostel은 eat.el 스타일의 입력 모드 다섯 가지를 제공합니다.
기본값은 세미-문자 모드이며, 몇 가지 예외(M-x, C-c 등)를 제외한 거의 모든 키를 터미널로 전달합니다.
문자 모드에서는 모든 키가 터미널로 갑니다. 종료하려면 M-RET를 누르세요.
줄 모드에서 Ghostel은 M-x shell처럼 동작합니다. 버퍼는 일반 Emacs 버퍼이며 어떤 키도 터미널로 보내지지 않습니다. 한 줄 입력을 마치고 RET를 눌렀을 때만 전체 줄이 한 번에 터미널로 전송됩니다.
Emacs 모드와 복사 모드는 버퍼를 일시적으로 일반 Emacs 버퍼처럼 만들어 탐색하고 살펴보고 텍스트를 복사할 수 있게 합니다. 둘의 차이는 복사 모드가 터미널을 멈춘다는 점입니다. 따라서 지속적인 출력이 있어도 무언가를 선택하려는 동안 내용이 "스크롤되어 사라지지" 않습니다. Emacs 모드는 실시간 이므로 스크롤하고 선택하는 동안에도 새 출력이 계속 들어옵니다.
이 읽기 전용 모드들은 기본적으로 ghostel-readonly-fast-exit가 활성화되어 있습니다(기본값은 t). 그래서 일반적으로 터미널로 보내질 것이라고 기대하는 대부분의 키를 누르면 자동으로 해당 모드를 종료합니다. 덕분에 전환이 자연스럽습니다. 예를 들어 출력이 실행 중인데 복사하고 싶은 내용을 보면 C-c C-t를 눌러 복사 모드로 들어가고, 일반 Emacs 버퍼처럼 이동하면서 텍스트를 선택할 수 있습니다. 무언가를 복사하거나 아무 글자나 입력하면 자동으로 평소의 ghostel 터미널 세션으로 돌아갑니다. 마우스로 선택하기, 하이퍼링크로 이동하기(C-c C-p), mark 활성화 같은 일부 동작도 자동으로 복사 모드를 켭니다.
네이티브 모듈은 처음 사용할 때 자동으로 다운로드됩니다. 다음 플랫폼용 사전 빌드 바이너리가 제공됩니다:
aarch64-macos (Apple Silicon)x86_64-macos (Intel Mac)x86_64-linuxaarch64-linuxx86_64-freebsdx86_64-windowsaarch64-windows소스에서 직접 빌드하고 싶거나 다른 플랫폼이 필요하다면 Zig 0.15.2도 필요합니다. 자세한 내용은 Building from source를 참고하세요.
(use-package ghostel :ensure t)
:vc와 함께 사용하는 use-package (Emacs 30+)(use-package ghostel :vc (:url "https://github.com/dakra/ghostel" :lisp-dir "lisp" :rev :newest))
:lisp-dir "lisp"는 Emacs < 31.1에서만 필요합니다.
:load-path와 함께 사용하는 use-package(use-package ghostel :load-path "/path/to/ghostel/lisp")
(add-to-list 'load-path "/path/to/ghostel/lisp") (require 'ghostel)
그런 다음 M-x ghostel로 터미널을 엽니다.
네이티브 모듈이 없으면 Ghostel은 사전 빌드 바이너리 다운로드 또는 소스에서 컴파일을 제안합니다. 이것은 ghostel-module-auto-install(기본값 ask)로 제어됩니다. 다음 명령으로 수동 실행도 가능합니다:
M-x ghostel-download-module - 지원되는 최소 사전 빌드 바이너리를 다운로드합니다.C-u M-x ghostel-download-module - 특정 릴리스 태그를 선택합니다(최신 버전을 원하면 비워 두세요).M-x ghostel-module-compile - zig build로 소스에서 빌드합니다.기본적으로 모듈은 패키지 디렉터리에서 읽고 씁니다. 패키지 관리자가 Emacs가 모듈을 로드한 상태에서 트리를 재빌드하거나 재설치한다면 ghostel-module-directory를 패키지 트리 밖의 안정적인 위치(예: ~/.config/emacs/ghostel/)로 지정하세요.
Ghostel은 동적 모듈 지원이 있는 네이티브 Windows Emacs 빌드를 지원합니다. 사전 빌드 릴리스 모듈은 일반적인 x86_64 및 aarch64 네이티브 Emacs 아키텍처를 대상으로 하며, 모듈 DLL은 Windows 자체의 아키텍처가 아니라 Emacs의 아키텍처와 일치해야 합니다. static-CRT 빌드를 포함한 특이한 사용자 정의 Emacs 빌드는 최선 노력 수준으로 지원됩니다.
로컬 터미널은 Windows ConPTY를 사용합니다. Ghostel은 먼저 네이티브 모듈 옆에 있는 Microsoft 재배포 가능 콘솔 런타임의 지원 파일(conpty.dll 및 일치하는 OpenConsole.exe 헬퍼)을 찾습니다. 이 파일들이 있으면 오래된 Windows 기본 버전보다 더 새로운 ConPTY 동작을 제공하며 상호작용성과 터미널 프로토콜 정확성을 모두 개선할 수 있습니다. 설치되어 있지 않으면 Ghostel은 기본 제공 Windows ConPTY API로 대체합니다.
사전 빌드 Ghostel 모듈을 다운로드하면 같은 Ghostel 릴리스의 일치하는 지원 파일도 함께 다운로드됩니다. 모듈을 로컬에서 컴파일할 때 Ghostel은 최신 지원 파일 설치를 시도합니다. 설치에 실패해도 기본 제공 ConPTY 대체 경로를 계속 사용할 수 있으므로 치명적이지 않습니다.
Windows에서 TRAMP 원격 터미널은 POSIX 원격지로 제한됩니다. 이들은 로컬 ConPTY 대신 TRAMP/ssh를 사용하므로 PTY는 원격 호스트에 존재합니다. Windows에서 TRAMP로 가는 터미널의 동적 창 크기 조절은 현재 지원되지 않습니다.
사전 빌드 바이너리를 사용하지 않으려는 경우에만 빌드가 필요합니다. Ghostel은 생성된 vendor/emacs-module.h를 포함하고 있으므로 일반적인 빌드에는 로컬 Emacs 헤더가 필요하지 않습니다.
git clone https://github.com/dakra/ghostel.git cd ghostel
zig build --prefix . -Doptimize=ReleaseFast
번들된 Emacs 헤더를 덮어쓰려면 EMACS_INCLUDE_DIR를 emacs-module.h가 있는 디렉터리로 설정하거나, EMACS_BIN_DIR를 Emacs의 bin/ 디렉터리로 설정하세요
../include와 ../share/emacs/include를 찾습니다.로컬 ghostty 체크아웃을 대상으로 빌드하려면 의존성을 임시로 로컬 경로로 지정하세요:
zig fetch --save=ghostty /path/to/ghostty zig build --prefix . -Doptimize=ReleaseFast
모듈과 ghostel-module.version 보조 파일은 Zig의 install prefix 아래에 설치됩니다. Ghostel이 직접 로드해야 하는 체크아웃 안에 빌드할 때는 --prefix .를 사용하고, 또는 ghostel-module-directory와 일치하는 다른 prefix를 전달하세요. Windows에서는 로컬 빌드가 보통 Zig의 MinGW 대상(예: -Dtarget=x86_64-windows-gnu)을 사용하므로 일반적인 네이티브 Emacs 빌드와 일치하며 Windows SDK가 필요하지 않습니다.
MELPA에서 설치한 경우 M-x ghostel-module-compile는 zig build --prefix <ghostel-module-directory>를 사용하여 네이티브 모듈을 소스에서 빌드합니다. Zig 패키지 관리자는 ghostty 의존성을 자동으로 가져옵니다.
컴파일된 xterm-ghostty terminfo 항목은 etc/terminfo/에 사전 빌드되어 포함되며, 로컬에서 tic가 생성하는 것과 동일합니다. 빌드 단계가 필요 없고, 파일 형식은 BSD와 ncurses 시스템 전반에서 이식 가능합니다. 유지 관리자는 libghostty를 올린 뒤 make regen-terminfo로 이를 다시 생성합니다.
셸 통합(OSC 7을 통한 디렉터리 추적, OSC 133을 통한 프롬프트 탐색, OSC 2를 통한 제목 추적, 그리고 셸에서 Elisp 호출하기를 위한 ghostel_cmd)은 bash, zsh, fish, nushell에서 자동입니다. 셸 설정 파일을 수정할 필요가 없습니다.
nushell의 경우 OSC 7/133/2는 nushell 자체의 $env.config.shell_integration(기본 활성화)에서 오므로, ghostel의 ghostel.nu는 ghostel_cmd와 바깥으로 나가는 ssh terminfo 래퍼만 추가합니다. shell_integration.osc7 및 .osc133를 활성 상태로 유지해야 디렉터리 추적과 프롬프트 탐색이 계속 동작합니다.
이 동작은 ghostel-shell-integration(기본값 t)로 제어됩니다. 자동 주입을 끄려면 nil로 설정하고, 대신 스크립트를 수동으로 source 하세요:
[[ "${INSIDE_EMACS%%,*}" = 'ghostel' ]] && source "$EMACS_GHOSTEL_PATH/etc/shell/ghostel.bash"
[[ "${${INSIDE_EMACS-}%%,*}" = 'ghostel' ]] && source "$EMACS_GHOSTEL_PATH/etc/shell/ghostel.zsh"
string match -qr '^ghostel(,|$)' -- "$INSIDE_EMACS"; and source "$EMACS_GHOSTEL_PATH/etc/shell/ghostel.fish"
source /path/to/ghostel/etc/shell/ghostel.nu
참고
macOS의 기본 /bin/bash는 버전 3.2인데, Apple이 자동 주입이 의존하는 ENV 변수를 무시하도록 패치했기 때문에 자동 통합이 동작하지 않습니다. ghostel-shell로 더 새로운 bash(예: Homebrew 버전)를 사용하거나, 위 예시처럼 ~/.bashrc에서 ghostel.bash를 수동으로 source 하세요.
원격(TRAMP / 바깥으로 나가는 ssh) 셸 통합은 별도의 설정이 있습니다. TRAMP (Remote Terminals)을 참고하세요.
Ghostel은 eat.el 스타일의 입력 모드 다섯 가지를 제공합니다. ghostel 버퍼에 들어가면 세미-문자 모드로 시작하며, 아래 키 바인딩으로 모드를 전환할 수 있습니다. 현재 모드 표시는 mode-line-process를 확인하세요.
| Mode | Indicator | Terminal | Input handling |
|---|---|---|---|
| semi-char | (none) | live | 터미널에 입력, C-c는 예약 |
| char | :Char | live | 모든 키가 터미널로 감 |
| Emacs | :Emacs | live | Emacs 탐색/검색/복사 |
| copy | :Copy | frozen | Emacs 탐색/검색/복사, 안정적인 출력 |
| line | :Line | live | Emacs 키로 입력 작성 후 RET에 전송 |
문자 모드를 제외한 모든 모드에서 사용 가능:
| Key | Action |
|---|---|
C-c C-j | 세미-문자 모드로 전환(공통 종료) |
C-c M-d | 문자 모드로 전환 |
C-c C-e | Emacs 모드로 전환 |
C-c C-t | 복사 모드 토글 |
C-c C-l | 줄 모드로 전환 |
M-RET | 문자 모드 전용: 세미-문자 모드로 돌아가기 |
대부분의 키는 터미널로 전송됩니다. ghostel-keymap-exceptions에 있는 키(기본값: C-c, C-x, C-u, C-h, M-x, M-:, C-\)는 Emacs로 전달됩니다.
| Key | Action |
|---|---|
| 대부분의 키 | 터미널로 직접 전송 |
C-c C-c | 인터럽트(C-c) 전송 |
C-c C-z | suspend(C-z) 전송 |
C-c C-d | EOF(C-d) 전송 |
C-c C-\ | quit(C-) 전송 |
C-c M-w | 전체 스크롤백을 kill ring으로 복사 |
C-y | kill ring에서 yank(브래킷된 붙여넣기) |
M-y | yank-pop(kill ring 순환) |
C-c C-y | kill ring에서 붙여넣기 |
C-c M-l | 스크롤백 지우기 |
C-c C-n | 다음 하이퍼링크로 이동 |
C-c C-p | 이전 하이퍼링크로 이동 |
C-c M-n | Emacs 모드로 들어가 다음 프롬프트로 이동 |
C-c M-p | Emacs 모드로 들어가 이전 프롬프트로 이동 |
C-c C-q | 다음 키를 있는 그대로 전송(탈출구) |
| 마우스 휠 | 스크롤백 스크롤 |
C-c M-d로 들어갑니다. 모든 키(ghostel-keymap-exceptions 포함)가 터미널로 전송됩니다. C-x, M-x, C-h 등을 직접 바인딩하려는 TUI 앱에 유용합니다. 유일한 탈출구는 M-RET(또는 C-M-m)입니다.
C-c C-e로 들어갑니다. 터미널은 계속 실행되고, 버퍼는 읽기 전용이 되며, 표준 Emacs 바인딩은 전역 맵으로 통과합니다. isearch-forward, occur, M-x, C-SPC + M-w, 화살표 키, 휠 스크롤이 모두 수정 없이 동작합니다. 터미널은 계속 출력을 생성하고 버퍼는 계속 커지지만, point는 사용자가 탐색한 위치에 유지됩니다(지연 redraw 경로가 Emacs 모드에서 point를 보존합니다).
입력한 키는 셸에 도달하지 않습니다. Emacs 모드는 "보기만 하고 건드리지 않는" 뷰입니다. self-insert, RET, TAB, DEL은 읽기 전용 버퍼로 전달되어 text-read-only를 발생시키므로, 실수한 키 입력이 프롬프트에 들어갈 수 없습니다. 셸에 입력하고 싶을 때는 세미-문자 모드(C-c C-j)로 전환하세요. 예외는 C-y로, 의도적인 동작으로 브래킷된 붙여넣기를 수행하며 point를 다시 실시간 커서로 돌립니다.
C-c C-e를 다시 누르면 Emacs 모드가 꺼지며(들어오기 전 모드로 돌아감), C-c C-t를 누르면 출력이 멈추는 복사 모드로 전환됩니다.
빌드가 실행 중일 때 스크롤백을 검색하거나, M-x occur로 스트리밍 로그를 필터링하거나, 보이는 기록 전반에 걸쳐 마크하고 복사하거나, 터미널 출력을 멈추지 않고 버퍼 기반 명령을 실행할 때 이 모드를 사용하세요.
C-c C-t로 들어갑니다. 터미널은 멈춘 상태가 되며 종료할 때까지 실시간 출력이 버퍼를 갱신하지 않습니다. 커서 아래에서 터미널이 스크롤되는 일 없이 텍스트를 정확하게 선택하고 싶을 때 사용하세요. 공격적인 복사 모드 키맵은 self-insert에서 종료하므로, 글자를 입력하면 해당 키를 터미널로 보내고 세미-문자 모드로 돌아갑니다(ghostel-readonly-fast-exit로 제어).
C-c C-t를 다시 누르면 복사 모드가 꺼지고, C-c C-e를 누르면 세미-문자 모드를 거치지 않고 출력이 다시 흐르는 읽기 전용 실시간 Emacs 모드로 전환됩니다.
| Key | Action |
|---|---|
C-SPC | mark 설정 |
M-w / C-w | 선택 영역 복사 후 종료 |
C-n / C-p | 줄 이동 |
M-v / C-v | 페이지 위 / 아래 스크롤 |
M-< / M-> | 버퍼 맨 위 / 맨 아래로 이동 |
C-c C-n | 다음 하이퍼링크로 이동 |
C-c C-p | 이전 하이퍼링크로 이동 |
C-c M-n | 다음 프롬프트로 이동 |
C-c M-p | 이전 프롬프트로 이동 |
C-l | 뷰포트 재중앙 정렬 |
q | 복사 없이 종료 |
a - z | 종료하고 해당 키를 터미널로 전송 |
소프트 래핑된 줄바꿈은 복사된 텍스트에서 자동으로 제거됩니다.
ghostel 버퍼 안에서 클릭하고 드래그하면 영역이 만들어집니다. 버튼을 놓으면 ghostel-mouse-drag-or-set-region이 입력 모드를 전환하여 스트리밍되는 터미널 출력이 선택을 덮어쓰지 못하게 합니다. 대상 모드는 ghostel-mouse-drag-input-mode(기본값 copy)가 결정합니다:
copy - 복사 모드로 들어갑니다. redraw가 멈추며, 선택 영역이 어디에 있든 안정적으로 유지됩니다.emacs - Emacs 모드로 들어갑니다. 터미널은 계속 스트리밍되고 버퍼는 읽기 전용이 됩니다. 스크롤백에 완전히 있는 선택은 유지되지만, 실시간 프로그램이 다시 쓰는 줄에 걸친 선택은 여전히 잃을 수 있습니다.nil - 세미-문자에 그대로 머뭅니다. 선택 유지 보장은 emacs와 같지만 M-w가 셸로 전달되므로 영역을 복사할 수 없습니다. 기본 선택 또는 GUI 메뉴로 복사하는 경우에만 이 값을 선택하세요.이미 선택된 창 안에서 한 번 클릭하면 point를 설정한 뒤 ghostel-mouse-drag-input-mode에 따라 입력 모드를 전환합니다. 동작은 드래그와 같습니다. 이전에 선택되지 않았던 창에 포커스를 주는 클릭은 그 창에 포커스만 줍니다. point는 클릭 위치가 아니라 터미널의 입력 커서에 놓이며 입력 모드는 바뀌지 않습니다. 따라서 단지 창에 포커스를 주려는 클릭은 버퍼를 멈추게 하지 않습니다. (ghostel-mouse-drag-input-mode를 nil로 설정하면 이 클릭 기능을 끌 수 있어, 일반 Emacs 버퍼처럼 클릭이 point만 설정하게 됩니다.) ghostel이 터미널 입력 모드에 있고 TUI가 DEC 마우스 추적(1000/1002/1003 - htop, lazygit 등)을 활성화한 경우, 클릭은 프로그램으로 전달되며 위 규칙들은 적용되지 않습니다. 복사, Emacs, 줄 모드는 터미널 마우스 추적이 활성화되어 있어도 일반 Emacs 마우스 동작을 유지합니다.
더블 클릭과 트리플 클릭은 각각 커서 아래의 단어와 줄을 선택하며, 드래그와 같은 방식으로 해당 영역을 보호합니다(포커스 여부와 무관하게).
같은 보호 기능은 키보드 선택에도 있습니다. 세미-문자 모드에서 명령이 mark를 활성화할 때 C-SPC(set-mark-command), expand-region 변형, C-x h 등 영역을 켜는 모든 동작에서 ghostel은 ghostel-mark-activation-input-mode(기본값 copy, emacs, 또는 세미-문자에 머무는 nil)가 지정한 입력 모드로 전환합니다. 이는 특정 키가 아니라 mark 활성화에 훅을 거는 방식이므로 set-mark-or-expand 같은 사용자 정의 바인딩도 작동합니다. TTY에서는 Ctrl+Space가 C-@(NUL)와 구별되지 않아 터미널로 전달된다는 점에 유의하세요. 문자 모드에서는 Ctrl+Space가 GUI와 TTY 모두에서 항상 NUL로 터미널에 전달됩니다.
비슷한 규칙은 명령이 아무것도 선택하지 않고 단지 point를 실시간 입력 위치에서 벗어나게 할 때도 적용됩니다. isearch(isearch-mode-end-hook를 통해)와 consult-line 같은 미니버퍼 명령(minibuffer-exit-hook를 통해)은 ghostel-point-leave-input-mode(기본값 copy, emacs, 또는 비활성화를 위한 nil)가 지정한 모드로 전환합니다. 그래서 위치가 고정되고 내용이 탐색 가능해지며, 다음 redraw가 point를 다시 프롬프트로 끌어당기지 않습니다.
다른 jump 패키지에는 내장 훅이 없으므로 ghostel-maybe-leave-input 명령으로 연결하세요. 이 명령은 세미-문자 모드에서 point가 실시간 커서에서 벗어난 경우에만 ghostel-point-leave-input-mode를 적용하고, 그렇지 않으면 아무것도 하지 않습니다. 인자를 무시하므로 훅 함수와 :after advice 양쪽으로 사용할 수 있습니다:
(add-hook 'flash-after-jump-hook #'ghostel-maybe-leave-input)
(with-eval-after-load 'avy (advice-add 'avy-action-goto :after #'ghostel-maybe-leave-input))
C-c C-l로 들어갑니다. 줄 모드는 사용자의 입력을 Emacs 안에서 로컬로 버퍼링합니다. 입력을 작성하는 동안에는 어떤 키 입력도 셸로 전달되지 않습니다. 완전한 Emacs 편집(M-b, M-DEL, C-y yank, transpose-words 등)이 입력 영역에서 동작합니다. RET를 누르면 전체 줄이 한 번의 write로 셸에 전송됩니다. bash는 이를 원자적으로 받아 echo하고 실행합니다.
터미널은 실시간 상태를 유지합니다. 입력을 작성하는 동안에도 출력은 계속 스트리밍되고 버퍼는 계속 다시 렌더링됩니다. 지연 redraw 경로의 snapshot/restore 단계가 매 redraw 전에 진행 중인 입력을 캡처하고, 이후 새 프롬프트 끝에 다시 삽입하므로, 비동기 출력이나 편집 도중 새 프롬프트가 도착해도 입력한 내용이 덮어써지지 않습니다. RET 후에도 줄 모드는 계속 활성 상태로 유지되며, 다음 프롬프트는 다음 redraw 주기에서 찾아지고 입력 마커도 그쪽으로 이동합니다.
줄 모드는 터미널 커서를 입력 영역의 경계로 사용하므로, 셸 통합이 없는 REPL(python3, irb, sqlite3, …)도 동작합니다. 커서 줄에 OSC 133 프롬프트 마커가 있으면 프롬프트 접두사가 인식되고 입력 경계는 그 직후로 잡힙니다. OSC 133이 없는 대체 경로에서는 프롬프트 접두사를 ghostel-prompt-regexp와 매칭합니다.
줄 모드와 전체 화면 TUI(vim, less, htop, …)는 같은 키스트로크 스트림을 공유할 수 없습니다. TUI는 모든 키를 가공 없이 전달받아야 하고, 줄 모드는 그것을 로컬에서 버퍼링해야 하기 때문입니다. Ghostel은 이를 투명하게 처리합니다. alt-screen TUI가 시작되면 줄 모드는 세미-문자로 내려가 TUI가 키를 받을 수 있게 하고, 종료 시 다시 줄 모드로 복귀한다는 짧은 메시지를 표시합니다. TUI가 종료되면 줄 모드는 새 프롬프트에서 다시 시작됩니다.
alt screen에서 C-c C-l을 누르면 현재 실행 중인 것에 맞게 올바르게 동작합니다. 가공 없는 TUI 위에서는 같은 자동 재개를 설정하여 TUI가 종료될 때 줄 모드가 활성화되도록 합니다. 명시적인 모드 전환(C-c C-j, ghostel-char-mode 등)은 이 설정을 취소합니다. 내부 셸 프롬프트, 즉 OSC 133 마커가 passthrough를 통해 Ghostel에 도달하는 tmux=/=screen 세션에서는 C-c C-l이 그 프롬프트에서 바로 줄 모드로 들어갑니다. 마커가 통과되지 않는 경우에는 C-u C-c C-l로 강제로 들어갈 수 있습니다.
TAB은 ghostel-line-mode-completion-at-point-functions를 통해 입력을 완성하며(기본적으로 comint 파일명/명령 완성), 선택적으로 ghostel-line-mode-use-bash-completion을 통해 bash 프로그래머블 완성을 그 위에 겹칠 수 있습니다.
| Key | Action |
|---|---|
| (letters) | 로컬 입력 편집(문자 단위로 전송되지 않음) |
RET | 전체 줄을 셸로 전송하고 줄 모드 유지 |
TAB | point 위치에서 입력 완성 |
C-c C-c | 입력 폐기 후 SIGINT 전송, 줄 모드 유지 |
C-d | 문자 삭제, 또는 입력이 비어 있으면 EOF 전송 |
M-p / M-n | 히스토리 링: 이전 / 다음 항목 |
C-a | 프롬프트 줄에서는 입력 시작 위치, 아니면 beginning-of-line |
C-c C-j | 세미-문자 모드로 종료(입력 폐기) |
TERM=xterm-ghostty - terminfo를 통해 기능을 확인하는 앱들(Claude Code, neovim, tmux, 최신 TUI)은 동기화된 출력(DEC 2026), Kitty 키보드 프로토콜, 트루컬러, 색 있는 밑줄, 포커스 보고 등을 발견해 빠른 경로를 사용합니다. 특히 동기화된 출력은 Claude Code가 큰 스크롤백 위에서 다시 그릴 때 나타나는 끊기는 부분 redraw 효과를 없애 줍니다. OSC 52(클립보드)는 지원되지만 번들 terminfo에는 의도적으로 광고되지 않습니다(Clipboard 참고). ghostel-term으로 덮어쓸 수 있습니다.duf, btop, delta, 그리고 termenv를 사용하는 다른 도구들이 Emacs face 색상에서 올바른 밝은/어두운 테마를 자동 감지합니다.isearch / consult-line이 기록 전체에서 동작).로컬 ghostel 버퍼는 기본적으로 네이티브 PTY 경로(ghostel-use-native-pty)를 사용합니다. 네이티브 리더는 백그라운드 스레드에서 PTY 출력을 소비하고, 비동기적으로 libghostty-vt를 갱신하며, 콜백이나 redraw가 필요할 때 이벤트 파이프를 통해 Emacs에 알립니다. 덕분에 대규모 로그 스트림과 전체 화면 TUI redraw가 Emacs 프로세스 필터를 바이트 단위로 통과하지 않습니다.
원격 TRAMP 버퍼는 여전히 Emacs 프로세스 기계를 사용하여, TRAMP가 원격 호스트에서 셸을 시작하고 파일 핸들러를 적용할 수 있게 합니다. 렌더링과 입력 API는 두 경로에서 공유됩니다.
Ghostel 버퍼는 Emacs 내장 북마크와 함께 동작합니다. bookmark-set (
C-x r
m
) 는 터미널의 작업 디렉터리와 버퍼 이름을 기록합니다. bookmark-jump (C-x r b)는 이를 다시 엽니다. 같은 이름의 실행 중인 ghostel 버퍼가 있으면 재사용하고, 없으면 북마크된 디렉터리에서 새 셸을 시작합니다. 북마크는 디렉터리와 이름만 저장하며(스크롤백이나 세션 내용은 저장하지 않음), 이는 vterm과 일치합니다.
ghostel-bookmark-check-dir(기본값 t)은 디렉터리 복원을 제어합니다. 새 버퍼는 북마크된 디렉터리에서 셸을 시작하고, 이후 다른 곳으로 이동한 재사용 버퍼는 셸에 cd를 입력받습니다. 디렉터리를 건드리지 않으려면 nil로 설정하세요.
RET로 열기).http:// 및 https:// URL을 자동으로 링크화(ghostel-enable-url-detection로 토글)./path/to/file.el:42 같은 패턴을 클릭 가능하게 만들어 해당 줄에서 파일을 엽니다(ghostel-enable-file-detection로 토글, 경로 패턴은 ghostel-file-detection-path-regex로 조정).ghostel-enable-osc52를 통해 opt-in, 원격 SSH 세션에 유용). 번들된 xterm-ghostty terminfo는 의도적으로 Ms capability를 광고하지 않습니다. 그래서 ghostel-enable-osc52가 기본값 nil일 때 앱이 조용히 클립보드를 잃어버리는 상황을 피합니다. OSC 52를 활성화하고 앱들(neovim, tmux)도 이를 자동 감지하게 하고 싶다면, 같은 경로에 업스트림 Ghostty의 terminfo를 설치하거나 TERMINFO를 덮어쓰세요.sudo, ssh, gpg, passwd 등이 비밀번호를 물으면 ghostel은 read-passwd를 띄우고 응답을 PTY를 통해 전송합니다. 키 입력은 Emacs의 일반 키 파이프라인을 통과하지 않으므로 비밀번호가 view-lossage, 최근 키 링, 키보드 매크로 기록 등에 남지 않습니다. 이 동작은 ghostel-detect-password-prompts(기본값 t)로 제어됩니다.
감지는 두 계층으로 이루어집니다. 주 신호는 libghostty의 휴리스틱을 반영합니다. slave tty가 canonical 모드이며 echo off 상태인지 작은 tcgetattr Zig 바인딩으로 읽습니다. 이것은 !ECHO를 켜는 로컬 프로그램들(sudo, ssh 자체 프롬프트, gpg, …)을 잡아냅니다. 커서 줄 regex 대체 경로(ghostel-password-prompt-regex, 기본값은 comint-password-prompt-regexp)는 tty 신호가 볼 수 없는 경우를 다루지만, 전경 셸이 원격 호스트에 있을 때만 실행됩니다(ghostel--remote-shell-p, OSC 7을 통해 동기화되는 TRAMP default-directory에서 파생). 이를 원격 전용으로 제한하면 vim이나 less 같은 로컬 raw-mode TUI에서의 오탐을 피할 수 있고, 구조적 앵커링은 $ echo Password: 같은 셸 입력 줄이 트리거되는 것도 막습니다. 진단은 ghostel-debug-start / ghostel-debug-password-events-show를 참고하세요.
프롬프트가 열려 있는 동안 mode line에는 = 🔒Password=가 표시됩니다. 잘못된 비밀번호로 인한 재시도도 자동 감지됩니다(커서가 새 프롬프트 줄로 이동). 비밀번호의 wire 사본은 전송 직후 clear-string 처리되므로 힙에 남아 있지 않습니다.
감지는 ghostel-password-prompt-functions를 통해 확장 가능합니다. 이는 순서대로 시도되는 (ROW) -> string-or-nil 소스 체인입니다. 기본값은 read-passwd로 읽고, 사용자는 자신의 소스(auth-source / KeePass / pass / 등)를 앞에 추가하며 기본값은 대체 경로가 됩니다. defcustom docstring에는 TRAMP 인식 auth-source-pick-first-password 예제가 포함되어 있습니다.
default-directory가 셸의 cwd를 따라감, 원격 호스트에서는 TRAMP 인식).C-c M-n / C-c M-p로 프롬프트 간 탐색 가능.ghostel-buffer-name-function 참고).INSIDE_EMACS 및 EMACS_GHOSTEL_PATH 환경 변수.ghostel-sync-theme를 통해 Emacs 테마와 동기화).Ghostel은 libghostty를 통해 Kitty graphics protocol을 사용해 인라인 이미지를 렌더링합니다. 실제 도구들이 사용하는 두 배치 모드를 모두 지원합니다:
timg, kitty +kitten icat, 그리고 직접 kitty graphics 명령을 출력하는 모든 도구.yazi 및 다른 최신 이미지 미리보기 도구가 버퍼의 텍스트 그리드에 이미지를 고정할 때 사용.픽셀 데이터는 Emacs 내장 이미지 지원을 통해 렌더링됩니다. PNG 페이로드는 번들된 stb image로 디코딩되고, raw RGB/RGBA/Gray/GrayAlpha 전송은 네이티브 모듈에서 PPM으로 변환됩니다. 외부 ImageMagick 의존성은 없습니다.
XTWINOPS 크기 질의(CSI 14 / 16 / 18 t)에 응답하므로 앱은 그래픽 지원을 감지하고 이미지 크기를 선택할 수 있습니다. 이것이 없으면 timg는 TERM_PROGRAM=ghostty여도 하프 블록 렌더링으로 대체합니다.
셀 픽셀 크기는 ghostel-cell-pixel-scale(기본값 auto, 디스플레이 DPI에서 파생)을 통해 물리 픽셀로 보고됩니다. 대부분의 디스플레이에서는 독립 실행형 Ghostty 출력에 근사하지만, 픽셀 단위로 완전히 맞추고 싶다면(특히 Linux Wayland의 fractional scaling 또는 비표준 DPI 환경) 명시적인 숫자를 설정하세요.
x, y, w, h)는 조용히 잘못 렌더링하는 대신 명시적인 오류와 함께 거부됩니다. timg, yazi, kitty +kitten icat이 사용하는 전체 이미지 배치는 영향을 받지 않습니다.yazi의 미리보기 창은 한 번에 하나의 이미지만 사용하므로 실제로는 문제가 되지 않았습니다.ghostel-kitty-graphics-mediums로 opt-in 해야 합니다. 권한 상승과 관련된 이유는 해당 docstring을 참고하세요.ghostel 안에서 실행되는 셸 스크립트는 ghostel_cmd 헬퍼(셸 통합 스크립트가 제공)를 통해 허용 목록에 있는 Elisp 함수를 호출할 수 있습니다:
ghostel_cmd find-file "/path/to/file" ghostel_cmd message "Hello from the shell"
이것은 예약된 kind 바이트를 가진 OSC 52 이스케이프 시퀀스(\e]52;e;<payload>\e\\)를 사용합니다. 이는 ghostel 전용 확장입니다. ghostel-eval-cmds에 나열된 함수만 허용됩니다.
기본 허용 명령: find-file, find-file-other-window, dired, dired-other-window, message.
사용자 명령을 추가하려면:
(add-to-list 'ghostel-eval-cmds '("magit-status-setup-buffer" magit-status-setup-buffer))
예시 셸 별칭(.bashrc / .zshrc에 추가):
if [[ "${INSIDE_EMACS%%,*}" = 'ghostel' ]]; then e() { ghostel_cmd find-file-other-window "$@"; }
dow() { ghostel_cmd dired-other-window "${1:-$PWD}"; }
gst() { ghostel_cmd magit-status-setup-buffer "$(pwd)"; }
fi
Ghostel은 터미널 프로그램이 사용하는 두 가지 알림 프로토콜을 인식합니다:
ESC ] 9 ; BODY ST - 본문만.notify): ESC ] 777 ; notify ; TITLE ; BODY ST - 제목 + 본문.둘 다 (TITLE BODY) 인자로 ghostel-notification-function으로 전달됩니다. 기본 핸들러 ghostel-default-notify는 설치되어 있으면 alert 패키지를 사용합니다. 이 패키지는 플랫폼별로 적절한 백엔드(macOS의 osascript, Linux의 libnotify, Growl, terminal-notifier 등)를 선택하며 alert-default-style로 설정 가능합니다. MELPA에서 M-x package-install RET alert RET로 설치하세요.
alert를 사용할 수 없으면 ghostel은 message로 대체하는데, 이 경우 에코 영역에만 표시됩니다. 알림을 완전히 끄려면 ghostel-notification-function을 nil로 설정하고, 다른 곳으로 보내고 싶다면 사용자 함수를 지정하세요.
사용자 정의 핸들러는 제목과 본문을 받아 원하는 곳으로 보낼 수 있습니다:
(setq ghostel-notification-function (lambda (title body) (alert body :title (or title "ghostel") :category 'ghostel)))
ConEmu의 OSC 9;4 진행 상태 프로토콜도 인식합니다. 빌드 도구, Claude Code 같은 AI 에이전트, 기타 장시간 실행 명령이 완료 비율을 보고할 때 사용합니다. Ghostel은 이를
(STATE
PROGRESS)
인자로 ghostel-progress-function에 전달합니다. 여기서 STATE는 remove, set, error, indeterminate, pause 중 하나이고 PROGRESS는 0-100 사이의 정수 또는 nil입니다.
내장 핸들러는 두 가지입니다:
ghostel-default-progress - mode-line-process에 일반 텍스트를 표시: [42%], [...], [err 73%], [paused 25%], 또는 remove일 때 지움. 의존성 없음.ghostel-spinner-progress - indeterminate 상태(예: Claude Code가 작업 중일 때) 동안 spinner.el로 mode-line-process를 애니메이션하고, 다른 상태에서는 같은 텍스트 표시로 대체.ghostel-progress-function의 기본값은 ghostel 로드 시점에 spinner.el이 load-path에 있으면 ghostel-spinner-progress, 아니면 ghostel-default-progress입니다. 특정 핸들러를 명시적으로 고정할 수도 있습니다:
(setq ghostel-progress-function #'ghostel-spinner-progress) (setq ghostel-progress-function #'ghostel-default-progress) (setq ghostel-spinner-type 'horizontal-moving)
16개의 ANSI 색상은 term-color-*를 상속하는 Emacs face로 정의됩니다:
ghostel-color-black ghostel-color-bright-black ghostel-color-red ghostel-color-bright-red ghostel-color-green ghostel-color-bright-green ghostel-color-yellow ghostel-color-bright-yellow ghostel-color-blue ghostel-color-bright-blue ghostel-color-magenta ghostel-color-bright-magenta ghostel-color-cyan ghostel-color-bright-cyan ghostel-color-white ghostel-color-bright-white
term-color-* face를 사용자 정의하는 테마는 자동으로 적용됩니다. 개별 face는 M-x customize-face로 조정하세요.
기본 텍스트는 default를 상속하는 ghostel-default face를 상속합니다. 이를 사용자 정의하면 ghostel 터미널에 Emacs 나머지 부분과 다른 기본 색상, 글꼴, 크기를 줄 수 있습니다(예: 밝은 Emacs 안의 어두운 터미널):
(set-face-attribute 'ghostel-default nil :foreground "#cdd6f4" :background "#1e1e2e" :family "Iosevka Term")
굵은 텍스트 색상은 ghostel-bold-color를 따릅니다(nil = 일반 텍스트와 같은 색상, bright = 밝은 ANSI 변형 사용, 또는 고정 #RRGGBB 문자열). 이는 Ghostty 1.2.0의 bold-color 설정과 일치합니다.
default-directory가 TRAMP 경로(예: /ssh:host:/home/user/)일 때,
M-x
ghostel
은 TRAMP의 프로세스 기계를 통해 원격 호스트에서 셸을 시작합니다. ghostel-tramp-shells 변수는 TRAMP 메서드별로 사용할 셸을 제어합니다:
(setq ghostel-tramp-shells '(("ssh" login-shell) ("sshx" login-shell) ("scp" login-shell) ("docker" "/bin/sh")))
각 항목은 (METHOD SHELL [FALLBACK [ARG...]]) 형태입니다. SHELL은 "/bin/bash" 같은 경로이거나, getent passwd를 통해 원격 사용자의 로그인 셸을 자동 감지하는 login-shell 심볼일 수 있습니다. 감지에 실패하면 FALLBACK이 사용됩니다.
FALLBACK 뒤의 요소들은 셸에 넘길 추가 인자입니다. 아무 인자도 주지 않으면 ghostel이 셸 종류를 인식해 기본값을 넣습니다. 인식된 셸(bash, zsh, fish)은 로그인+대화형 셸(-l -i)로 시작되어 사용자의 rc/profile 파일을 source 하며, 이는 대화형 ssh host 로그인과 비슷합니다. 인식되지 않는 셸(예: /bin/sh)에는 인자를 주지 않습니다. 이 기본값은 원격 셸 통합(아래 참고)을 켠 경우에도 적용되어, 사용자의 설정이 함께 로드됩니다. 단, bash는 통합이 의존하는 --rcfile을 로그인 bash가 무시하기 때문에 그 경우 -i만 사용합니다. 이를 덮어쓰려면 FALLBACK 뒤에 인자를 명시적으로 나열하세요:
(setq ghostel-tramp-shells '(("ssh" login-shell nil "-i") ("docker" "/bin/sh")))
OSC 7 디렉터리 추적은 TRAMP를 인식합니다. 셸이 원격 호스트 이름을 보고하면, default-directory는 해당 TRAMP 경로로 설정되며 가능한 경우 기존 TRAMP 접두사(메서드, 사용자, 다중 홉)를 재사용합니다. 접두사가 없으면 메서드는 tramp-default-method가 기본입니다. ghostel에서만 이를 덮어쓰려면 ghostel-tramp-default-method를 설정하세요(예: "scp", 또는 emacs-tramp-rpc와 함께 "rpc").
원격 터미널은 SSH 스타일 TRAMP 메서드를 통해 접근하는 POSIX 원격지를 지원합니다. 이 경로에서는 Windows 원격지는 지원되지 않습니다. Ghostel의 원격 래퍼는 POSIX 셸과 tty 기능을 사용하기 때문입니다.
동적 원격 리사이즈 지원은 로컬 플랫폼과 TRAMP 메서드에 따라 달라집니다. POSIX 로컬 Emacs 빌드는 SSH 클라이언트가 창 크기 변화를 원격 PTY로 전파하는 것에 의존할 수 있습니다. Windows에서 TRAMP로 가는 원격 터미널도 실행은 가능하지만, 동적 리사이즈는 현재 지원되지 않습니다.
기본적으로 원격 세션에는 셸 통합 스크립트가 주입되지 않습니다. 이를 활성화하는 방법은 두 가지입니다.
ghostel-tramp-shell-integration을 t로 설정하면 ghostel이 통합 스크립트를 원격 호스트로 자동 전송합니다:
(setq ghostel-tramp-shell-integration t)
이 방식은 원격 호스트에 작은 임시 파일을 만들며(터미널 종료 시 정리됨), 특정 셸에 대해서만 켤 수도 있습니다:
(setq ghostel-tramp-shell-integration '(bash zsh))
ghostel의 etc/shell/ 디렉터리에서 통합 스크립트를 각 원격 호스트(예: ~/.local/share/ghostel/)로 복사하고, 셸 설정에서 이를 source 하세요. 선택적으로 번들된 xterm-ghostty terminfo도 같은 위치에 둘 수 있습니다. TRAMP로 시작한 원격 셸을 실행하는 래퍼가 terminfo 검색 경로 앞에 ~/.local/share/ghostel/terminfo를 추가하므로, ghostty를 인식하는 앱들(Claude Code, neovim, tmux, …)이 tic나 ~/.terminfo 없이도 빠른 경로를 사용할 수 있습니다(Manual install에서 그 대안을 참고). 로컬 셸에서:
ssh REMOTE 'mkdir -p ~/.local/share/ghostel/terminfo' scp "$EMACS_GHOSTEL_PATH"/etc/shell/ghostel.{bash,zsh,fish,nu} REMOTE:.local/share/ghostel/ scp -r "$EMACS_GHOSTEL_PATH"/etc/terminfo/{x,78} REMOTE:.local/share/ghostel/terminfo/
($EMACS_GHOSTEL_PATH는 ghostel 버퍼 안에서 설정됩니다. 밖에서는 ghostel 패키지 설치 경로로 바꾸세요. terminfo scp는 선택 사항입니다. 이것이 없으면 TRAMP로 시작한 원격 셸은 TERM=xterm-256color로 대체되며, 이 경우 echo와 기본 색상은 동작하지만 ghostty 전용 빠른 경로는 없습니다.)
그다음 원격 셸 설정에 적절한 게이트를 추가하세요:
if [[ "${INSIDE_EMACS%%,*}" = 'ghostel' || "$TERM" = 'xterm-ghostty' ]]; then source ~/.local/share/ghostel/ghostel.bash fi
if [[ "${${INSIDE_EMACS-}%%,*}" = 'ghostel' || "$TERM" = 'xterm-ghostty' ]]; then source ~/.local/share/ghostel/ghostel.zsh fi
if string match -qr '^ghostel(,|$)' -- "$INSIDE_EMACS"; or test "$TERM" = 'xterm-ghostty' source ~/.local/share/ghostel/ghostel.fish end
source ~/.local/share/ghostel/ghostel.nu
이 두 절 게이트는 원격 ghostel 셸에 도달하는 두 방식을 모두 다룹니다:
/ssh:host: 경로에서 M-x ghostel)은 원격에서 INSIDE_EMACS를 ghostel,tramp:VER로 다시 씁니다. ${INSIDE_EMACS%%,*} 접두사 매칭이 이를 잡아냅니다.ssh REMOTE 는 ssh를 통해 INSIDE_EMACS를 전파할 수 없습니다. SetEnv는 서버 측 AcceptEnv가 있어야 효과가 있기 때문입니다. 대신 게이트는 TERM에 의존합니다. 이 값은 SSH 프로토콜이 네이티브로 전파합니다. Ghostel은 로컬 PTY 셸 환경에서 TERM=xterm-ghostty를 설정하며(ghostel-term으로 제어), 버퍼 안에서 시작한 모든 ssh는 이 값을 상속하고 전달합니다.두 번째 절이 일치하지만 실제로 ghostel 세션이 아닌 상황, 즉 오탐의 예로는 non-ghostel ghostty 터미널에서의 ssh, 같은 TERM을 그대로 가져가는 중첩 ssh 홉, 사용자가 수동으로 TERM=xterm-ghostty를 export한 경우 등이 있습니다. 이런 상황에서 통합을 source 해도 해롭지 않습니다(OSC 7 / OSC 133은 일반 ghostty에서도 동작하고, 반대쪽에 ghostel이 없으면 ghostel_cmd는 no-op가 됩니다).
ghostel-term을 xterm-ghostty가 아닌 다른 값으로 사용자 정의하면 두 번째 절은 일치하지 않습니다. 이 절을 제거하고 TRAMP로 시작한 ghostel에만 의존해 원격 통합을 사용하거나, 사용자 정의한 TERM에 맞는 매칭으로 바꾸세요.
통합 스크립트는 디렉터리 추적(OSC 7), 프롬프트 탐색(OSC 133), 셸에서 Elisp를 호출하기 위한 ghostel_cmd를 제공합니다.
xterm-ghostty terminfoGhostel은 버퍼 안의 앱들이 전체 capability 집합(동기화된 출력, Kitty 키보드 등)을 받도록 TERM=xterm-ghostty를 설정합니다. 이 TERM 값은 버퍼 안에서 시작된 모든 것에 상속됩니다. 여기에는
ssh
REMOTE
및 TRAMP default-directory에서 실행하는 M-x ghostel이 포함됩니다. 원격 호스트에 xterm-ghostty 항목이 없으면 Error opening terminal: xterm-ghostty가 출력됩니다.
ghostel-ssh-install-terminfo(기본값 auto)가 두 경우를 모두 처리합니다. auto는 ghostel-tramp-shell-integration이 켜져 있을 때 활성화되므로, 원격 통합을 켜면 terminfo 설치도 함께 켜집니다. 즉 스위치 하나입니다.
TRAMP 경로(/ssh:host:/path/)에서 M-x ghostel을 실행하면 셸이 원격에서 시작됩니다. Ghostel은 기존 TRAMP 연결을 통해 번들된 컴파일 완료 terminfo를 원격 임시 디렉터리로 밀어 넣고(추가 ssh 왕복 없음), 원격 셸 환경에
TERMINFO=<that
dir>
를 설정하며, 종료 시 정리합니다. Linux(x/, g/)와 macOS(78/, 67/) 레이아웃을 모두 써 넣어 ncurses 또는 BSD libcurses가 어느 쪽이든 찾을 수 있게 합니다. 원격에 영구적으로 남는 것은 없습니다.
ssh번들된 bash/zsh/fish 통합은 ssh를 다음과 같은 함수로 가립니다:
ssh -G로 표준 대상 이름을 확인합니다(ssh config 별칭 정규화).~/.cache/ghostel/ssh-terminfo-cache에서 대상을 조회합니다. 캐시 키에는 로컬 terminfo의 해시가 포함되므로 libghostty가 올라가면 자동으로 무효화됩니다. 캐시 적중 → 기억된 TERM으로 연결.tic -x -를 통해 ~/.terminfo/에 설치합니다. 결과로 ok(xterm-ghostty 사용) 또는 skip(xterm-256color 사용)을 캐시에 기록합니다.TERM으로 사용자의 실제 ssh를 실행합니다.setup ssh는 새로운 호스트마다 추가 연결 1회가 필요합니다. ControlMaster가 없으면 첫 번째 연결에서 인증 프롬프트가 두 번 보일 수 있습니다. 강력히 권장합니다:
Host * ControlMaster auto ControlPath ~/.ssh/cm-%r@%h:%p ControlPersist 60s
이렇게 하면 setup 연결과 실제 연결이 하나의 인증을 공유합니다. 이후 ControlPersist 기간 내의 연결은 추가 비용이 없습니다.
캐시 키에는 로컬 terminfo의 해시가 포함되므로 libghostty가 올라가면 자동 무효화됩니다. 하지만 원격의 terminfo가 대역 외로 바뀌는 경우(시스템 업데이트, 수동 tic)는 감지하지 못합니다. 강제로 다시 조사하려면
M-x
ghostel-ssh-clear-terminfo-cache
를 실행하세요.
macOS에서 Linux 원격지로의 동작은 검증되었습니다. macOS↔macOS 또는 BSD 대상 혼합에서도 tic의 네이티브 hashed-dir 레이아웃(~/.terminfo/<hex>/)을 상속하고, infocmp도 같은 경로를 읽으므로 올바르게 짝을 이룹니다.
설치 건너뛰기 휴리스틱:
ssh HOST cmd (사용자가 원격 명령을 전달한 경우): 래퍼는 사용자 명령과 충돌하지 않도록 해당 호출에서 설치를 건너뜁니다. 캐시된 TERM이 알려져 있으면 그것으로 연결하고, 아니면 xterm-256color를 사용합니다. 다음 대화형 ssh HOST에서 설치가 트리거됩니다.ssh -V, ssh -h 등(호스트를 해석할 수 없는 경우): 그대로 통과.infocmp가 없는 경우: 그대로 통과.호출별 탈출구: GHOSTEL_SSH_KEEP_TERM=1을 앞에 붙이면 래퍼를 완전히 우회합니다.
ghostel이 원격 호스트를 건드리지 않게 하고 싶고(자동 캐시도 원하지 않는다면), (setq ghostel-ssh-install-terminfo nil)로 설정하고 호스트별로 한 번씩 직접 항목을 설치하세요.
로컬 항목을 파이프로 넘기기:
infocmp -x xterm-ghostty | ssh REMOTE 'mkdir -p ~/.terminfo && tic -x -'
또는 패키지 디렉터리의 번들된 컴파일 완료 바이너리를 복사하기:
ssh REMOTE 'mkdir -p ~/.terminfo/x' scp <package-dir>/etc/terminfo/x/xterm-ghostty REMOTE:~/.terminfo/x/ ssh REMOTE 'uname' | grep -q Darwin && { ssh REMOTE 'mkdir -p ~/.terminfo/78' scp <package-dir>/etc/terminfo/78/xterm-ghostty REMOTE:~/.terminfo/78/ }
이후에는 원격의 모든 셸이 xterm-ghostty를 볼 수 있으므로 ghostel의 바깥으로 나가는 ssh 래퍼는 필요 없어집니다.
로컬에서 TERM=xterm-ghostty를 제거하려면 (setq ghostel-term "xterm-256color")를 설정하세요. 광고도 없고, terminfo 관련 처리도 없으며, 동기화된 출력 빠른 경로도 사라집니다.
모든 변수는 M-x customize-group RET ghostel RET로 사용자 정의할 수 있습니다. 아래 표는 영역별로 묶은 것이며, 표시된 기본값은 기본 설정 그대로의 값입니다.
| Variable | Default | Description |
|---|---|---|
ghostel-shell | $SHELL (없으면 /bin/sh) | 실행할 셸 프로그램. 문자열 또는 실행 파일 + 인자 목록. |
ghostel-use-native-pty | t | 로컬 버퍼에 네이티브 PTY 리더 사용. 원격 TRAMP 버퍼는 항상 Emacs 프로세스를 사용. |
ghostel-term | "xterm-ghostty" | TERM 값. 번들 terminfo의 전체 capability 집합을 광고. 대체하려면 "xterm-256color"로 설정. |
ghostel-environment | nil | 추가 환경 변수("KEY=VALUE" 문자열 목록, "KEY"만 주면 해제). dir-locals를 통해 존중됨. |
ghostel-macos-login-shell | macOS에서 t | 셸을 login(1)으로 감싸 로그인 셸로 시작(Terminal.app / Ghostty와 일치). |
ghostel-pre-spawn-hook | nil | make-process 직전에 실행되는 훅. process-environment가 마지막 환경 조정용으로 바인딩됨. |
ghostel-buffer-name | "*ghostel*" | 기본 버퍼 이름. |
ghostel-project-buffer-scope | both | 프로젝트 명령이 버퍼를 범위화하는 방식: default-directory, identity, 또는 both. |
ghostel-buffer-name-function | ghostel-buffer-name-by-title | OSC 2 제목 + default-directory를 버퍼 이름으로 매핑(nil을 반환하면 유지). nil이면 비활성화. |
ghostel-kill-buffer-on-exit | t | 셸 프로세스가 끝나면 버퍼를 종료. |
ghostel-query-before-killing | auto | 실행 중 버퍼를 종료하거나 Emacs를 종료하기 전에 확인: t, nil, 또는 auto(명령 실행 중일 때만). |
ghostel-exit-functions | nil | 터미널 프로세스 종료 시 (BUFFER EVENT)로 호출되는 훅. |
ghostel-command-start-functions | (ghostel--query-before-killing-on-cmd-start) | OSC 133 C(명령 시작) 시 (BUFFER)로 실행되는 훅. |
ghostel-command-finish-functions | (ghostel--query-before-killing-on-cmd-finish) | OSC 133 D(명령 종료) 시 (BUFFER EXIT-STATUS)로 실행되는 훅. |
ghostel-bookmark-check-dir | t | ghostel bookmark로 이동할 때 작업 디렉터리 복원(이동된 재사용 버퍼에 cd). |
| Variable | Default | Description |
|---|---|---|
ghostel-module-auto-install | ask | 네이티브 모듈이 없을 때 할 일: ask, download, compile, 또는 nil. |
ghostel-module-directory | nil | 네이티브 모듈을 보관할 디렉터리(nil = 패키지 디렉터리). |
ghostel-github-release-url | .../releases | 사전 빌드 모듈 다운로드용 기본 URL(포크/미러에 맞게 사용자 정의 가능). |
| Variable | Default | Description |
|---|---|---|
ghostel-shell-integration | t | 셸 통합 자동 주입(bash/zsh/fish). |
ghostel-tramp-shells | (위 참고) | TRAMP 메서드별 사용할 셸, login-shell 자동 감지 포함. |
ghostel-tramp-shell-integration | nil | 원격 TRAMP 세션용 셸 통합 자동 주입(t, nil, 또는 셸 목록). |
ghostel-tramp-default-method | nil | OSC 7에서 새 원격 경로를 만들 때 사용할 TRAMP 메서드(nil = tramp-default-method). |
ghostel-ssh-install-terminfo | auto | 원격 호스트에 xterm-ghostty terminfo 설치. auto는 ghostel-tramp-shell-integration을 따름. |
| Variable | Default | Description |
|---|---|---|
ghostel-max-scrollback | 5 MB | 스크롤백 최대 크기(버퍼에 물질화됨, 80열 기준 약 5,000줄). |
ghostel-timer-delay | 0.033 | 기본 redraw 지연 시간(초, 약 30 fps). |
ghostel-adaptive-fps | t | 적응형 프레임 속도(유휴 후 지연 단축, 유휴 시 타이머 중지). |
ghostel-immediate-redraw-interval | 0.05 | 즉시 redraw를 허용할 마지막 키 입력 이후 최대 초. |
ghostel-inhibit-redraw-functions | nil | 비정상 훅(각 함수는 버퍼를 인자로 호출됨). 하나라도 non-nil을 반환하면 redraw가 연기됨. ghostel-ime 같은 추가 기능이 사용. |
ghostel-inhibit-anchor-functions | nil | 비정상 훅(각 함수는 WINDOW와 FORCE를 인자로 호출됨). non-nil이면 해당 창의 anchoring을 건너뜀. evil-ghostel 같은 추가 기능이 사용. |
ghostel-cell-pixel-scale | auto | 셀 크기 보고용 물리:논리 픽셀 비율(auto는 DPI에서 파생). |
ghostel-glyph-scale-floor | 0.0 | 셀에 맞지 않는 글리프의 최소 스케일(0.0은 그리드 정렬 유지, 1.0은 CJK를 자연 크기로 렌더링). |
| Variable | Default | Description |
|---|---|---|
ghostel-kitty-graphics-storage-limit | 320 MiB | 터미널당 kitty graphics 저장소 상한. 0이면 kitty graphics를 완전히 비활성화. |
ghostel-kitty-graphics-mediums | nil | 인라인 base64 외의 이미지 로딩 매체에 대한 opt-in: (file temp-file shared-mem)의 부분집합. |
| Variable | Default | Description |
|---|---|---|
ghostel-enable-url-detection | t | 터미널 출력의 일반 텍스트 URL을 링크화. |
ghostel-enable-file-detection | t | 터미널 출력의 file:line 참조를 링크화. |
ghostel-file-detection-path-regex | (regex) | file:line[:col] 감지 패턴의 경로 부분. |
ghostel-plain-link-detection-delay | 0.1 | redraw에 의해 트리거된 링크 감지가 실행되기 전 지연(0 = 즉시). |
ghostel-enable-osc52 | nil | 앱이 OSC 52를 통해 클립보드를 설정하도록 허용. |
ghostel-eval-cmds | (위 참고) | OSC 52;e eval용 허용 목록 함수. |
| Variable | Default | Description |
|---|---|---|
ghostel-detect-password-prompts | t | 비밀번호 프롬프트를 감시하고 read-passwd를 띄움. |
ghostel-password-prompt-functions | (ghostel--default-password-source) | 비밀번호를 얻기 위해 순서대로 시도할 소스(auth-source 등을 앞에 추가). |
ghostel-password-prompt-regex | comint-password-prompt-regexp | 커서 줄 regex 대체 경로(원격 셸에서만). |
ghostel-password-prompt-debounce | 0.2 | rising edge 후 read-passwd를 열기까지 기다릴 초. |
| Variable | Default | Description |
|---|---|---|
ghostel-notification-function | ghostel-default-notify | OSC 9 / OSC 777 알림 핸들러(nil이면 비활성화). |
ghostel-progress-function | spinner 사용 가능 시 spinner, 아니면 ghostel-default-progress | OSC 9;4 진행 상태 핸들러(nil이면 비활성화). |
ghostel-spinner-type | progress-bar | ghostel-spinner-progress용 spinner 스타일(spinner-types 참고). |
| Variable | Default | Description |
|---|---|---|
ghostel-keymap-exceptions | ("C-c" "C-x" ...) | 세미-문자 모드에서 Emacs로 전달되는 키. |
ghostel-ignore-cursor-change | nil | 터미널이 주도하는 커서 모양/가시성 변경 무시. |
ghostel-readonly-fast-exit | t | 복사/Emacs 모드에서 q, C-g, 또는 self-insert 키로 종료. |
ghostel-readonly-fake-cursor | t | 복사/Emacs 모드에서 실시간 터미널 위치에 힌트 커서 표시. |
ghostel-mouse-drag-input-mode | copy | 클릭/드래그/다중 클릭 선택 후 들어갈 모드: copy, emacs, 또는 nil. |
ghostel-mark-activation-input-mode | copy | 명령이 mark를 활성화할 때 들어갈 모드: copy, emacs, 또는 nil. |
ghostel-point-leave-input-mode | copy | point가 입력 위치를 벗어날 때(isearch/minibuffer) 들어갈 모드: copy, emacs, 또는 nil. |
ghostel-word-boundary-string | (docstring 참고) | 더블 클릭 선택과 단어 이동에서 단어를 끝내는 문자(Ghostty의 selection-word-chars를 반영). |
ghostel-scroll-on-input | t | 스크롤백에 올라가 있는 상태에서 입력하면 맨 아래로 이동. |
ghostel-bold-color | nil | 굵은 텍스트 색상 방식: nil, bright, 또는 #RRGGBB 문자열. |
| Variable | Default | Description |
|---|---|---|
ghostel-prompt-regexp | (docstring 참고) | OSC 133 마커가 없을 때 사용할 프롬프트 접두사 regex 대체 경로. |
ghostel-line-mode-history-size | 200 | 줄 모드 히스토리 링의 최대 항목 수. |
ghostel-line-mode-completion-at-point-functions | (comint-completion-at-point) | 줄 모드 TAB에서 활성화되는 capf. |
ghostel-line-mode-use-bash-completion | auto | TAB에 bash 프로그래머블 완성을 겹칠지 여부(auto, t, nil). |
ghostel-line-mode-bash-completion-prespawn | nil | 줄 모드 진입 시 bash-completion 서브프로세스를 미리 시작. |
이 기능들은 코어를 가볍게 유지하기 위해 별도 파일(또는 evil의 경우 별도 패키지)에 있습니다. 사용하는 것만 로드하세요.
Ghostel은 evil-ghostel.el을 통해 선택적 evil-mode 지원을 포함합니다. 이것은 evil 상태 전환 동안 터미널 커서를 Emacs point와 동기화하여 normal state 탐색(hjkl 등)이 올바르게 동작하게 합니다.
evil-ghostel은 ghostel에 의존하는 독립적인 MELPA 패키지로 배포됩니다. ghostel과 함께 설치하세요:
(use-package evil-ghostel :ensure t :after (ghostel evil) :hook (ghostel-mode . evil-ghostel-mode))
또는 소스에서 설치할 수 있습니다(Emacs 30+). :lisp-dir은 package-vc가 ghostel monorepo 안의 이 확장 하위 디렉터리를 가리키게 합니다:
(use-package evil-ghostel :vc (:url "https://github.com/dakra/ghostel" :lisp-dir "extensions/evil-ghostel" :rev :newest) :after (ghostel evil) :hook (ghostel-mode . evil-ghostel-mode))
evil-ghostel-mode가 활성화되면:
h, j, k, l, w, b, e, 0, $, …)이 기대대로 동작합니다.i, a, I, A)는 insert state로 들어가기 전에 터미널 커서를 point에 동기화합니다.d, dw, dd, D, x, X)는 텍스트를 kill ring으로 복사하고 셸을 통해 삭제합니다.c, cw, cc, C, s, S)은 삭제 후 insert state로 들어갑니다.r)은 커서 아래 문자를 치환합니다.p, P)는 kill ring에서 브래킷된 붙여넣기로 붙여넣습니다.u)는 readline undo (Ctrl+_)를 보냅니다.초기 상태와 ESC 동작은 evil-ghostel-initial-state(기본값 insert)와 evil-ghostel-escape(기본값 auto)로 설정할 수 있습니다.
ghostel-compile은 ghostel 버퍼에서 셸 명령을 실행하고 결과를 M-x compile처럼 보여줍니다. 즉 compilation-mode 스타일의 헤더, 푸터, 오류 강조, next-error 탐색을 제공하지만, 실제 TTY 위에서 동작하므로 isatty(3)를 확인하는 프로그램(색 있는 출력, 진행 막대, curses 도구)이 일반 셸과 동일하게 동작합니다.
각 호출은 ghostel이 렌더링하는 PTY를 통해 shell-file-name -c COMMAND로 새 프로세스를 생성합니다. 명령과 사용자 사이에 대화형 셸이 있지 않으므로 여러 줄 셸 스크립트도 그대로 전달되며, 셸 통합 설정도 필요하지 않습니다. 프로세스 sentinel은 실제 종료 상태를 전달합니다.
ghostel-compile은 M-x ghostel과 같은 TERM=xterm-ghostty 및 TERMINFO…= 환경을 상속하므로, 빌드 출력도 동기화된 출력, 트루컬러 등을 사용할 수 있습니다. 테스트 러너나 빌드 도구가 익숙하지 않은 TERM 때문에 혼란스러워하면
(setq
ghostel-term "xterm-256color")
를 설정하세요.
(require 'ghostel-compile)
(global-set-key (kbd "C-c c") #'ghostel-compile)
명령:
| Command | Description |
|---|---|
M-x ghostel-compile | 읽기 전용 ghostel 버퍼에서 명령 실행(compile-command 사용). |
C-u M-x ghostel-compile | 명령을 물어보고 대화형 터미널 입력과 함께 실행. |
M-x ghostel-recompile | 마지막 명령을 원래 디렉터리에서 다시 실행(실행 모드 유지). |
M-x ghostel-compile-global-mode | 모든 compile 스타일 호출을 ghostel로 라우팅(opt-in). |
실행 결과는 M-x compile 버퍼처럼 보입니다:
-- mode: ghostel-compile -- Compilation started at Wed Apr 15 08:30:11
make -j4 test
...command output (live, with full TTY)...
Compilation finished at Wed Apr 15 08:30:19, duration 8.20 s
기본적으로 버퍼는 처음부터 읽기 전용이며 탐색 가능합니다. 즉 M-x compile 버퍼와 같습니다. g로 재실행하고, n / p로 오류를 순회하며(실행이 끝난 뒤 파싱됨), RET로 소스로 이동합니다. 키 입력은 실행 중인 프로세스에 도달하지 않으므로, 명령 실행 중에도 "compile-mode" UX(색 있는 출력 읽기, C-c C-c로 중단)를 사용할 수 있습니다.
prefix 인자(C-u M-x ghostel-compile, C-u M-x compile과 동일)를 주면 대신 대화형 모드로 시작합니다. 이 경우 키 입력이 실행 중인 프로세스로 전달되므로 htop, less, 입력을 묻는 테스트 러너 등 실시간 키 입력이 필요한 모든 프로그램이 동작합니다. 렌더링된 버퍼는 계속 읽기 전용입니다. ghostel-recompile (g)는 버퍼가 어떤 모드로 시작되었는지 그대로 유지합니다.
명령이 끝나면 실시간 프로세스와 ghostel 렌더러가 정리되고, 버퍼의 major mode는 ghostel-compile-view-mode(compilation-mode에서 파생)로 전환됩니다. mode-line-process는 명령 실행 중 :run, 이후에는 :exit [N]를 표시합니다. 대화형 실행은 :run 대신 :run/i로 표시됩니다.
때로는 명령이 입력을 필요로 한다는 사실이 뒤늦게 드러납니다. 예를 들어 read -p, git push 비밀번호 프롬프트, y/n을 묻는 테스트 러너, 또는 실행 중간에 htop에 붙고 싶은 경우가 그렇습니다. 다음 두 키는 프로세스를 재시작하지 않고 버퍼 상태를 전환합니다:
| Key | Action |
|---|---|
C-c C-j | 대화형 터미널 입력으로 전환 |
C-c C-e / C-c C-t | 다시 컴파일 스타일 탐색으로 전환 |
(C-c C-t는 ghostel-mode에서 복사 모드로 들어가는 키와 맞추어져 있어서, 같은 근육 기억을 compile 버퍼에서도 사용할 수 있습니다.) 두 키 모두 ghostel-compile-toggle-mode라는 버퍼 로컬 minor mode에 바인딩되며, 이 모드는 compile 버퍼에서 자동 활성화됩니다. 이후 재컴파일도 마지막으로 전환한 상태를 유지합니다.
ghostel-compile-view-mode, 읽기 전용 실행 중에도 활성)| Key | Action |
|---|---|
g | ghostel-recompile로 재실행 |
n / p | point를 다음 / 이전 오류로 이동(자동 열기 없음) |
RET / mouse-2 | point 아래 오류의 소스로 이동 |
M-g n / M-g p | 표준 next-error / previous-error |
C-c C-c | compile-goto-error (RET와 동일) |
C-c C-k | kill-compilation - 실행 중인 프로세스 중단 |
C-c C-j / C-c C-e / C-c C-t | 대화형 입력 / 컴파일 스타일 탐색 전환 |
다음 표준 compile 옵션이 존중됩니다: compile-command / compile-history(M-x compile과 공유), compilation-read-command, compilation-ask-about-save, compilation-auto-jump-to-first-error, compilation-finish-functions. 출력 스크롤은 항상 활성화됩니다.
ghostel-recompile는 g를 누르는 시점에 어느 버퍼에 있든 상관없이, 원래 ghostel-compile이 호출된 디렉터리에서 실행됩니다.
compile / recompile / project-compile가 ghostel을 사용하게 만들기ghostel-compile-global-mode를 활성화하면 compilation-start에 advice를 걸어, 이를 거치는 모든 호출 M-x compile, M-x recompile, M-x project-compile, 그리고 compilation-start를 사용하는 모든 서드파티 명령이 자동으로 ghostel 버퍼에서 실행되게 합니다.
(require 'ghostel-compile) (ghostel-compile-global-mode 1)
호출 라우팅 방식:
M-x compile(또는 MODE=nil, compilation-mode, compilation-mode의 하위 클래스를 넘기는 호출자) → 읽기 전용 ghostel 버퍼. 하위 클래스도 존중되며, 버퍼가 최종화될 때 해당 오류 regexp, font-lock 키워드, 키맵이 적용됩니다.C-u M-x compile(즉 compilation-start COMMAND t, comint 변형) → 기본 comint-mode 대신 대화형 ghostel 버퍼.grep-mode는 실시간 TTY와 잘 맞지 않는 출력 파싱 및 창 관리 관습 때문에 기본 compilation-start 구현으로 통과합니다. 다른 모드도 제외하려면 ghostel-compile-global-mode-excluded-modes를 확장하세요.Ghostel 전용 사용자 정의:
| Option | Effect |
|---|---|
ghostel-compile-buffer-name | 버퍼 이름(기본값 *ghostel-compile*). |
ghostel-compile-finished-major-mode | 각 실행 후 major mode(기본값 ghostel-compile-view-mode; nil이면 ghostel-mode에 머묾). |
ghostel-compile-finish-functions | Ghostel 전용 finish hook(compilation-finish-functions와 함께 실행). |
ghostel-compile-global-mode-excluded-modes | 전역 advice가 기본 compile로 통과시키는 모드(기본값 (grep-mode)). |
ghostel-compile-debug | 수명 주기 이벤트를 *Messages*에 기록(기본값 nil). |
compile 버퍼 밖에서도, 두 훅을 사용하면 어떤 ghostel 버퍼의 어떤 셸 명령에도 반응할 수 있습니다:
ghostel-command-start-functions - 셸이 OSC 133 C(명령 시작)를 출력하면 (BUFFER)로 호출됩니다.ghostel-command-finish-functions - 셸이 OSC 133 D(명령 종료)를 출력하면 (BUFFER EXIT-STATUS)로 호출됩니다.개별 훅 함수가 오류를 던져도 이를 잡아 로그에 남기므로, 하나의 잘못된 소비자가 나머지를 망가뜨릴 수 없습니다.
ghostel-eshell-visual-command-mode는 eshell이 "visual" 명령들(eshell-visual-commands, eshell-visual-subcommands, eshell-visual-options에 들어 있는 vim, htop, less, top, git log의 pager 등)을 기본 term-mode 대체 경로 대신 전용 ghostel 버퍼에서 실행하게 하여, 실제 터미널 에뮬레이터를 쓰도록 만듭니다.
(require 'ghostel-eshell) (add-hook 'eshell-load-hook #'ghostel-eshell-visual-command-mode)
프로그램이 종료되면 버퍼는 [Process exited] 상태로 남아 있어 남은 출력을 읽을 수 있습니다(창 point가 끝으로 맞춰지므로 스크롤 없이도 보입니다). 죽은 버퍼를 닫으려면 q를 누르세요. 대신 종료 시 자동으로 버퍼를 죽이고 싶다면 eshell-destroy-buffer-when-process-dies를 t로 설정하세요.
eshell-visual-commands를 수정하지 않고 ad-hoc 명령을 ghostel 버퍼에서 실행하려면 ghostel eshell 내장 명령을 사용하세요:
~ $ ghostel nethack
원한다면 더 짧은 별칭을 추가할 수도 있습니다:
(defalias 'eshell/v 'eshell/ghostel)
| Option | Effect |
|---|---|
ghostel-eshell-track-title | non-nil이면 프로그램이 OSC 제목 이스케이프로 visual-command 버퍼 이름을 바꾸도록 허용. 기본값 nil(*vim*을 안정적으로 유지). |
이 모드의 공개 기본 도구는
ghostel-exec BUFFER PROGRAM &optional
ARGS
입니다. 셸 통합 없이 임의의 프로그램을 ghostel 버퍼에서 실행합니다. 사용자 통합을 만들 때 유용합니다.
ghostel-comint-mode는 comint의 내장 ansi-color-process-output을, 모든 프로세스 출력 청크를 libghostty-vt의 VT 파서에 통과시키는 스트림 필터로 교체합니다. M-x shell(및 다른 모든 comint 파생 버퍼 - REPL 등)에서 출력은 실제 ghostel 터미널과 같은 SGR 충실도로 렌더링되며, OSC 8 하이퍼링크와 OSC 7 디렉터리 추적도 지원합니다.
(require 'ghostel-comint) (add-hook 'shell-mode-hook #'ghostel-comint-mode)
또는 모든 comint 파생 버퍼에서 한 번에 활성화하려면:
(ghostel-comint-global-mode 1)
기본 필터(및 xterm-color) 대비 장점:
| Feature | Stock ansi-color | xterm-color | ghostel-comint |
|---|---|---|---|
| ANSI 8 / bright / 256 / truecolor | yes | yes | yes |
| Italic, bold, faint, strike-through, overline, inverse | partial | yes | yes |
Curly / double / dotted / dashed underline (\e[4:3m, …) | - | - | yes |
Underline color (\e[58;...m) | - | - | yes |
OSC 8 hyperlinks (gh, git, ls --hyperlink=auto) | - | - | yes |
| OSC 7 working-directory updates | - | - | yes |
| DCS / APC / SS3 sequences consumed cleanly | - | - | yes |
여전히 이것은 스트림 필터이지 완전한 터미널 은 아닙니다. 커서 위치 지정 이스케이프, alt-screen 진입(\e[?1049h), 전체 화면 redraw는 조용히 버려집니다. 따라서 htop이나 less 같은 프로그램은 여기서 올바르게 렌더링되지 않습니다. 그런 경우에는 실제 터미널인 M-x ghostel을 사용하세요.
CR / BS / TAB은 변경 없이 통과하므로, comint 자체의 comint-carriage-motion 필터가 진행 막대, read -s 프롬프트 등을 계속 처리할 수 있습니다.
최상의 성능을 위해 xterm-color가 shell 버퍼에서 font-lock을 끄라고 조언하는 내용이 여기에도 적용됩니다. 자세한 내용은 ghostel-comint-mode의 docstring을 보세요.
일부 Emacs Lisp 입력기(hangul-input-method가 대표적 예)는 키 이벤트를 반환하는 대신 현재 버퍼에 텍스트를 삽입해 commit합니다. ghostel 버퍼 안에서는 그 삽입이 버퍼에는 들어가지만 PTY로 전송되지 않으므로, 다음 redraw에서 사라집니다.
ghostel-ime-mode는 버퍼 로컬 input-method-function을 감싸는 선택적 minor mode입니다. 입력기가 버퍼 삽입으로 commit할 때, 이 래퍼는 일시적인 삽입을 삭제하고 확정된 텍스트를 UTF-8로 PTY에 전송하여, 셸이 이를 정상적인 redraw 경로로 다시 echo하게 합니다. 또한 Quail 스타일 조합이 진행 중일 때는 ghostel에 redraw를 미루도록 요청합니다(ghostel-inhibit-redraw-functions를 통해). 따라서 렌더러가 조합 중간에 버퍼를 다시 쓰지 않습니다. GUI 네이티브 preedit 처리는 영향을 받지 않습니다.
(use-package ghostel-ime :hook (ghostel-mode . ghostel-ime-mode))
이것은 quail-overlay를 사용하는 모든 Lisp 입력기(한국어 Hangul, 일본어, 중국어 등)에 일반화됩니다.
| Command | Description |
|---|---|
M-x ghostel | 새 터미널 열기(prefix 인자를 주면 새 버퍼 생성). |
M-x ghostel-project | 현재 프로젝트 루트에서 터미널 열기(prefix 인자를 주면 새 버퍼). |
M-x ghostel-other | 다음 터미널로 전환하거나 새로 생성. |
M-x ghostel-next | 다음 ghostel 버퍼로 순환(이름순, 끝에서 처음으로). |
M-x ghostel-previous | 이전 ghostel 버퍼로 순환. |
M-x ghostel-list-buffers | read-buffer로 ghostel 버퍼 선택. |
M-x ghostel-project-next | 현재 프로젝트의 다음 ghostel 버퍼로 순환. |
M-x ghostel-project-previous | 현재 프로젝트의 이전 ghostel 버퍼로 순환. |
M-x ghostel-project-list-buffers | 프로젝트 범위의 ghostel 버퍼 선택. |
M-x ghostel-clear | 화면과 스크롤백 지우기. |
M-x ghostel-clear-scrollback | 스크롤백만 지우기. |
M-x ghostel-semi-char-mode | 세미-문자 입력 모드로 전환(기본값). |
M-x ghostel-char-mode | 문자 입력 모드로 전환. |
M-x ghostel-emacs-mode | Emacs 입력 모드로 전환(읽기 전용, 실시간). |
M-x ghostel-copy-mode | 복사 모드로 들어가기(멈춤). |
M-x ghostel-line-mode | 줄 입력 모드로 전환. |
M-x ghostel-copy-all | 전체 스크롤백을 kill ring에 복사. |
M-x ghostel-paste | kill ring에서 붙여넣기. |
M-x ghostel-send-next-key | 다음 키를 있는 그대로 전송. |
M-x ghostel-next-prompt | 다음 셸 프롬프트로 이동. |
M-x ghostel-previous-prompt | 이전 셸 프롬프트로 이동. |
M-x ghostel-next-hyperlink | 다음 하이퍼링크(OSC 8, URL, 파일 참조)로 이동. |
M-x ghostel-previous-hyperlink | 이전 하이퍼링크로 이동. |
M-x ghostel-force-redraw | 전체 터미널 redraw 강제 실행. |
M-x ghostel-debug-typing-latency | 키 입력별 타이핑 지연 측정. |
M-x ghostel-sync-theme | 테마 변경 후 색상 팔레트 재동기화. |
M-x ghostel-ssh-clear-terminfo-cache | 바깥으로 나가는 ssh의 terminfo 설치 캐시 지우기(재조사 강제). |
M-x ghostel-download-module | 사전 빌드 네이티브 모듈 다운로드. |
M-x ghostel-module-compile | 네이티브 모듈을 소스에서 컴파일. |
실행 중인 ghostel 버퍼에 입력을 주입해야 하는 패키지(에이전트 통합, 사용자 키맵 등)를 위해 공개 함수 두 개가 제공됩니다:
(ghostel-send-string "ls -la\n") (ghostel-send-key "return") (ghostel-send-key "a" "ctrl") (ghostel-send-key "up" "shift,ctrl")
두 함수 모두 현재 버퍼에서 동작합니다. 다른 ghostel 버퍼를 제어할 때는 with-current-buffer로 감싸세요. ghostel 버퍼 밖에서 호출하면 user-error를 발생시킵니다.
ghostel-project는 현재 프로젝트 루트 디렉터리에 프로젝트 접두사가 붙은 버퍼 이름으로 터미널을 엽니다. 이를 project-switch-project (C-x p p)에서 사용할 수 있게 하려면:
(add-to-list 'project-switch-commands '(ghostel-project "Ghostel") t)
테스트는 ERT를 사용합니다. Makefile은 편리한 타깃을 제공합니다:
make test make all make bench-quick
직접 테스트를 실행할 수도 있습니다:
emacs --batch -Q -L . -l ert -l test/ghostel-test.el -f ghostel-test-run-elisp
emacs --batch -Q -L . -l ert -l test/ghostel-test.el -f ghostel-test-run
테스트는 ERT 태그에 따라 두 그룹으로 나뉩니다. Elisp 테스트(make test)에는 native 태그가 없으며 Zig 모듈이 필요하지 않습니다. 네이티브 테스트(make test-native)는 native 태그가 있으며 빌드된 모듈이 필요합니다. 추가 타깃으로 make test-zig(Zig 단위 테스트), make test-all(전체), make test-evil(evil-ghostel 확장)이 있습니다.
Ghostel에는 다른 Emacs 터미널 에뮬레이터와 처리량을 비교하는 벤치마크 스위트가 포함되어 있습니다: vterm (네이티브 모듈), eat (순수 Elisp), Emacs 내장 term.
에뮬레이터 간 공통 벤치마크는 실제 프로세스 파이프를 통해 1 MB 데이터를 스트리밍하고, 각 백엔드의 실제 프로덕션 필터를 통해 전달합니다. 실제 터미널 사용과 일치하도록 설계된 것입니다. 비교를 공정하게 유지하기 위해 모든 백엔드에는 ghostel도 포함되며 이 공통 Emacs 프로세스 경로로 구동됩니다. ghostel의 기본 로컬 경로는 메인 스레드 밖에서 비동기적으로 읽는 네이티브 Zig PTY이며, 이는 아래 Native vs Emacs PTY에서 별도로 측정한 것처럼 더 빠릅니다. 모든 백엔드는 약 1,000줄의 스크롤백(vterm 기본값과 동일)으로 설정됩니다. Apple M4 Max, Emacs 32.0.50에서의 결과:
| Backend | Plain ASCII | URL-heavy |
|---|---|---|
| ghostel | 75 MB/s | 36 MB/s |
| ghostel (no detect) | 76 MB/s | 78 MB/s |
| vterm | 18 MB/s | 15 MB/s |
| eat | 6.2 MB/s | 4.5 MB/s |
| term | 7.2 MB/s | 5.9 MB/s |
Ghostel은 터미널 출력에서 URL과 파일 경로를 스캔해 클릭 가능하게 만듭니다. 감지는 redraw의 hot path 밖에서 병합된 타이머로 실행되므로, 일반적인 출력에서는 사실상 비용이 없습니다. plain-ASCII 줄들은 실행 간 잡음 수준의 차이만 보입니다. URL과 경로가 빽빽한 출력에서는 타이머가 실제로 찾아야 할 매치를 가지게 되어 처리량이 대략 절반으로 줄어듭니다. "no detect" 줄은 ghostel-enable-url-detection과 ghostel-enable-file-detection을 nil로 두었을 때의 순수 엔진 속도를 분리해 보여줍니다. 다른 에뮬레이터들은 이 기능이 없습니다.
위 표는 ghostel을 Emacs 프로세스 경로로 구동하여 vterm/eat/term과 사과 대 사과 비교를 합니다. 로컬 셸의 경우 ghostel은 기본적으로 자체 네이티브 PTY(ghostel-use-native-pty)를 사용하며, 여기서는 Zig 백그라운드 스레드가 PTY를 읽고 libghostty-vt에 직접 공급하며, read가 block될 때만 Emacs를 깨웁니다. 따라서 청크별 필터를 메인 스레드에서 실행하지 않아도 되어 대량 출력이 더 빠르고, 폭주 중에도 UI가 반응성을 유지합니다.
bench/run-bench.sh --backends는 같은 cat 작업을 두 백엔드로 실행하고 비율을 보고합니다. 지속적인 수 MB 덤프에서는 네이티브 경로가 Emacs 경로보다 대략 2배 빠르며, 크기가 커질수록 격차는 더 벌어집니다. 독립 실행형 GPU 터미널과 남아 있는 차이는 Emacs 자체의 버퍼 물질화와 redisplay 비용이며, 이는 두 백엔드가 모두 공유합니다.
cat으로 10 MB 파일 출력)네이티브 PTY를 체감하는 두 번째 방법: 10 MB 로그 파일을 cat하고, 버스트가 완전히 흡수되어 셸 프롬프트가 돌아오기까지 걸리는 시간을 측정합니다. 각 백엔드는 실제 cat 서브프로세스를 실행합니다. 다섯 번 반복의 중앙값, Emacs 32.0.50, Apple M4 Max:
| Backend | Per 10 MB cat |
|---|---|
| ghostel (native PTY) | ~110 ms |
| ghostel (Emacs PTY) | ~220 ms |
| vterm | ~550 ms |
| eat | ~1.8 s |
네이티브 ghostel은 약 10분의 1초 만에 덤프를 흡수합니다. 이는 vterm보다 약 5배, eat보다 16배, 그리고 ghostel 자체의 Emacs PTY 경로보다 2배 빠릅니다. 하지만 벽시계 시간 차이만이 전부는 아닙니다. vterm과 eat은 Emacs 메인 스레드에서 동기적으로 파싱하므로 전체 전송 동안 Emacs가 멈춥니다. 끝날 때까지 입력, 스크롤, 버퍼 전환을 할 수 없습니다. Ghostel의 네이티브 PTY는 Zig 백그라운드 스레드에서 파싱하므로, 같은 버스트도 메인 스레드를 막지 않고 소화합니다. Emacs는 repaint를 위해서만 깨어나므로 전 과정 내내 반응성을 유지합니다. 수백 MB 로그에서는 이것이 상호작용을 유지하며 C-c로 중단할 수 있느냐, 아니면 몇 초 동안 얼어붙느냐의 차이가 됩니다.
상호작용 키 입력은 대량 처리량과 별도로 최적화됩니다. 문자를 입력하면 PTY echo가 감지되어 즉시 렌더링되며(33ms redraw 타이머 우회), 따라서 문자가 화면에 최소 지연으로 나타납니다. 시스템의 종단 간 지연을 측정하려면 M-x ghostel-debug-typing-latency를 사용하세요. 키 입력별 PTY, 렌더링, 전체 지연에 대한 최소/중앙값/p99/최대 통계를 보고합니다.
벤치마크를 직접 실행해 보세요:
bench/run-bench.sh bench/run-bench.sh --quick bench/run-bench.sh --backends
Ghostel, vterm, eat는 Emacs용으로 활발히 개발 중인 세 가지 터미널 에뮬레이터입니다. 이들은 서로 다른 공학적 선택을 합니다. ghostel은 libghostty-vt (Zig, Ghostty의 VT 엔진)를 네이티브 모듈로 구동하고, vterm은 libvterm (C, Neovim 터미널의 엔진)을 자체 모듈로 감싸며, eat은 순수 Emacs Lisp로 작성되어 네이티브 모듈이 전혀 필요 없습니다. 마지막 점이 아래 비교의 많은 부분을 좌우합니다. eat은 빌드 없는 이식성을 위해 순수 처리량을 희생하고, ghostel과 vterm은 C/Zig 엔진을 위해 컴파일 단계를 받아들입니다.
| Feature | ghostel | vterm | eat |
|---|---|---|---|
| 네이티브 모듈 필요 | ✅ | ✅ | ❌ |
| 모듈 자동 다운로드 | ✅ | ❌ | n/a |
| 트루컬러(24비트) | ✅ | ✅ | ✅ |
| OSC 4/10/11 색상 질의 | ✅ | ❌ | partial |
| Bold / italic | ✅ | ✅ | ✅ |
| Faint / dim | ✅ | ❌ | ✅ |
| 밑줄 스타일 | 5 types | ❌ | curly |
| 색 있는 밑줄 | ✅ | ❌ | ✅ |
| 취소선 | ✅ | ✅ | ✅ |
| 커서 모양 | 4 | 3 | 3 |
| 깜빡이는 커서 | ✅ | partial | ✅ |
| Kitty graphics (이미지) | ✅ | ❌ | ❌ |
| Sixel graphics | ❌ | ❌ | ✅ |
| OSC 8 하이퍼링크 | ✅ | ❌ | ❌ |
| 일반 텍스트 URL/파일 감지 | ✅ | ❌ | ❌ |
| OSC 9 / 777 알림 | ✅ | ❌ | ❌ |
| OSC 9;4 진행 상태 보고 | ✅ | ❌ | ❌ |
| 동기화된 출력 (2026) | ✅ | ❌ | ❌ |
| Kitty 키보드 프로토콜 | ✅ | ❌ | ❌ |
| TUI로 마우스 전달 | ✅ | ❌ | ✅ |
| 브래킷된 붙여넣기 | ✅ | ✅ | ✅ |
| 대체 화면 | ✅ | ✅ | ✅ |
| OSC 52 클립보드 | ✅ | ✅ | ✅ |
| 셸 통합 자동 주입 | ✅ | manual | manual |
| 프롬프트 탐색 | ✅ | ✅ | ✅ |
| 셸에서 Elisp eval | ✅ | ✅ | ❌ |
| TRAMP 원격 터미널 | POSIX | ✅ | ✅ |
| Eshell 통합 | ✅ | ❌ | ✅ |
| 북마크 지원 | ✅ | ✅ | ❌ |
| 입력 모드(char/semi/emacs) | ✅ | partial | ✅ |
| 줄 모드(로컬 편집) | ✅ | ❌ | ✅ |
| 복사 모드(출력 멈춤) | ✅ | ✅ | ❌ |
| Emacs 모드(읽기 전용, 실시간) | ✅ | ❌ | ✅ |
| 입력기 전달 | ✅ | ✅ | ❌ |
| 드래그 앤 드롭(파일 경로) | ✅ | ❌ | ❌ |
| 비밀번호 프롬프트 감지 | ✅ | ❌ | manual |
| 기본 스크롤백 | 5 MB | 1,000 ln | 128 KB |
| 기본 redraw 속도 | ~30 fps | ~10 fps | adaptive |
| 처리량, plain ASCII | 75 MB/s | 18 MB/s | 6.2 MB/s |
manual은 지원되지만 수동 설정(source 스크립트)이 필요함을 뜻하고, partial은 일부만 지원함을 뜻합니다(eat은 OSC 10/11에는 응답하지만 OSC 4 팔레트 질의에는 응답하지 않으며, 곡선 밑줄은 렌더링하지만 이중/점선/파선 밑줄은 렌더링하지 않습니다. vterm의 입력 모드는 기본 모드와 복사 모드만 포함합니다). POSIX는 TRAMP 터미널이 POSIX 원격지를 대상으로 함을 뜻합니다. Windows-to-TRAMP 원격 터미널은 현재 동적 리사이즈를 지원하지 않습니다. 스크롤백 단위는 설계상 서로 다릅니다. ghostel과 eat은 바이트 기준, vterm은 줄 기준입니다. 처리량은 Performance의 plain-ASCII 수치입니다(Emacs 32.0.50, Apple M4 Max).
터미널 엔진. libghostty-vt는 Ghostty에서 왔으며, 세 가지 중 Kitty 키보드 및 그래픽 프로토콜, 다섯 가지 밑줄 스타일, OSC 8 하이퍼링크, OSC 4/10/11 색상 질의, DEC 2026 동기화된 출력을 지원하는 유일한 엔진입니다. libvterm은 VT220/xterm 에뮬레이션을 목표로 하며 가장 보수적입니다. 흐린 텍스트도 없고, 단일 밑줄만 있으며, 이미지도 없습니다. eat은 그 중간쯤에 위치합니다. 트루컬러, 흐린 텍스트, 색 있는 밑줄, 심지어 Sixel 이미지도 지원하지만, Kitty 프로토콜, OSC 8, 데스크톱 알림은 지원하지 않습니다.
패키징과 설치. eat의 가장 큰 장점은 순수 Emacs Lisp라는 점입니다. 컴파일이나 다운로드가 전혀 필요 없어 Emacs가 동작하는 곳이면 어디서나 실행할 수 있으며, 네이티브 모듈을 빌드할 수 없는 호스트에서도 마찬가지입니다. Ghostel은 처음 사용할 때 사전 빌드 모듈을 다운로드하거나(Zig로) 빌드합니다. vterm은 첫 로드 시 CMake로 단일 C 의존성을 컴파일합니다. 이 네이티브 엔진들이 ghostel과 vterm에 eat 대비 큰 처리량 우위를 안겨 줍니다.
그래픽. 인라인 이미지는 ghostel에서는 Kitty graphics protocol로, eat에서는 Sixel로 동작합니다. vterm은 둘 다 없습니다. 두 프로토콜은 서로 호환되지 않습니다. Kitty graphics를 출력하는 프로그램은 eat에서 아무것도 보이지 않고, 반대도 마찬가지입니다.
마우스 처리. Ghostel과 eat은 둘 다 마우스 이벤트(누름, 놓음, 드래그)를 인코딩해 SGR 프로토콜로 자식 프로그램에 전달하므로, htop이나 lazygit 같은 TUI가 실제 마우스 입력을 받습니다. 반면 vterm은 마우스 클릭을 Emacs point 이동에 가로채며 터미널로 전달하지 않습니다.
입력 모드. char / semi-char / Emacs / line 모델은 eat에서 시작되었고, ghostel은 같은 설계를 채택해 하나의 기본 키맵에서 다섯 가지 모드를 선택합니다(Input modes 참고). vterm에는 기본 모드(≈ semi-char)와 vterm-copy-mode만 있습니다. 줄 모드 - 진행 중인 줄을 버퍼에 보관해 완전한 Emacs 편집을 하고 RET로 원자적으로 전송하는 기능 - 는 ghostel과 eat에 있지만 vterm에는 없습니다. copy/Emacs 분리도 다릅니다. ghostel과 vterm은 출력을 멈추는 복사 모드가 있어 안정적인 스냅샷 위에서 선택할 수 있고, eat의 읽기 전용 "Emacs 모드"는 실시간으로 유지됩니다. Ghostel은 멈추는 복사 모드와 실시간 읽기 전용 Emacs 모드를 모두 제공합니다.
셸 통합. Ghostel은 bash, zsh, fish에 대한 통합을 자동 주입하므로 RC 변경이 필요 없습니다. vterm과 eat은 둘 다 통합 스크립트를 제공하지만 사용자가 직접 source 해야 합니다. 세 도구 모두 작업 디렉터리를 추적하고(TRAMP를 통해서도) 프롬프트 사이를 이동합니다. ghostel은 OSC 133을, vterm과 eat은 자체 OSC 51 기반 주석을 사용합니다. Ghostel과 vterm은 추가로 셸이 허용 목록의 Elisp를 평가하게 할 수 있지만, eat은 임의 eval 대신 고정된 UI 명령 집합만 노출합니다.
비밀번호 프롬프트. Ghostel은 전경 프로그램이 비밀번호를 읽고 있는지(sudo, ssh, gpg, …) 감지하고 read-passwd로 묻고, 키 입력을 Emacs 키 파이프라인에 태우지 않은 채 PTY로 응답을 보냅니다. 훅을 사용하면 알려진 프롬프트를 auth-source에서 자동으로 채울 수도 있습니다(Password prompt detection 참고). eat에는 수동 eat-send-password가 있지만 프롬프트를 자동 감지하지는 않습니다. vterm에는 그런 가로채기가 전혀 없으므로, 각 문자는 일반 키 입력으로서 view-lossage, 최근 키 링, 입력을 감시하는 다른 모든 곳에 남습니다.
입력기. Ghostel과 vterm은 Emacs 입력기를 터미널로 전달합니다(ghostel은 ghostel-ime-mode를 통해). eat은 그렇지 않습니다.
렌더링. 세 도구 모두 overlay 대신 텍스트 프로퍼티를 사용합니다. Ghostel은 libghostty의 row dirtiness와 커서 추적을 사용해 변경이 필요한 줄만 갱신하며 기본값은 약 30 fps입니다. vterm은 damage-rectangle 콜백을 사용하고 기본값은 약 10 fps입니다. eat은 8-33 ms 지연 창 안에서 출력을 병합합니다.
성능. 에뮬레이터 공통 벤치마크(1 MB 스트리밍, 약 1,000줄 스크롤백, Emacs 32.0.50)에서 ghostel은 plain-ASCII 처리량 약 75 MB/s로 선두이며, 이는 vterm(~18)의 약 4배, eat(~6)의 12배입니다. Emacs 내장 term은 eat과 비슷한 수준(~7)입니다. eat은 순수 Elisp라는 대가를 치릅니다. Ghostel의 클릭 가능한 링크 감지는 URL과 경로가 빽빽한 출력에서 실제 일을 하므로 처리량을 대략 절반으로 줄이지만(그래도 여전히 가장 빠름), 일반 출력에서는 비용이 없고 끌 수도 있습니다. 로컬 셸에서는 ghostel의 네이티브 PTY 경로가 더 빠릅니다. 전체 수치는 Performance를 참고하세요.
상세한 아키텍처 비교는 Architecture 섹션을 참고하세요.
Ghostel은 Zig 네이티브 모듈(터미널 엔진)과 Elisp(Emacs 통합)의 2계층 설계를 가집니다.
ghostel.el Elisp: 키맵, 프로세스 관리, 모드, 명령 src/module.zig 진입점: emacs_module_init, 함수 등록 src/GhostelTerm.zig libghostty-vt를 감싼 터미널 상태(및 네이티브 리더) src/Renderer.zig 터미널 페이지 -> 스타일이 적용된 텍스트가 있는 Emacs 버퍼 src/input.zig ghostty 인코더를 통한 키 인코딩 src/handler.zig OSC 동작을 가로채 Elisp 콜백으로 라우팅 src/PtyProcess.zig 네이티브 PTY: master/replica 열기, 자식 프로세스 생성 src/NativeProcess.zig 백그라운드 리더 스레드 + Emacs로 가는 이벤트 파이프 src/emacs.zig Emacs 모듈 C API를 위한 Zig 래퍼
Elisp 소스는 lisp/에, Zig 소스는 src/에, 벤더링된 업스트림 헤더는 vendor/에, 번들된 컴파일 완료 terminfo는 etc/terminfo/에, 셸 통합 자산은 etc/shell/에 있습니다. ghostel에 의존하는 선택적 MELPA 확장은 extensions/<package>/ 아래에 있습니다(현재는 evil-ghostel).
Ghostel은 같은 Elisp 입력 및 렌더링 API 뒤에 두 가지 PTY 경로를 가집니다.
로컬 버퍼는 기본적으로 네이티브 PTY 경로(ghostel-use-native-pty)를 사용합니다. Zig가 PTY를 열고, 자식 프로세스를 시작하고, 백그라운드 스레드에서 출력을 읽고, 바이트를 libghostty-vt에 직접 공급합니다. Emacs에서 실행해야 하는 터미널 콜백(OSC 핸들러)이나 redraw를 위한 버퍼 무효화가 필요하면, 리더는 Lisp 이벤트 form을 직렬화하여 Emacs 파이프 프로세스에 씁니다. 그 프로세스의 필터(ghostel--events-filter)는 form을 평가하고 redraw를 예약합니다. 따라서 대량 출력은 Emacs 프로세스 필터를 바이트 단위로 실행하지 않고도 터미널 상태를 갱신합니다.
원격 TRAMP 버퍼는 Emacs 프로세스 기계를 사용합니다. TRAMP가 원격 시작을 소유하고, 출력을 ghostel--filter에 전달하며, 이 함수는 ghostel--write-vt를 호출해 동일한 터미널 모델에 Emacs에서 동기적으로 공급합니다. 이렇게 하면 TRAMP 파일 핸들러 동작을 유지하면서 렌더러와 입력 코드를 네이티브 경로와 공유할 수 있습니다.
입력은 ghostel--write-pty를 통해 즉시 기록됩니다. 네이티브 경로는 네이티브 PTY master에 쓰고, Emacs 경로는 버퍼 로컬 프로세스로 위임합니다.
주요 데이터 흐름(네이티브 경로 기준, TRAMP 경로는 2단계가 VT 파서에 도달하는 방식만 다름):
ghostel--encode-key → libghostty 인코더 → ghostel--write-pty → PTY.ghostel--events-filter → ghostel--invalidate. (TRAMP 경로에서는 대신 바이트가 ghostel--filter에 도착하고, 이 함수가 ghostel--write-vt를 호출한 뒤 ghostel--invalidate를 동기적으로 호출합니다.)ghostel--invalidate는 ghostel--redraw-now를 실행합니다. 상호작용 echo의 경우 즉시, 그렇지 않으면 병합 타이머에서 실행되며, 여기서 Renderer.zig가 터미널 페이지를 스캔해 더러운 줄을 스타일이 적용된 텍스트로 갱신합니다.Ghostel은 터미널 그리드를 Emacs 버퍼에 렌더링합니다. 렌더링은 일반적인 append-only 텍스트 편집 워크플로가 아닙니다. 줄이 교체될 수 있고, 버퍼가 다시 만들어질 수 있으며, 크기 조절은 터미널 내용을 재흐름시킬 수 있고, 스크롤백은 물질화되거나 퇴출될 수 있습니다.
그럼에도 Elisp 코드는 ghostel 버퍼를 일반 Emacs 버퍼처럼 다룰 수 있어야 합니다. point, mark, 창 상태 등을 어딘가에 두었다면, 렌더러가 버퍼를 갱신한 뒤에도 그 위치들은 여전히 의미가 있어야 합니다.
따라서 렌더러는 자신이 수행하는 버퍼 변경 전반에 걸쳐 관련 버퍼 위치를 보존할 책임을 집니다. 어떤 위치를 정확히 저장하고, 그것들을 어떻게 변환하는지는 구현 세부 사항입니다. 구체적인 내용에 대한 진실의 원천은 코드와 테스트입니다.
아키텍처 경계는, 렌더러가 렌더링 연산으로 인해 발생하는 위치의 기계적 변환을 소유한다는 점입니다. 덕분에 Elisp 코드는 단순해집니다. Elisp는 렌더러의 변경으로부터 방어하기 위해 모든 redraw를 임시 marker 캡처/복원 로직으로 감쌀 필요가 없어야 합니다.
여기서 따라오는 일반적인 설계 원칙이 있습니다. 렌더러가 소유하는 의미를 Elisp의 around-redraw 해킹으로 패치하지 마세요.
Elisp는 정책과 사용자 의도를 결정해야 합니다. 예를 들어:
렌더러는 렌더링의 결과를 처리해야 합니다. 예를 들어:
Elisp가 렌더러 변경을 사후 보정하려 하면, 대개 휴리스틱적이고 취약해집니다. window-start가 사용자의 스크롤 때문에 바뀐 것인지, Emacs redisplay가 clamp한 것인지, 리사이즈가 있었는지, 렌더러가 내용을 다시 쓴 것인지 추측해야 하기 때문입니다. 렌더러는 정확한 편집 경계와 터미널 상태를 알고 있으므로, 그런 로직은 거기에 속합니다.
요컨대 Elisp는 자신이 point를 둔 곳에 point가 그대로 있다고 기대하면서 일반 Emacs 코드를 작성할 수 있어야 합니다. 렌더러는 터미널 그리드가 그 아래에서 다시 쓰이는 동안에도 이것이 참이 되도록 만드는 호환성 계층입니다.
버그 보고, 기능 요청, 풀 리퀘스트는 GitHub repository에서 환영합니다. 렌더링 문제나 상호작용 동작 문제를 보고할 때는 재현 방법(정확한 명령이나 이스케이프 시퀀스, Emacs 버전, 플랫폼)을 함께 주시면 큰 도움이 됩니다. M-x ghostel-debug-info는 첨부하기 좋은 진단 이벤트를 수집합니다.
Ghostel은 자유 소프트웨어이며, GNU General Public License 버전 3 이상(GPL-3.0-or-later)으로 배포됩니다. 전체 텍스트는 LICENSE 파일을 참고하세요.