feat: browse repo-dialog paths with a directory tree - #4
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records why the dialog completes paths natively instead of hosting a real shell (Windows has no readline equivalent, so a native completer is needed either way) and why the in-repo tree navigator cannot be reused for a filesystem picker. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tab was inert in the dialog: text_input_char rejects it, so the one field where a path is typed with no view of the filesystem had no way to look. One read_dir per press against the directory the buffer names, directories only. Extending and listing are independent: an extension that still narrows the prefix skips the list, but an empty fragment means "what is in here?" and lists even while extending. Exact-case matching runs first and only falls back to ignoring case, so Linux gains no surprise matches while macOS and Windows stop needing the on-disk casing. The typed text is never rewritten — a leading ~ is expanded to read the directory, not to replace what the user typed with an absolute path. Candidates are stored but not yet drawn; the notice row follows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The candidates the completer produced had nowhere to go. They take the notice row, which already exists on both screens, rather than a floating popup that would be this project's first — and would need its own hit-test regions with the mouse captured. Priority on that row is notice, then candidates, then the repo header: a notice explains a rejected action and any edit clears it, so the two never stay stale together. A list too wide for the row reports the tail as `+N more` instead of dropping it silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the key, the extend-or-list rule, what is offered (directories only, dotted ones on request, case corrected), and the boundary that trips people up: the field takes ~ / .. / relative paths but is not a shell. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Notes the two constraints that are easy to violate later: the completer must not reuse git::tree::read_children (it requires a Repository and forbids paths outside the worktree, which is exactly what the picker walks), and it must not write an expanded ~ back into the buffer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tab reaching the dialog at all was the original defect — text_input_char rejects it, so the key was swallowed. dispatch_key had no test covering that route, only the completion rules underneath it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
split_dir and read_dir_names are what the filesystem browser needs too, so they move out of complete_dir_path's body. Pure refactor: an unreadable directory and an empty one already produced the same completion result, so collapsing both to an empty listing changes nothing, and read_dir_names sorting makes the later sort of the filtered matches redundant. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Enter was encoded as a bare CR whatever modifiers were held, so a pane program could not tell "insert a newline" from "submit". Alt+Enter now carries the Meta prefix the same way Alt+Char does, which is the sequence Claude Code and other pane TUIs read as newline. Shift+Enter still cannot work: without the kitty keyboard protocol the outer terminal never reports the modifier, so nightcrow has nothing to encode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stage one shipped, so the status header no longer claims it is in progress. Stage two's three open questions are resolved: Enter fills the text field rather than opening the repo, Ctrl+T opens the tree, and deriving the root from the buffer removes the need to persist a last-browsed location at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Panel titles show `<L> 1` / `<L> 2`, not `^F1`: written without the space it reads as Ctrl+F1, which is a different key the app passes through to the PTY instead of intercepting. Also notes that the project tab row's F1…F10 legend is a separate axis.
DiffLine now carries libgit2's old_lineno/new_lineno instead of discarding them. Deriving the numbers from the hunk header at render time would have put per-kind counters in the rendering layer, which is the wrong place for that state. Added lines have no old number and removed lines have no new one, so the gutter leaves that column blank rather than inventing one. The gutter is a separate Paragraph from the body, and that is load-bearing: horizontal scrolling is Paragraph::scroll, which shifts a whole line, so a gutter sharing the body's paragraph slides off the left edge. The file view already had a gutter with exactly that bug — scrolling right lost its numbers. Both vectors are filled in one loop so the two paragraphs cannot disagree about which rows they are showing. MIN_SPLIT_WIDTH goes 80 -> 90: each half now spends 5 columns on its gutter, and leaving the threshold alone would have quietly narrowed the code each half can show rather than falling back to unified. Title assembly moves to title.rs, which drops a copy that had been duplicated between the unified and split renderers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There were already three displays — unified, split, file — but v and s each toggle one of them against the unified default, so the third is undiscoverable unless you already know it is there. Tab walks all three; v and s stay for jumping straight to a known view. The file step is skipped when can_open_file_view is false rather than being a dead press, matching the gate that already makes v a no-op, and the whole cycle is inert in tree view where the right pane is always the file preview. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`w` wraps long lines instead of making you scroll sideways for the tail. Wrapping and horizontal scrolling are mutually exclusive by construction, not by choice: ratatui's Paragraph ignores scroll.x once wrapping is on. Enabling wrap therefore resets the offset, or turning it back off would resurrect a stale one. While wrapping, the line-number gutter folds into the body line. A wrapped body line spans several screen rows while its gutter line spans one, so a separate gutter paragraph would desynchronise every row below it — and the reason the gutter was separated in the first place (horizontal scroll) does not exist in this mode. A continuation row therefore carries no number. The split view ignores wrapping outright: halves folding to different heights would stop lining up, which is the only reason that layout exists. Vertical scroll stays in logical lines, so wrapping can leave the bottom of the pane short of a full screen. Nothing becomes unreachable, and search matches keep indexing logical rows. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`w` is handled for the whole diff focus, but the legend only offered it in the unified diff arms — so the one view where a long unwrapped line sends you looking for the key was the view that never named it. The split view stays out on purpose: it ignores wrapping, and a hint for a no-op key would lie. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Typing a path only helps when you know it. `↓` in the repo dialog opens a directory browser rooted at whatever the field names, and `Enter` takes the selection back into the field rather than opening it — the field's own Enter stays the single place a repo is opened, so a picked path can still be extended with Tab or corrected by hand. `↓` rather than a Ctrl chord: every other key in the dialog is bare, the field's horizontal keys already mean "edit this path", and the browser's own cursor moves on the vertical axis. A second Tab, once the candidate list is up, escalates to the same browser — that press used to redraw the same list, and it is exactly the moment the flat list proved too little. State is a flat row list: expanding splices children in after their parent and collapsing drains the deeper rows below it, so the selection is a plain index into what is on screen. Paths keep the user's own notation, with one exception — `←` past a `~` root has no expressible parent, so it falls back to the absolute path, checked against the real parent rather than trusted from text surgery. The dialog replaces the hint legend entirely, so the input line now spells its own keys out; without that neither `↓` nor Tab is findable anywhere on screen. The legend is dropped whole when the path leaves no room, since the caret has to stay visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Marks the picker plan implemented and keeps the three places the implementation diverged from it: the entry key (`↓`, not `Ctrl+T`), the hint legend the plan never accounted for, and the flat row list in place of a `BTreeSet` plus children cache. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
<prefix> o다이얼로그에서 경로를 손으로 치는 것 외에 디렉터리 탐색이 가능해집니다. 필드에서↓를 누르면 필드가 가리키는 디렉터리를 루트로 브라우저가 열립니다.docs/repo-picker-plan.md의 2단계이고, 계획과 갈린 세 지점은 그 문서에 기록했습니다.설계 판단
↓(계획의Ctrl+T에서 변경).T니모닉이<prefix> t(새 터미널)와 겹쳐 "충돌하지 않는다"를 설명해야 했고, 다이얼로그의 다른 키가 전부 bare인데 Ctrl 화음만 튑니다. 필드의 수평 키(→/End= prefill 수락)가 이미 "이 경로를 편집한다"는 뜻이라 수직 축이 비어 있었고, 브라우저 안에서↓/j가 커서를 옮기므로 진입 키와 진입 후 조작이 같은 축에 놓입니다.Tab도 같은 브라우저로 승격. 그 상태의 Tab은 같은 목록을 다시 그리는 죽은 키였고, 평면 목록이 실패한 지점이 정확히 거기입니다.Enter는 확정이 아니라 필드 복귀 + 경로 주입. repo를 여는 지점은 필드의Enter한 곳뿐입니다. 그래서 브라우저에서는 확장이→전용입니다(트리 뷰는Enter도 확장).BTreeSet+ children 캐시에서 변경). 확장은 자식을 부모 뒤에 splice, 접기는 아래 깊은 row를 drain — 선택이 화면 인덱스 그대로여서 visible_rows 계산도 캐시 무효화도 없습니다.git::tree는 재사용하지 않습니다.git2::Repository가 필수고 워크트리 밖 경로와 심볼릭 링크를 거부하는데, 브라우저는 어떤 repo에도 속하지 않는 경로를 돌아다녀야 하고 프로젝트 0개 상태에서도 떠야 합니다.~/coding에서 탐색하면~/coding/…로 돌아옵니다. 예외는←로~위로 올라가는 경우 하나 — 그 부모는 표현 불가라 절대 경로로 대체하되, 텍스트 수술을 믿지 않고canonicalize로 실제 부모와 대조해 검증합니다.src/ui/에 오버레이 인프라가 없고 마우스 캡처가 기본 on이라 플로팅 박스는hit_test.rs에 새 히트 영역을 요구합니다. 마우스 클릭 선택은 계획대로 범위 밖.발견해서 같이 고친 것
Tab완성조차 화면에 안 나오고 있었습니다. 진입 키를 아무리 잘 골라도 광고할 자리가 없으면 못 찾으므로, 입력 줄이 커서 뒤에 축약 legend를 답니다. 폭이 모자라면 반쪽으로 자르지 않고 통째로 버립니다 — 커서는 반드시 보여야 하고 반쪽 legend는 렌더 결함으로 읽힙니다.w(soft wrap)가 파일 미리보기 힌트에서 빠져 있었습니다 (별도 커밋).w처리는 diff focus 전체에 걸려 있어 파일 뷰에서도 동작하는데, 힌트는 unified diff 분기에만 있었습니다 — 정작 긴 줄 때문에 랩을 찾게 되는 뷰가 그것을 안 알려주고 있었습니다. side-by-side 분할 뷰는 렌더가 랩을 무시하므로 의도적으로 제외했습니다.테스트
34개 추가, 전체 981 passed.
cargo build/cargo test/cargo clippy --all-targets --all-features -- -D warnings모두 green.workspace::path_tree13 — splice/drain, re-root 표기 보존,~폴백, 빈 디렉터리, 클램프workspace::tests::repo_picker_tests8 — 필드↔브라우저 계약 (prefill 해제, 취소 전파, 후보 무효화)application_tests::repo_dialog7 — 키 라우팅 (↓진입, 두 번째 Tab 승격, Esc 2단, 브라우저 중 타이핑 차단)ui::tests::repo_picker_tests5 +hint_diff_tests1 — 렌더와 legend참고
이 PR은
whackur:dev→code0xff:dev인데,upstream/dev가 18 커밋 뒤처져 있어 이 기능 외의 기존 커밋들도 함께 들어갑니다 (line numbers, Tab view cycle, soft wrap, Tab 경로 완성, source 재구성 등). 이 기능만 따로 보려면 아래 3개를 보시면 됩니다:a0cdc56fix: advertise the wrap key in the file preview's hint legend7c8782bfeat: browse repo-dialog paths with a directory tree5c12f5ddocs: record the repo dialog's directory browser🤖 Generated with Claude Code