fix: custom provider model list overflow and add /provider command#3766
Conversation
… command - Fix selectOne/selectMany overflow: when the model list exceeds the terminal height, the menu now shows a viewport-sized window with scroll indicators (↑ N more above / ↓ N more below) instead of rendering all items off-screen. - Add keyword search: pressing '/' in any selectOne/selectMany menu enters a search mode that filters items by name/desc substring match (case-insensitive). Esc exits search and restores the full list. - Add /provider slash command: lists configured providers and their models; '/provider <name>' switches to that provider's default model (or lists models for selection when multiple are configured). - Add provider name autocomplete for /provider <Tab>. - Add /provider to /help listing. - Add i18n messages for all new UI strings (en + zh). - Add test coverage for /provider arg completion in control/slash_test.go. Closes esengine#3765
esengine
left a comment
There was a problem hiding this comment.
Thanks @lanshi17 — this is a lot of solid work aimed squarely at the right problem.
The /provider command is nicely integrated: completion, help, i18n en/zh parity, and a test for the arg completion. Deferring multi-model selection to /model instead of launching a raw-mode picker inside the bubbletea loop is exactly the right call, and the comment explains why. The viewport scrolling also does the main job from #3765 — with plain arrow/jk navigation the window tracks the selection, so models past the terminal height are reachable again.
One thing I'd like fixed before it goes in, and it's specifically in the search/filter redraw, not the scrolling:
On each filter keystroke the code reassigns filtered and then moves the cursor up by totalLines() computed from the new (post-filter) list:
filtered = filterMenuItems(items, searchQuery) // e.g. 100 -> 3
...
fmt.Fprintf(w, "\033[%dA", totalLines()) // moves up by the NEW height
drawHeader(); render()The previous frame was drawn at the old height. When a filter narrows the list below the viewport — the headline "100+ models, type to filter" path — the move-up is shorter than what was printed last frame, and the extra rows are never cleared: render() only clears each line it rewrites (\033[K), nothing below it. So typing a filter leaves stale rows on screen and drifts the cursor. (There's also a 1-line off-by-one when first entering search, since totalLines() counts the search bar before it's drawn.)
The robust fix is to make the redraw independent of how the height changes between frames: track the number of lines actually printed last frame and move up by that, then emit \033[J (clear to end of screen) right after the redraw so any rows below are wiped. That covers both the narrowing case and the enter-search case.
Two small things while you're in there (non-blocking):
ProviderSwitchingFmt/ProviderSwitchedFmtare added to i18n but never used — drop them or wire them in.select.gohas no automated coverage (understandable for raw-mode TTY), which is why this slipped past CI. A tiny unit test on the line-accounting (the printed-line count for a given filtered size) would guard the regression.
Really nice contribution — once the search redraw clears properly this is good to merge. Thanks again!
|
作者你好,感谢这个 PR。这个方向没问题,但目前还有几处需要改一下:
我这边验证过:隔离 HOME 后 |
…ration
1. Search redraw stale rows: track prevLines (lines actually printed in
the last frame), move cursor up by prevLines (not the new height),
and emit \033[J (clear-to-end-of-screen) after each redraw to wipe
stale rows left by a taller previous frame.
2. Panic in showProviders(): use the unified fallback model list
(ChatModelList → ModelList) for both the count and the single-model
display label. Previously models[0] could panic on an empty slice
when only non-chat models were configured.
3. Viewport height: fixedLines() now returns the exact non-item line
count (4 normal, 5 with search bar) instead of a hardcoded 3.
maxViewport() accepts a searching flag and subtracts the right
overhead. Frame rows are padded to a constant height so cursor
positioning stays stable.
4. Remove unused i18n fields: ProviderSwitchingFmt, ProviderSwitchedFmt.
5. Desktop integration:
- Commands(): add /provider to the built-in command list.
- SlashArgs(): populate ArgData.ProviderNames and CurrentProvider
from the Models() result.
- managementNotice(): handle /provider (list) and /provider <name>
(switch) for the desktop/HTTP Submit path.
- Add providerListText() and providerSwitchText() controller methods.
6. Tests: add select_test.go with FrameLines, MaxViewportBounds, and
FilterMenuItems unit tests. Add provider name completion test in
control/slash_test.go.
|
All review items addressed in the latest push. Here's a summary of what changed: Search redraw stale rows (both reviewers)
Panic in
|
esengine
left a comment
There was a problem hiding this comment.
Verified the latest push end to end: the redraw now tracks prevLines (lines actually printed last frame) and emits \033[J after each frame, which covers both the narrowing-filter case and the enter-search off-by-one; showProviders/switchToProvider share one fallback model slice so the empty-chat-list panic is gone; fixedLines/maxViewport account for the real per-frame overhead with the searching flag; and the desktop/HTTP paths now ship /provider completion + managementNotice handling instead of dangling shared-completion entries. The exhaustive TestFrameLinesNeverExceedsTerminal sweep is exactly the regression guard I asked for.
Thanks for the quick, thorough turnaround — merging.
Summary
Fixes #3765
Two improvements to the CLI TUI:
Bug Fix: Model list overflow in terminal selection menus
When a custom provider returns a large model list (100+ models),
selectOne/selectManymenus rendered all items without viewport windowing — items beyond the terminal height were invisible and unreachable.Changes to
internal/cli/select.go:term.GetSize(fd)/key to enter keyword search mode (case-insensitive filter on name + desc)Escexits search and restores the full listselectOne(single-choice) andselectMany(multi-choice) menusNew Feature:
/providerslash commandAdded a
/providercommand to switch providers from the CLI chat interface:/provider— lists all configured providers with model counts, marks active/provider <name>— switches to that provider's default model (or lists models when multiple are configured)Files Changed
internal/cli/select.gointernal/cli/provider.go/providercommand (new file)internal/cli/chat_tui.go/providerin slash handlerinternal/cli/complete.go/providerautocomplete + provider datainternal/cli/help_view.go/providerin help listinginternal/cli/model.goproviderNames()helperinternal/control/slash.go/providerarg completion +ProviderNamesinArgDatainternal/control/slash_test.go/providercompletioninternal/i18n/i18n.gointernal/i18n/messages_en.gointernal/i18n/messages_zh.goTesting
go build ./...— passesgo vet ./...— passesgo test ./internal/i18n/...— passes (drift guard confirms en/zh parity)go test ./internal/control/...— passes (includes new/providertest cases)go test ./internal/config/...— passes