- 기존 Howl 에디터의 한계(개발 중단, 느린 검색, SSH 비호환, 터미널 미지원)로 인해 직접 새로운 TUI 텍스트 에디터를 개발함
- Helix, VS Code, Vim, Neovim, Emacs 등 13개 에디터를 시도했으나 원하는 조작 감각(Fingerspitzengefühl) 을 충족하는 에디터가 없었음
- 초기에는 개인 맞춤형 최소 기능만 구현하고, 성능·유니코드·다국어 지원 등은 후순위로 두며 점진적으로 확장함
- 개발 과정에서 자체 정규식 엔진, 파일 브라우저, TUI 기반 렌더링, 터미널 버퍼 통합 등을 직접 구현함
- 프로젝트 전역 검색, 구문 강조, 캐시 처리, 멀티스레드 작업 분배 등에서 성능 최적화 기법을 다수 적용함
- 결과적으로 자신만의 워크플로에 완벽히 맞는 도구를 완성하며, 생산성과 프로그래밍에 대한 즐거움을 되찾았다고 밝힘
기존 에디터의 한계와 대안 탐색
- 약 10년간 사용한 Howl 에디터의 문제점이 직접 개발하게된 계기
- 수년간 개발이 중단되어 직접 포크를 유지해왔으나, MoonScript로 작성되어 깊이 있는 수정이 어려웠음
- 프로젝트 전체 파일 검색 성능이 부족해 작업 흐름이 끊기는 문제 발생
-
GUI 에디터여서 SSH 연결을 통한 원격 사용이 불가
-
통합 터미널이 없어 외부 명령 실행 시 라이브 상호작용 불가, ANSI 이스케이프 코드 대부분 미지원
- Helix, VS Code, Sublime Text, Vim, Zed, Neovim, Emacs, Geany, Micro, Lite XL, Lapce, GNOME Builder, Kakoune 등 13개 에디터를 시도
- 각각 장점이 있었으나, 원하는 조작 감각(Fingerspitzengefühl) 을 충족하지 못함
- Helix를 가장 오래 사용했으나 한 달 후 흥미를 잃음
초기 개발 전략
- 시작 단계에서 범위를 최소한으로 제한
- 자신 외의 사용자를 위한 기능 배제, 모든 설정을 하드코딩
- 성능 최적화는 미루고 String 기반 버퍼로 시작
- 유니코드 그래핌 완전 지원 배제, £ 기호가 단일 컬럼을 차지하면 충분하다는 판단
-
구문 강조는 주로 사용하는 소수 언어만 지원, 나머지는 범용 구분자 기반 강조로 대체
- 두 번째 시도에서 간단한 TUI 프레임워크를 먼저 구축했으나, 시간이 지나며 대부분을 걷어내고 더 직접적이고 세밀한 접근으로 전환
Dogfooding 실천
- 에디터가 단일 파일을 열고 편집·저장할 수 있는 최소 기능 임계점에 도달한 후 세 가지 실천을 시작
-
nano 대신 자체 에디터를 사용하여 시스템 파일 편집이나 메모 작성 시 강제로 사용
- 빠진 기능, 버그, 이상 동작, 한계를 발견할 때마다 프로젝트 README.md에 기록
- 짜증이 날 정도의 문제는 즉시 수정
- 이 세 가지 실천으로 작업량이 월 1시간에서 주 수 시간으로 증가
- 전체 약 10,000줄의 코드 중 거의 대부분이 최근 6개월 내에 작성됨
커서 조작
- 커서 조작은 구현 난이도가 높은 영역
-
ctrl + shift + left 같은 키 조합의 동작은 사용자에게 당연하지만 로직 구현은 복잡
- 고수준 입력을 원시 연산의 조합으로 구현하는 것이 핵심 조언
- 예: 단어 단위 백스페이스 → 단어 단위 커서 이동 + 범위 선택 + 삭제로 분해
- undo/redo 구현 시 이 3개 동작을 하나의 그룹으로 묶어야 직관적 결과 보장
- 모달 에디터가 이런 원시 연산을 사용자에게 직접 노출하는 이유가 이해됨
파일 브라우저
- Howl의 파일 브라우저가 다른 에디터로 넘어가지 못한 결정적 이유
-
즉시 업데이트되는 퍼지 필터가 탁월하여, 1~2 키 입력만으로 원하는 파일 검색이 대부분 가능
- 파일이 존재하지 않으면 인라인으로 생성 가능
-
~/ 입력 시 홈 디렉토리로 자동 전환
- 메인 편집 창에서 열려는 파일의 미리보기 표시
- 다른 에디터들이 파일 열기 문제를 마우스 의존, GTK 기본 대화상자, 파일명 추측 등으로 해결하는 것에 대한 불만
- 자체 구현 시 Levenshtein distance 같은 복잡한 방식 대신 단순 기준 3가지로 충분
- 필터 구문으로 시작하는지 여부
- 필터 구문을 포함하는지 여부
- 가장 최근 수정/접근 시간
- 대소문자 무시 매칭을 허용하되, 대소문자 일치 시 순위를 약간 상향
- 수만 개 파일이 있는 프로젝트에서도 2 키 입력 후 약 95% 확률로 상위 2개 안에 원하는 파일 위치
정규식 엔진
- 정규식은 프로젝트 전체 검색, 구문 강조, 버퍼 내 찾기 세 곳에서 사용
- 기존 regex-automata 크레이트 대신 직접 구현한 이유
- Rust의 raw string 문법 같은 컨텍스트 민감 엣지 케이스 처리 필요
- 프로젝트 자체가 자체 스택 구축과 이해를 위한 연습
- 초기 구현은 파싱 크레이트 chumsky로 정규식 구문을 파싱하고 AST를 매 문자마다 순회하는 느린 방식
- 이후 단계적 최적화 수행
-
단일 패스 옵티마이저: 반복 출현하는 문자 매칭 그룹을 단일 String 노드로 변환하여 정확한 문자열 검색 수행
-
공통 접두사 추출: 예를 들어 hel[(lo)p]에서 공통 접두사 hel을 발견하여 해당 위치에서만 매칭 수행 → 프로젝트 전체 검색에서 큰 성능 향상
- AST 워커를 Rust 동적 호출 기반의 threaded code VM으로 재구현
- threaded code VM을 CPS(Continuation-Passing Style) 형태로 변환, 각 VM 명령이 후속 명령을 tail-call하여 컴파일러 최적화 활용
- Rust의 느린 동적 함수 호출을 vtable 룩업 없이 래핑, 많은 정규식 명령의 코드젠이 몇 개의 기계어 명령으로 축소
- 가능한 한 많은 정규식 명령을 유니코드 코드포인트 대신 바이트 단위로 구현, UTF-8 설계 덕분에 ASCII 최적화 기법이 멀티바이트 코드포인트에서도 유효
- jump LUT 체인으로의 컴파일도 시도했으나, 벤치마크 결과 threaded code 대비 20~30% 빠른 정도에 불과하고 유연성이 크게 저하되어 채택하지 않음
- 최종 결과: Rust용 가장 복잡한 구문 강조에서 50,000줄짜리 자동 생성 바인딩 파일을 클린 상태에서 10밀리초 미만에 전체 강조 완료
구문 강조 캐시
- 초기에는 매 변경마다 전체 파일을 재강조하는 방식이었으나, 큰 파일에서 성능 저하 발생
-
온디맨드 토큰 강조 캐시 구현
- 대략 동일한 크기의 청크 단위로 토큰을 강조
- 버퍼에 변경(damage)이 발생하면 해당 위치와 겹치거나 이후의 청크만 무효화
- 가장 비관적인 경우(대형 파일 중간 편집)에도 damage 이전의 강조 상태는 유지되고, 화면 아래쪽 이후는 강조 정보가 요청되지 않아 처리 불필요
-
수요 기반(demand-driven) 접근이므로, 동일 버퍼의 서로 다른 부분을 보는 복수 패널에서도 정상 동작
프로젝트 전체 검색
- 검색 프로세스 4단계
- 현재 디렉토리에서 역방향으로 .git/ 디렉토리를 찾아 프로젝트 루트 결정
- 프로젝트 루트의 모든 디렉토리를 재귀 순회하며 검색 패턴을 파일 내용에 매칭
- 각 양성 매칭에서 파일 스니펫을 추출하고 결과 미리보기를 위해 구문 강조 적용
- 현재 경로로부터의 탐색 거리에 따라 결과 순위 결정 (가까운 파일일수록 높은 순위)
- 빌드 디렉토리 등을 피하는 기본 필터링 규칙 적용
-
멀티스레드 처리로, 기본적인 work-stealing 방식으로 스레드 간 작업 할당
- 모든 스레드가 소비자이자 생산자인 특수한 구조에서 종료 감지 문제 해결
- 대기 중인 스레드가 원자적 카운터를 증가시키고, 카운터가 워커 수에 도달하고 작업 큐가 비면 전체 종료
- 정규식 최적화와 현대 SSD 속도 덕분에 Veloren 같은 큰 코드베이스에서도 단순 패턴 검색이 거의 즉시 완료
- 플레임그래프 상 대부분 IO 바운드 상태
- 에디터 내에서 대형 코드베이스를 사고 속도로 검색할 수 있는 것이 생산성에 큰 기여
터미널 에뮬레이터 버퍼
- 패널 기반 에디터에서 하나의 패널을 터미널 창으로 사용하는 기능의 편의성이 큼
- ANSI 파서를 직접 구현하려 했으나, OSC52, Kitty 키보드 확장 등 최신 터미널 렌더링 기능 지원이 방대
-
alacritty_terminal 크레이트를 활용하여 Alacritty 터미널 에뮬레이터의 이스케이프 시퀀스 파서 및 터미널 상태 관리 로직을 재사용
- 결과적으로 screen/tmux의 핵심 기능을 대체할 수 있으며, 더 풍부한 이스케이프 시퀀스 지원 제공
렌더링 최적화
- TUI 기반이지만 원격 모바일 연결 시 대역폭이 여전히 중요
-
더블 버퍼링: 터미널 화면의 내부 복사본을 이중으로 유지
- 재그리기 시 이전 프레임과 비교하여 변경된 셀만 ANSI 이스케이프 시퀀스 출력
- 커서 이동, 스타일 모드 변경 등의 시퀀스도 실제 필요할 때만 출력
- 대부분의 터미널 에뮬레이터(Ghostty 제외)에서, 에디터의 터미널 패널에서 대형 파일을 cat한 후 에디터를 닫는 것이 호스트 터미널에서 직접 cat하는 것보다 더 빠름
-
alacritty_terminal이 호스트 터미널에 대한 stdout 바이트 처리 비용을 차단해주기 때문
결론: 자신만의 도구를 만들 것
- 직접 만든 에디터는 내 작업 흐름에 완벽히 맞는 도구로 자리 잡았음
- 자체 에디터/도구 제작이 무의미한 고통이라는 통념에 반대함
- 네 가지 이점
-
완벽한 맞춤: 원하는 동작만 수행, 그 이상도 이하도 없음
-
다양한 기술 학습: 정규식, ANSI, 의사터미널(pty), TUI 디자인, UTF-8의 세부 사항 등 범용적으로 유용한 기술에 대한 깊은 이해 획득
-
장기적 생산성 향상: 자신의 도구를 완벽히 이해하고 개인 워크플로에 맞춘 기능을 내장하여, 도구와의 마찰 감소
-
순수한 즐거움: 자기완결적인 문제들을 해결하고 그 결과를 손끝으로 느끼는 경험이 프로그래밍에 대한 사랑을 다시 불러일으킴, 수년 만에 코딩하며 활짝 웃고 혼자 웃음이 나는 경험
- 텍스트 에디터가 아니어도 좋으니 자신만의 도구를 만들 것을 권장하며, 어려운 부분을 통계 상자(AI 등)에 넘기지 말고 도전 자체를 즐길 것을 강조