diff options
| author | Fayçal Mitidji <[email protected]> | 2025-12-30 17:52:06 +0100 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-12-30 10:52:06 -0600 |
| commit | b3784588ae3800460fdbcfcc25d1abae6ff994e1 (patch) | |
| tree | 4cdb5a4708e8551560f41dfb8ea0eff420533025 | |
| parent | 104d52bc383ec249e6946fee68eb07edf4209fab (diff) | |
| download | opencode-b3784588ae3800460fdbcfcc25d1abae6ff994e1.tar.gz opencode-b3784588ae3800460fdbcfcc25d1abae6ff994e1.zip | |
Fix: High CPU / memory leak when filtering model list window to empty results (#6435)
| -rw-r--r-- | packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx | 123 |
1 files changed, 67 insertions, 56 deletions
diff --git a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx index 1e764d66b..2a39bb01e 100644 --- a/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx +++ b/packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx @@ -115,11 +115,12 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) { setStore("selected", currentIndex) } } - scroll.scrollTo(0) + scroll?.scrollTo(0) }), ) function move(direction: number) { + if (flat().length === 0) return let next = store.selected + direction if (next < 0) next = flat().length - 1 if (next >= flat().length) next = 0 @@ -129,6 +130,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) { function moveTo(next: number) { setStore("selected", next) props.onMove?.(selected()!) + if (!scroll) return const target = scroll.getChildren().find((child) => { return child.id === JSON.stringify(selected()?.value) }) @@ -172,7 +174,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) { } }) - let scroll: ScrollBoxRenderable + let scroll: ScrollBoxRenderable | undefined const ref: DialogSelectRef<T> = { get filter() { return store.filter @@ -213,61 +215,70 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) { /> </box> </box> - <scrollbox - paddingLeft={1} - paddingRight={1} - scrollbarOptions={{ visible: false }} - ref={(r: ScrollBoxRenderable) => (scroll = r)} - maxHeight={height()} + <Show + when={grouped().length > 0} + fallback={ + <box paddingLeft={4} paddingRight={4} paddingTop={1}> + <text fg={theme.textMuted}>No results found</text> + </box> + } > - <For each={grouped()}> - {([category, options], index) => ( - <> - <Show when={category}> - <box paddingTop={index() > 0 ? 1 : 0} paddingLeft={3}> - <text fg={theme.accent} attributes={TextAttributes.BOLD}> - {category} - </text> - </box> - </Show> - <For each={options}> - {(option) => { - const active = createMemo(() => isDeepEqual(option.value, selected()?.value)) - const current = createMemo(() => isDeepEqual(option.value, props.current)) - return ( - <box - id={JSON.stringify(option.value)} - flexDirection="row" - onMouseUp={() => { - option.onSelect?.(dialog) - props.onSelect?.(option) - }} - onMouseOver={() => { - const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value)) - if (index === -1) return - moveTo(index) - }} - backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)} - paddingLeft={current() || option.gutter ? 1 : 3} - paddingRight={3} - gap={1} - > - <Option - title={option.title} - footer={option.footer} - description={option.description !== category ? option.description : undefined} - active={active()} - current={current()} - gutter={option.gutter} - /> - </box> - ) - }} - </For> - </> - )} - </For> - </scrollbox> + <scrollbox + paddingLeft={1} + paddingRight={1} + scrollbarOptions={{ visible: false }} + ref={(r: ScrollBoxRenderable) => (scroll = r)} + maxHeight={height()} + > + <For each={grouped()}> + {([category, options], index) => ( + <> + <Show when={category}> + <box paddingTop={index() > 0 ? 1 : 0} paddingLeft={3}> + <text fg={theme.accent} attributes={TextAttributes.BOLD}> + {category} + </text> + </box> + </Show> + <For each={options}> + {(option) => { + const active = createMemo(() => isDeepEqual(option.value, selected()?.value)) + const current = createMemo(() => isDeepEqual(option.value, props.current)) + return ( + <box + id={JSON.stringify(option.value)} + flexDirection="row" + onMouseUp={() => { + option.onSelect?.(dialog) + props.onSelect?.(option) + }} + onMouseOver={() => { + const index = filtered().findIndex((x) => isDeepEqual(x.value, option.value)) + if (index === -1) return + moveTo(index) + }} + backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)} + paddingLeft={current() || option.gutter ? 1 : 3} + paddingRight={3} + gap={1} + > + <Option + title={option.title} + footer={option.footer} + description={option.description !== category ? option.description : undefined} + active={active()} + current={current()} + gutter={option.gutter} + /> + </box> + ) + }} + </For> + </> + )} + </For> + </scrollbox> + </Show> <Show when={keybinds().length} fallback={<box flexShrink={0} />}> <box paddingRight={2} paddingLeft={4} flexDirection="row" gap={2} flexShrink={0} paddingTop={1}> <For each={keybinds()}> |
