feat(kebab-tui): p9-fb-09 external editor return — terminal restore #68
Reference in New Issue
Block a user
Delete Branch "feat/p9-fb-09-editor"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
요약
Search
g(citation jump) 후 TUI 화면이 깨지는 도그푸딩 item 7 버그 수정. external editor suspend / restore 의 정확한 시퀀스를 helper 로 묶고, post-resumeterminal.clear()누락을 채움.변경
kebab-tui::editor::with_external_program(new,pub(crate)) — RAII guard 로 atomic 한 suspend → spawn → restore 시퀀스. panic 시에도 raw mode + alt screen 복구.App.pending_editor: Option<EditorRequest>— 키 핸들러는 enqueue 만, run loop 가TuiTerminal핸들 들고 spawn.App.force_redraw: bool— editor 종료 후 다음 draw 가terminal.clear()우선 실행, 잔상 제거. 향후 다른 use case (config reload 등) 와도 공유.jump_to_citation시그니처:(&Citation, &str, &Path)→(&mut TuiTerminal, &Citation, &str, &Path).pub→pub(crate). 기존 raw-mode 토글 + RAII guard 코드 제거하고 helper 호출로 단일화.테스트
unspawnable_program_surfaces_program_name_in_error,g_key_enqueues_pending_editor_request,g_key_with_no_hits_does_not_enqueuecargo clippy -p kebab-tui --all-targets -- -D warningsclean문서
kebab tui행에 Searchg동작 + 자동 redraw 안내planned→in_progress후속
pending_editorqueue +with_external_programhelper 가 향후 p9-fb-20 (TUI Ask citation jump), p9-fb-09 의 추가 keybinding (o등) 의 공통 진입점이 됨.Search `g` 키 (citation jump) 후 TUI 화면이 깨지는 버그 수정. 도그푸딩 item 7 — `g` 로 vim 띄우고 `:q` 후 복귀하면 이전 frame 의 잔상이 새 draw 위에 겹쳐 보였음. ## 핵심 변경 - **`kebab-tui::editor::with_external_program(&mut TuiTerminal, Command)`** helper 추가. suspend / spawn / restore 시퀀스를 RAII guard 로 atomic 하게 묶어 panic 발생해도 raw mode + alt screen 복구 보장: 1. LeaveAlternateScreen + Show cursor + disable_raw_mode 2. Command::status() 로 child 실행 3. enable_raw_mode + EnterAlternateScreen + Hide cursor + `terminal.clear()` ← 이 한 줄이 핵심 fix - **`App.pending_editor: Option<EditorRequest>`** 추가. 키 핸들러 (현재 `kebab-tui::search::handle_key_search` 의 `g`) 가 직접 spawn 하는 대신 EditorRequest 를 enqueue, 실제 spawn 은 run loop 가 `TuiTerminal` 핸들 in scope 일 때 처리. - **`App.force_redraw: bool`** ratchet. with_external_program 종료 후 set, run loop draw 직전 check → terminal.clear() 후 reset. editor 외 다른 향후 use case (config reload, theme change 등) 도 같은 hook 사용 가능. ## 가시성 정리 `with_external_program` / `jump_to_citation` 은 `pub(crate)` 로 좁혀짐 — `TuiTerminal` 자체가 module-private (raw mode + alt screen 의 안전 한 lifecycle 은 `Drop` 만 보장) 이므로 외부 caller 는 `App.pending_ editor` enqueue 패턴으로만 spawn 요청 가능. 외부 surface (`build_jump_ command`, `handle_key_search`, `render_search`) 는 그대로. ## 테스트 - `unspawnable_program_surfaces_program_name_in_error` — helper 의 spawn 실패 경로 (ENOENT) error context 검증 - `g_key_enqueues_pending_editor_request` — `g` on hit → EditorRequest enqueue, citation 정보 보존 - `g_key_with_no_hits_does_not_enqueue` — empty hits → no-op - 기존 17 개 search 테스트 + 14 lib + 18 ask + 12 inspect + 10 library 모두 통과 - `cargo clippy -p kebab-tui --all-targets -- -D warnings` clean ## 문서 - README: `kebab tui` 행에 Search `g` 동작 + 자동 redraw 안내 - HANDOFF: 2026-05-03 머지 후 발견 entry - spec status: `planned` → `in_progress` 후속 task (p9-fb-20 의 citation jump in TUI Ask 등) 가 같은 `pending_editor` queue + `with_external_program` helper 위에 build. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>회차 1 — fix 자체는 견고합니다. RAII guard atomic, helper 가 module 밖으로 안 새고, run loop 가 deferred queue 패턴으로 terminal handle 깔끔히 격리. 테스트 3 건도 enqueue / no-op / spawn fail path 골고루.
actionable nit 3 건 inline. 모두 cosmetic / encapsulation 정리 — 동작 변경 없음.
pending_editor와force_redraw둘 다pub인데, enqueue 는 같은 crate 안의 키 핸들러만 (e.g.search.rs) 하고 외부 caller (kebab-cli/desktop) 는App.pending_editor = Some(...)같은 mutation 을 할 일이 없습니다. encapsulation 관점에서pub(crate)가 안전한 default 입니다.예외: 향후 외부 (예: integration test) 에서 enqueue 검증이 필요하면 그 시점에
pub으로 풀면 됩니다. 지금pub로 두면 외부에서 잘못 mutate 했을 때 invariants ("run loop 가 매 tick 마다 take()") 가 어긋날 수 있습니다.@@ -261,1 +261,4 @@pub(crate) should_quit: bool,/// p9-fb-09: deferred external-program request. A pane's key/// handler enqueues an `EditorRequest` here when the user wants/// to spawn `$EDITOR` (e.g. Search `g` jumps to a citation indoc 의 "ratchet incremented every time..." 표현이 type 과 어긋납니다 —
force_redraw는boolflag 라서 "increment" 가 아니라 "set" / "toggle" 이 정확합니다. 제안:실제 동작에 맞고 "counter 라서 monotonic 일 거다" 같은 오해도 막아줍니다.
@@ -0,0 +134,4 @@),"unexpected error kind: {err:?}",);}이 테스트는 이름과 달리
with_external_program자체를 호출하지 않고Command::status()만 직접 호출합니다 — helper 의with_context(...)wrapper 가 program name 을 넣어주는지 검증하지 못 합니다. 둘 중 하나로 정리하면 좋겠습니다:command_status_returns_not_found_for_missing_program정도로 바꿔서 "OS 동작 sanity check" 로 명시. helper 내부 동작은 dogfooding 으로 검증한다는 doc comment 추가.TuiTerminal을 mock 하기 어려우면cfg_attr(test, allow(...))로 internal_status_onlyshim 을 노출해 그것을 테스트. (오버엔지니어링 가능성 있음)저는 (1) 이 가성비 좋다고 봅니다 — 기존 test 코드는 그대로 두고 이름 + doc 만 손보는 정도.
회차 2 — nit 3 건 모두 깔끔히 반영.
추가 지적 없음. 머지 OK.