mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix: prevent scrollbar flickering in chat view during content streaming (#6266)
This commit is contained in:
parent
623fa2a025
commit
43f649b03f
2 changed files with 119 additions and 90 deletions
|
|
@ -21,7 +21,7 @@ export const Markdown = memo(({ markdown, partial }: { markdown?: string; partia
|
|||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
style={{ position: "relative" }}>
|
||||
<div style={{ wordBreak: "break-word", overflowWrap: "anywhere", marginBottom: -15, marginTop: -15 }}>
|
||||
<div style={{ wordBreak: "break-word", overflowWrap: "anywhere" }}>
|
||||
<MarkdownBlock markdown={markdown} />
|
||||
</div>
|
||||
{markdown && !partial && isHovering && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { memo } from "react"
|
||||
import React, { memo, useMemo } from "react"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import styled from "styled-components"
|
||||
import { visit } from "unist-util-visit"
|
||||
|
|
@ -7,7 +7,6 @@ import remarkMath from "remark-math"
|
|||
import remarkGfm from "remark-gfm"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { useExtensionState } from "@src/context/ExtensionStateContext"
|
||||
|
||||
import CodeBlock from "./CodeBlock"
|
||||
import MermaidBlock from "./MermaidBlock"
|
||||
|
|
@ -117,6 +116,19 @@ const StyledMarkdown = styled.div`
|
|||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
/* Prevent layout shifts during streaming */
|
||||
pre {
|
||||
min-height: 3em;
|
||||
transition: height 0.2s ease-out;
|
||||
}
|
||||
|
||||
/* Code block container styling */
|
||||
div:has(> pre) {
|
||||
position: relative;
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
@ -133,11 +145,18 @@ const StyledMarkdown = styled.div`
|
|||
|
||||
/* Table styles for remark-gfm */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1em 0;
|
||||
width: auto;
|
||||
min-width: 50%;
|
||||
max-width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* Table wrapper for horizontal scrolling */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
th,
|
||||
|
|
@ -145,6 +164,8 @@ const StyledMarkdown = styled.div`
|
|||
border: 1px solid var(--vscode-panel-border);
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
th {
|
||||
|
|
@ -163,96 +184,104 @@ const StyledMarkdown = styled.div`
|
|||
`
|
||||
|
||||
const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
|
||||
const { theme: _theme } = useExtensionState()
|
||||
|
||||
const components = {
|
||||
a: ({ href, children, ...props }: any) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
// Only process file:// protocol or local file paths
|
||||
const isLocalPath = href?.startsWith("file://") || href?.startsWith("/") || !href?.includes("://")
|
||||
|
||||
if (!isLocalPath) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
// Handle absolute vs project-relative paths
|
||||
let filePath = href.replace("file://", "")
|
||||
|
||||
// Extract line number if present
|
||||
const match = filePath.match(/(.*):(\d+)(-\d+)?$/)
|
||||
let values = undefined
|
||||
if (match) {
|
||||
filePath = match[1]
|
||||
values = { line: parseInt(match[2]) }
|
||||
}
|
||||
|
||||
// Add ./ prefix if needed
|
||||
if (!filePath.startsWith("/") && !filePath.startsWith("./")) {
|
||||
filePath = "./" + filePath
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: filePath,
|
||||
values,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...props} href={href} onClick={handleClick}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
pre: ({ children, ..._props }: any) => {
|
||||
// The structure from react-markdown v9 is: pre > code > text
|
||||
const codeEl = children as React.ReactElement
|
||||
|
||||
if (!codeEl || !codeEl.props) {
|
||||
return <pre>{children}</pre>
|
||||
}
|
||||
|
||||
const { className = "", children: codeChildren } = codeEl.props
|
||||
|
||||
// Get the actual code text
|
||||
let codeString = ""
|
||||
if (typeof codeChildren === "string") {
|
||||
codeString = codeChildren
|
||||
} else if (Array.isArray(codeChildren)) {
|
||||
codeString = codeChildren.filter((child) => typeof child === "string").join("")
|
||||
}
|
||||
|
||||
// Handle mermaid diagrams
|
||||
if (className.includes("language-mermaid")) {
|
||||
const components = useMemo(
|
||||
() => ({
|
||||
table: ({ children, ...props }: any) => {
|
||||
return (
|
||||
<div style={{ margin: "1em 0" }}>
|
||||
<MermaidBlock code={codeString} />
|
||||
<div className="table-wrapper">
|
||||
<table {...props}>{children}</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
a: ({ href, children, ...props }: any) => {
|
||||
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
// Only process file:// protocol or local file paths
|
||||
const isLocalPath = href?.startsWith("file://") || href?.startsWith("/") || !href?.includes("://")
|
||||
|
||||
// Extract language from className
|
||||
const match = /language-(\w+)/.exec(className)
|
||||
const language = match ? match[1] : "text"
|
||||
if (!isLocalPath) {
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap CodeBlock in a div to ensure proper separation
|
||||
return (
|
||||
<div style={{ margin: "1em 0" }}>
|
||||
<CodeBlock source={codeString} language={language} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
code: ({ children, className, ...props }: any) => {
|
||||
// This handles inline code
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
}
|
||||
e.preventDefault()
|
||||
|
||||
// Handle absolute vs project-relative paths
|
||||
let filePath = href.replace("file://", "")
|
||||
|
||||
// Extract line number if present
|
||||
const match = filePath.match(/(.*):(\d+)(-\d+)?$/)
|
||||
let values = undefined
|
||||
if (match) {
|
||||
filePath = match[1]
|
||||
values = { line: parseInt(match[2]) }
|
||||
}
|
||||
|
||||
// Add ./ prefix if needed
|
||||
if (!filePath.startsWith("/") && !filePath.startsWith("./")) {
|
||||
filePath = "./" + filePath
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "openFile",
|
||||
text: filePath,
|
||||
values,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...props} href={href} onClick={handleClick}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
pre: ({ children, ..._props }: any) => {
|
||||
// The structure from react-markdown v9 is: pre > code > text
|
||||
const codeEl = children as React.ReactElement
|
||||
|
||||
if (!codeEl || !codeEl.props) {
|
||||
return <pre>{children}</pre>
|
||||
}
|
||||
|
||||
const { className = "", children: codeChildren } = codeEl.props
|
||||
|
||||
// Get the actual code text
|
||||
let codeString = ""
|
||||
if (typeof codeChildren === "string") {
|
||||
codeString = codeChildren
|
||||
} else if (Array.isArray(codeChildren)) {
|
||||
codeString = codeChildren.filter((child) => typeof child === "string").join("")
|
||||
}
|
||||
|
||||
// Handle mermaid diagrams
|
||||
if (className.includes("language-mermaid")) {
|
||||
return (
|
||||
<div style={{ margin: "1em 0" }}>
|
||||
<MermaidBlock code={codeString} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Extract language from className
|
||||
const match = /language-(\w+)/.exec(className)
|
||||
const language = match ? match[1] : "text"
|
||||
|
||||
// Wrap CodeBlock in a div to ensure proper separation
|
||||
return (
|
||||
<div style={{ margin: "1em 0" }}>
|
||||
<CodeBlock source={codeString} language={language} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
code: ({ children, className, ...props }: any) => {
|
||||
// This handles inline code
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
)
|
||||
},
|
||||
}),
|
||||
[],
|
||||
)
|
||||
|
||||
return (
|
||||
<StyledMarkdown>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue