큰 텍스트 파일과 한글 소설을 편하게 읽기 위한 데스크톱 리더. VS Code 스타일의 파일 탐색 사이드바와 텍스트 뷰어를 갖췄으며, EUC-KR / CP949 등 한국어 레거시 인코딩과 수십 MB급 대용량 파일을 매끄럽게 다루는 데 초점을 맞췄습니다.
Tauri v2 + React 19 + TypeScript 로 만들어졌고, OS 내장 WebView를 사용해 앱 용량이 작고 가볍습니다.
- 📁 트리 탐색기 — 폴더를 펼칠 때만 하위를 읽는 지연 로딩. 대형 프로젝트 폴더도 멈춤 없이 열림
- 🔎 파일명 검색 — 열린 폴더 전체를 재귀 탐색(펼치지 않은 폴더 포함). 결과를 평면 목록으로 표시
- ↕ 정렬 — 이름/크기 기준, 오름/내림 토글 (폴더는 항상 위)
- 📏 파일 크기 표시 — 각 파일 옆에 크기 표기
- 🔄 새로고침 — 펼침 상태를 유지한 채 트리 갱신
- 🕘 최근 폴더 — 열었던 폴더를 기억해 드롭다운에서 바로 다시 열기
- ⏮ 마지막 폴더 자동 열기 — 시작 시 마지막에 보던 폴더를 복원(없으면
~/Documents)
- ⚡ 가상화(virtualization) — 화면에 보이는 줄만 렌더링. 최대 50MB 파일까지 부드럽게 스크롤
- 🌏 인코딩 자동 감지 — UTF-8 / UTF-8(BOM) / UTF-16 LE·BE / EUC-KR / CP949
- 일부가 손상·혼합된 한글 파일도 열어 읽을 수 있도록 관대하게 디코딩(깨진 바이트는
�처리)
- 일부가 손상·혼합된 한글 파일도 열어 읽을 수 있도록 관대하게 디코딩(깨진 바이트는
- 📖 문단 모드 — 빈 줄을 문단 간격으로 정리해 소설을 책처럼 표시
- ↩ 자동 줄바꿈 — 지정한 글자 수에서 줄바꿈 (열 수 조절 가능)
- 🔢 줄번호 표시/숨김 — 줄번호는 복사에 포함되지 않음
- 🔠 글씨 크기 조절 —
Cmd/Ctrl++/-/0 - ⬆⏸⬇ 자동 스크롤 — 위/아래/정지 + 속도 조절 (핸즈프리 읽기)
- 📊 상태 표시줄 — 줄 수 · 파일 크기 · 커서 위치 · 인코딩
- ★ 즐겨찾기 — 자주 읽는 파일을 사이드바 목록에 고정
- 🔖 북마크 — 줄 번호를 클릭해 특정 줄에 표시, 목록에서 점프
- 📌 이어읽기 — 파일별 마지막 읽던 위치를 자동 저장·복원
- 🗂 사이드바 탭 — 파일 / 즐겨찾기 / 북마크 전환
모든 설정과 즐겨찾기·북마크·읽기 위치는 로컬에 저장되어 앱을 다시 켜도 유지됩니다. 저장 위치는 WebView 캐시가 아니라 OS의 앱 데이터 폴더(
state.json)라, 앱을 다시 설치하거나 캐시를 정리해도 데이터가 남습니다. 원본 파일은 절대 수정하지 않습니다 — 모든 표시 옵션은 읽기 전용 변환입니다.
| 영역 | 기술 |
|---|---|
| 셸 / 백엔드 | Tauri v2 (Rust) |
| 프론트엔드 | React 19, TypeScript, Vite 7 |
| 가상 스크롤 | @tanstack/react-virtual v3 |
| 인코딩 | encoding_rs (EUC-KR/CP949) |
| 다이얼로그 | tauri-plugin-dialog |
npm install
npm run tauri dev프론트엔드(Vite)와 Rust 백엔드가 함께 뜨며, 소스를 수정하면 자동으로 리로드/재컴파일됩니다.
npm run tauri buildsrc-tauri/target/release/bundle/ 에 Text File Reader.app(및 .dmg)가 생성됩니다.
file-reader/
├── src/ # React 프론트엔드
│ ├── App.tsx # 레이아웃 · 상태 오케스트레이션
│ ├── api.ts # Rust invoke 래퍼 + 폴더 다이얼로그
│ ├── types.ts # DirEntry / FileContent (백엔드와 형태 일치)
│ ├── format.ts # 크기 포맷 등 공용 유틸
│ ├── store.ts # 영속 저장소 (app_data_dir/state.json, 동기 캐시 + 디바운스 저장)
│ ├── readingPosition.ts # 이어읽기 위치(store 직접)
│ ├── use*.ts # 설정 훅 (글씨/줄바꿈/문단/줄번호/즐겨찾기/북마크/최근폴더)
│ └── components/
│ ├── FileTree.tsx # 재귀 트리 + 정렬 + 크기 표시
│ ├── FileViewer.tsx # 가상화 뷰어 + 뷰 옵션 + 자동 스크롤
│ └── StatusBar.tsx # 하단 상태 표시줄
└── src-tauri/ # Rust 백엔드
├── src/lib.rs # Tauri 명령 (아래 참조)
└── tauri.conf.json # 앱 이름 · 창 · 번들 설정
| 명령 | 역할 |
|---|---|
read_directory(path) |
디렉터리 나열 (폴더 우선, 이름·크기 포함) |
read_file_text(path) |
파일 내용 + 감지된 인코딩 + 크기 반환 (50MB 상한, 바이너리 거부) |
resolve_start_folder(preferred) |
시작 폴더 결정 (마지막 폴더 → ~/Documents → 홈) |
search_files(root, query) |
파일명 재귀 검색 (심링크·깊이·개수 안전장치) |
load_state() / save_state(contents) |
앱 상태(설정·즐겨찾기·읽기 위치 등) JSON 영속화. app_data_dir/state.json 에 임시 파일+rename 으로 원자적 저장하고 직전 버전을 .bak 백업 |
파일시스템 접근은 전부 Rust 쪽 std::fs로만 이뤄지고, 프론트엔드는 invoke()로만 요청합니다 — Tauri의 권한 모델(capabilities)로 보안 경계가 강제됩니다.
| 동작 | 키 |
|---|---|
| 글씨 크게 | Cmd/Ctrl + = (또는 +) |
| 글씨 작게 | Cmd/Ctrl + - |
| 글씨 크기 초기화 | Cmd/Ctrl + 0 |
- 북마크 추가/제거: 뷰어의 줄 번호 클릭
- 즐겨찾기 토글: 뷰어 상단의 ★ 버튼
- 자동 스크롤: 뷰어 우하단 플로팅 컨트롤 (▲ / ⏸ / ▼,
−/+로 속도)
- 가상화 + 동적 측정: 줄바꿈·한글(넓은 글자)로 행 높이가 가변이라,
measureElement로 실제 높이를 측정해 스크롤 정렬을 맞춥니다. 덕분에 파일 크기와 무관하게 DOM 노드 수가 일정합니다. - 인코딩 판별: BOM → UTF-8 → EUC-KR/CP949 순으로 시도합니다. 바이너리는 NUL 바이트 유무로 걸러내고(깨짐 비율은 CP949 코드공간이 넓어 부적합), 그 외에는 손상이 있어도 읽을 수 있게 디코딩합니다.
- 줄번호 복사 제외: 줄번호를
::before생성 콘텐츠(content: attr(...))로 그려 선택/복사 대상에서 원천 제외합니다. - 견고한 상태 저장: 설정·즐겨찾기·이어읽기 위치를
localStorage(WebView 캐시라 재설치·정리 시 소실 위험) 대신 OS 앱 데이터 폴더의state.json에 저장합니다. 쓰기는 임시 파일에 기록 후rename으로 교체해 원자적이라 쓰기 도중 전원이 꺼져도 기존 파일이 손상되지 않고, 직전 버전을.bak로 백업합니다. 렌더 전에 파일을 한 번 읽어 동기 메모리 캐시를 채우므로 훅은 기존처럼 값을 동기로 읽고(깜빡임 없음), 실제 디스크 쓰기는 뒤에서 디바운스됩니다. 기존localStorage데이터는 첫 실행 시 자동 이관됩니다.
개인 프로젝트입니다.
