From 3c8b06f51277d56b1f524088b273d523f373c530 Mon Sep 17 00:00:00 2001 From: Michal Jach Date: Tue, 17 Feb 2026 19:43:02 +0100 Subject: [PATCH] tui: make modified files in sidebar jump to diff context --- .../src/cli/cmd/tui/routes/session/index.tsx | 40 ++++++++++++++++++- .../cli/cmd/tui/routes/session/sidebar.tsx | 16 ++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 55ab4d54dd4..9c1f9e06a90 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -298,6 +298,42 @@ export function Session() { dialog.clear() } + function scrollToFile(file: string) { + const match = (part: ToolPart) => { + if (part.tool === "edit" || part.tool === "write") { + const filePath = part.state.input.filePath + if (typeof filePath !== "string") return false + return normalizePath(filePath) === file + } + if (part.tool !== "apply_patch") return false + if (!("metadata" in part.state)) return false + const files = part.state.metadata?.files + if (!Array.isArray(files)) return false + return files.some((entry) => { + if (!entry || typeof entry !== "object") return false + const item = entry as Record + if (item.relativePath === file) return true + if (typeof item.filePath !== "string") return false + return normalizePath(item.filePath) === file + }) + } + + const msgs = messages() + const message = msgs.findLast((msg) => { + if (msg.role !== "assistant") return false + const parts = sync.data.part[msg.id] ?? [] + return parts.some((part) => part.type === "tool" && match(part)) + }) + if (!message || message.role !== "assistant") return + + const anchor = msgs.findLast((msg) => msg.role === "user" && msg.id <= message.id) + if (!anchor) return + + const child = scroll.getChildren().find((item) => item.id === anchor.id) + if (!child) return + scroll.scrollBy(child.y - scroll.y - 1) + } + function toBottom() { setTimeout(() => { if (!scroll || scroll.isDestroyed) return @@ -1128,7 +1164,7 @@ export function Session() { - + - + diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx index 4ffe91558ed..ae790f24a23 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/sidebar.tsx @@ -1,5 +1,5 @@ import { useSync } from "@tui/context/sync" -import { createMemo, For, Show, Switch, Match } from "solid-js" +import { createMemo, createSignal, For, Show, Switch, Match } from "solid-js" import { createStore } from "solid-js/store" import { useTheme } from "../../context/theme" import { Locale } from "@/util/locale" @@ -12,7 +12,7 @@ import { useDirectory } from "../../context/directory" import { useKV } from "../../context/kv" import { TodoItem } from "../../component/todo-item" -export function Sidebar(props: { sessionID: string; overlay?: boolean }) { +export function Sidebar(props: { sessionID: string; overlay?: boolean; onFileClick?: (file: string) => void }) { const sync = useSync() const { theme } = useTheme() const session = createMemo(() => sync.session.get(props.sessionID)!) @@ -238,9 +238,17 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) { {(item) => { + const [hover, setHover] = createSignal(false) return ( - - + props.onFileClick && setHover(true)} + onMouseOut={() => setHover(false)} + onMouseUp={() => props.onFileClick?.(item.file)} + > + {item.file}