From e5d86ffb8db491551b19b70b37ab43c96cd59e0f Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Tue, 30 Jul 2024 04:22:47 -0400 Subject: [PATCH] Revert "Rollback virtualized list changes" This reverts commit e6f6d754b2d3610f954326e707402cd16a455848. --- webview-ui/package-lock.json | 13 +++++++ webview-ui/package.json | 1 + webview-ui/src/components/ChatView.tsx | 47 +++++++++++++++----------- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/webview-ui/package-lock.json b/webview-ui/package-lock.json index bf40ea3..e5f12c3 100644 --- a/webview-ui/package-lock.json +++ b/webview-ui/package-lock.json @@ -24,6 +24,7 @@ "react-text-truncate": "^0.19.0", "react-textarea-autosize": "^8.5.3", "react-use": "^17.5.1", + "react-virtuoso": "^4.7.13", "rewire": "^7.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" @@ -17516,6 +17517,18 @@ "react-dom": "*" } }, + "node_modules/react-virtuoso": { + "version": "4.7.13", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.7.13.tgz", + "integrity": "sha512-rabPhipwJ8rdA6TDk1vdVqVoU6eOkWukqoC1pNQVBCsvjBvIeJMi9nO079s0L7EsRzAxFFQNahX+8vuuY4F1Qg==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16 || >=17 || >= 18", + "react-dom": ">=16 || >=17 || >= 18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/webview-ui/package.json b/webview-ui/package.json index 09530aa..3b9733b 100644 --- a/webview-ui/package.json +++ b/webview-ui/package.json @@ -19,6 +19,7 @@ "react-text-truncate": "^0.19.0", "react-textarea-autosize": "^8.5.3", "react-use": "^17.5.1", + "react-virtuoso": "^4.7.13", "rewire": "^7.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/webview-ui/src/components/ChatView.tsx b/webview-ui/src/components/ChatView.tsx index ed0cad1..646e115 100644 --- a/webview-ui/src/components/ChatView.tsx +++ b/webview-ui/src/components/ChatView.tsx @@ -11,6 +11,7 @@ import { getSyntaxHighlighterStyleFromTheme } from "../utilities/getSyntaxHighli import { vscode } from "../utilities/vscode" import ChatRow from "./ChatRow" import TaskHeader from "./TaskHeader" +import { Virtuoso, type VirtuosoHandle } from "react-virtuoso" import Announcement from "./Announcement" interface ChatViewProps { @@ -42,7 +43,8 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA const [syntaxHighlighterStyle, setSyntaxHighlighterStyle] = useState(vsDarkPlus) - const chatContainerRef = useRef(null) + const virtuosoRef = useRef(null) + const [expandedRows, setExpandedRows] = useState>({}) const toggleRowExpansion = (ts: number) => { @@ -312,23 +314,16 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA }, [modifiedMessages]) useEffect(() => { + // We use a setTimeout to ensure new content is rendered before scrolling to the bottom. virtuoso's followOutput would scroll to the bottom before the new content could render. const timer = setTimeout(() => { - scrollToBottom() - }, 0) + // TODO: we can use virtuoso's isAtBottom to prevent scrolling if user is scrolled up, and show a 'scroll to bottom' button for better UX + // NOTE: scroll to bottom may not work if you use margin, see virtuoso's troubleshooting + virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "smooth" }) + }, 50) return () => clearTimeout(timer) }, [visibleMessages]) - const scrollToBottom = (instant: boolean = false) => { - if (chatContainerRef.current) { - const scrollOptions: ScrollToOptions = { - top: chatContainerRef.current.scrollHeight, - behavior: instant ? "auto" : "smooth", - } - chatContainerRef.current.scrollTo(scrollOptions) - } - } - const placeholderText = useMemo(() => { if (messages.at(-1)?.ask === "command_output") { return "Type input to command stdin..." @@ -376,14 +371,23 @@ const ChatView = ({ messages, isHidden, vscodeThemeName, showAnnouncement, hideA )} -
- {visibleMessages.map((message, index) => ( + }} + // followOutput={(isAtBottom) => { + // const lastMessage = modifiedMessages.at(-1) + // if (lastMessage && shouldShowChatRow(lastMessage)) { + // return "smooth" + // } + // return false + // }} + increaseViewportBy={{ top: 0, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts) + data={visibleMessages} // messages is the raw format returned by extension, modifiedMessages is the manipulated structure that combines certain messages of related type, and visibleMessages is the filtered structure that removes messages that should not be rendered + itemContent={(index, message) => ( - ))} -
+ )} + />
setInputValue(e.target.value)} onKeyDown={handleKeyDown} - onHeightChange={() => scrollToBottom(true)} + onHeightChange={() => + //virtuosoRef.current?.scrollToIndex({ index: "LAST", align: "end", behavior: "auto" }) + virtuosoRef.current?.scrollTo({ top: Number.MAX_SAFE_INTEGER, behavior: "auto" }) + } placeholder={placeholderText} maxRows={10} autoFocus={true}