Add theme based syntax highlighting for code blocks

This commit is contained in:
Saoud Rizwan
2024-07-21 12:04:18 -04:00
parent 6e96dc529b
commit c11ab41d01
11 changed files with 1725 additions and 44 deletions

View File

@@ -1,16 +1,16 @@
import { ClaudeAsk, ClaudeMessage, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
import { VSCodeBadge, VSCodeButton, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"
import React, { useState } from "react"
import { ClaudeMessage, ClaudeAsk, ClaudeSay, ClaudeSayTool } from "@shared/ExtensionMessage"
import { VSCodeButton, VSCodeProgressRing, VSCodeBadge } from "@vscode/webview-ui-toolkit/react"
import { COMMAND_OUTPUT_STRING } from "../utilities/combineCommandSequences"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism"
import CodeBlock from "./CodeBlock"
import { SyntaxHighlighterStyle } from "../utilities/getSyntaxHighlighterStyleFromTheme"
import CodeBlock from "./CodeBlock/CodeBlock"
interface ChatRowProps {
message: ClaudeMessage
syntaxHighlighterStyle: SyntaxHighlighterStyle
}
const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
const ChatRow: React.FC<ChatRowProps> = ({ message, syntaxHighlighterStyle }) => {
const [isExpanded, setIsExpanded] = useState(false)
const cost = message.text != null && message.say === "api_req_started" ? JSON.parse(message.text).cost : undefined
@@ -126,7 +126,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
borderRadius: "3px",
padding: "8px",
whiteSpace: "pre-line",
wordWrap: "break-word"
wordWrap: "break-word",
}}>
<span>{message.text}</span>
</div>
@@ -188,7 +188,11 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
{toolIcon("edit")}
<span style={{ fontWeight: "bold" }}>Claude wants to edit this file:</span>
</div>
<CodeBlock diff={tool.diff!} path={tool.path!} />
<CodeBlock
diff={tool.diff!}
path={tool.path!}
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</>
)
case "newFileCreated":
@@ -200,7 +204,11 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
Claude wants to create a new file:
</span>
</div>
<CodeBlock code={tool.content!} path={tool.path!} />
<CodeBlock
code={tool.content!}
path={tool.path!}
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</>
)
case "readFile":
@@ -210,7 +218,11 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
{toolIcon("file-code")}
<span style={{ fontWeight: "bold" }}>Claude wants to read this file:</span>
</div>
<CodeBlock code={tool.content!} path={tool.path!} />
<CodeBlock
code={tool.content!}
path={tool.path!}
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</>
)
case "listFiles":
@@ -222,7 +234,12 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
Claude wants to view this directory:
</span>
</div>
<CodeBlock code={tool.content!} path={tool.path!} language="shell-session" />
<CodeBlock
code={tool.content!}
path={tool.path!}
language="shell-session"
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</>
)
}
@@ -260,7 +277,11 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
</div>
<div style={contentStyle}>
<div>
<CodeBlock code={command} language="shell-session" />
<CodeBlock
code={command}
language="shell-session"
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</div>
{output && (
@@ -268,7 +289,11 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
<p style={{ ...contentStyle, margin: "10px 0 10px 0" }}>
{COMMAND_OUTPUT_STRING}
</p>
<CodeBlock code={output} language="shell-session" />
<CodeBlock
code={output}
language="shell-session"
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</>
)}
</div>
@@ -318,7 +343,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
return (
<div
style={{
padding: "10px 20px 10px 20px",
padding: "10px 15px 10px 15px",
}}>
{renderContent()}
{isExpanded && message.say === "api_req_started" && (
@@ -326,6 +351,7 @@ const ChatRow: React.FC<ChatRowProps> = ({ message }) => {
<CodeBlock
code={JSON.stringify(JSON.parse(message.text || "{}").request, null, 2)}
language="json"
syntaxHighlighterStyle={syntaxHighlighterStyle}
/>
</div>
)}

View File

@@ -9,13 +9,16 @@ import { getApiMetrics } from "../utilities/getApiMetrics"
import { vscode } from "../utilities/vscode"
import ChatRow from "./ChatRow"
import TaskHeader from "./TaskHeader"
import { getSyntaxHighlighterStyleFromTheme } from "../utilities/getSyntaxHighlighterStyleFromTheme"
import vsDarkPlus from "react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus"
interface ChatViewProps {
messages: ClaudeMessage[]
isHidden: boolean
vscodeThemeName?: string
}
// maybe instead of storing state in App, just make chatview always show so dont conditionally load/unload? need to make sure messages are persisted (i remember seeing something about how webviews can be frozen in docs)
const ChatView = ({ messages, isHidden }: ChatViewProps) => {
const ChatView = ({ messages, isHidden, vscodeThemeName }: ChatViewProps) => {
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined
const task = messages.length > 0 ? messages[0] : undefined // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see ClaudeDev.abort)
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
@@ -34,6 +37,16 @@ const ChatView = ({ messages, isHidden }: ChatViewProps) => {
const [primaryButtonText, setPrimaryButtonText] = useState<string | undefined>(undefined)
const [secondaryButtonText, setSecondaryButtonText] = useState<string | undefined>(undefined)
const [syntaxHighlighterStyle, setSyntaxHighlighterStyle] = useState(vsDarkPlus)
useEffect(() => {
if (!vscodeThemeName) return
const theme = getSyntaxHighlighterStyleFromTheme(vscodeThemeName)
if (theme) {
setSyntaxHighlighterStyle(theme)
}
}, [vscodeThemeName])
const scrollToBottom = (instant: boolean = false) => {
const options = {
containerId: "chat-view-container",
@@ -320,7 +333,7 @@ const ChatView = ({ messages, isHidden }: ChatViewProps) => {
overflowY: "auto",
}}>
{modifiedMessages.map((message, index) => (
<ChatRow key={index} message={message} />
<ChatRow key={index} message={message} syntaxHighlighterStyle={syntaxHighlighterStyle} />
))}
</div>
<div

View File

@@ -1,8 +1,9 @@
import React, { useMemo, useState } from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism"
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { getLanguageFromPath } from "../utilities/getLanguageFromPath"
import { useMemo, useState } from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { getLanguageFromPath } from "../../utilities/getLanguageFromPath"
import { SyntaxHighlighterStyle } from "../../utilities/getSyntaxHighlighterStyleFromTheme"
/*
const vscodeSyntaxStyle: React.CSSProperties = {
backgroundColor: "var(--vscode-editor-background)",
@@ -62,13 +63,12 @@ interface CodeBlockProps {
diff?: string
language?: string | undefined
path?: string
syntaxHighlighterStyle: SyntaxHighlighterStyle
}
const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
const CodeBlock = ({ code, diff, language, path, syntaxHighlighterStyle }: CodeBlockProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const backgroundColor = oneDark['pre[class*="language-"]'].background as string
/*
We need to remove leading non-alphanumeric characters from the path in order for our leading ellipses trick to work.
@@ -83,13 +83,11 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
[path, language, code]
)
console.log(inferredLanguage)
return (
<div
style={{
borderRadius: "3px",
backgroundColor: backgroundColor,
backgroundColor: "var(--vscode-editor-background)",
overflow: "hidden", // This ensures the inner scrollable area doesn't overflow the rounded corners
}}>
{path && (
@@ -132,7 +130,16 @@ const CodeBlock = ({ code, diff, language, path }: CodeBlockProps) => {
<SyntaxHighlighter
wrapLines={false}
language={diff ? "diff" : inferredLanguage} // "diff" automatically colors changed lines in green/red
style={oneDark}
style={{
...syntaxHighlighterStyle,
// Our syntax highlighter style doesn't always match the vscode theme 1:1, so we'll apply sensible styles here that vscode exposes to us
'code[class*="language-"]': {
background: "var(--vscode-editor-background)",
},
'pre[class*="language-"]': {
background: "var(--vscode-editor-background)",
},
}}
customStyle={{
margin: 0,
padding: "6px 10px",