-
-
+ <>
+
- {status?.status === "started" && (
-
-
-
Running
- {status.pid &&
(PID: {status.pid})
}
-
+
+
+ {status?.status === "started" && (
+
+
+
Running
+ {status.pid &&
(PID: {status.pid})
}
+
+
+ )}
+ {status?.status === "exited" && (
+
+
+
Exited ({status.exitCode})
+
+ )}
+ {output.length > 0 && (
+
-
- )}
- {status?.status === "exited" && (
-
-
-
Exited ({status.exitCode})
-
- )}
- {output.length > 0 && (
-
- )}
+ )}
+
-
-
+
+
+
+
+
+ >
)
}
CommandExecution.displayName = "CommandExecution"
-const OutputContainer = ({ isExpanded, output }: { isExpanded: boolean; output: string }) => (
+const OutputContainerInternal = ({ isExpanded, output }: { isExpanded: boolean; output: string }) => (
{output.length > 0 && }
)
-const MemoizedOutputContainer = memo(OutputContainer)
+const OutputContainer = memo(OutputContainerInternal)
+
+const parseCommandAndOutput = (text: string) => {
+ const index = text.indexOf(COMMAND_OUTPUT_STRING)
+
+ if (index === -1) {
+ return { command: text, output: "" }
+ }
+
+ return {
+ command: text.slice(0, index),
+ output: text.slice(index + COMMAND_OUTPUT_STRING.length),
+ }
+}
diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx
index 70f1840dd5..151a133c25 100644
--- a/webview-ui/src/components/common/CodeBlock.tsx
+++ b/webview-ui/src/components/common/CodeBlock.tsx
@@ -6,8 +6,10 @@ import { bundledLanguages } from "shiki"
import type { ShikiTransformer } from "shiki"
import { ChevronDown, ChevronUp, WrapText, AlignJustify, Copy, Check } from "lucide-react"
import { useAppTranslation } from "@src/i18n/TranslationContext"
+
export const CODE_BLOCK_BG_COLOR = "var(--vscode-editor-background, --vscode-sideBar-background, rgb(30 30 30))"
export const WRAPPER_ALPHA = "cc" // 80% opacity
+
// Configuration constants
export const WINDOW_SHADE_SETTINGS = {
transitionDelayS: 0.2,
@@ -95,7 +97,6 @@ const CodeBlockButtonWrapper = styled.div`
const CodeBlockContainer = styled.div`
position: relative;
overflow: hidden;
- border-bottom: 4px solid var(--vscode-sideBar-background);
background-color: ${CODE_BLOCK_BG_COLOR};
${CodeBlockButtonWrapper} {
@@ -122,7 +123,6 @@ export const StyledPre = styled.div<{
windowshade === "true" ? `${collapsedHeight || WINDOW_SHADE_SETTINGS.collapsedHeight}px` : "none"};
overflow-y: auto;
padding: 10px;
- // transition: max-height ${WINDOW_SHADE_SETTINGS.transitionDelayS} ease-out;
border-radius: 5px;
${({ preStyle }) => preStyle && { ...preStyle }}
@@ -137,7 +137,7 @@ export const StyledPre = styled.div<{
pre,
code {
- /* Undefined wordwrap defaults to true (pre-wrap) behavior */
+ /* Undefined wordwrap defaults to true (pre-wrap) behavior. */
white-space: ${({ wordwrap }) => (wordwrap === "false" ? "pre" : "pre-wrap")};
word-break: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "normal")};
overflow-wrap: ${({ wordwrap }) => (wordwrap === "false" ? "normal" : "break-word")};
@@ -233,24 +233,28 @@ const CodeBlock = memo(
const { showCopyFeedback, copyWithFeedback } = useCopyToClipboard()
const { t } = useAppTranslation()
- // Update current language when prop changes, but only if user hasn't made a selection
+ // Update current language when prop changes, but only if user hasn't
+ // made a selection.
useEffect(() => {
const normalizedLang = normalizeLanguage(language)
+
if (normalizedLang !== currentLanguage && !userChangedLanguageRef.current) {
setCurrentLanguage(normalizedLang)
}
}, [language, currentLanguage])
- // Syntax highlighting with cached Shiki instance
+ // Syntax highlighting with cached Shiki instance.
useEffect(() => {
const fallback = `
${source || ""}
`
+
const highlight = async () => {
- // Show plain text if language needs to be loaded
+ // Show plain text if language needs to be loaded.
if (currentLanguage && !isLanguageLoaded(currentLanguage)) {
setHighlightedCode(fallback)
}
const highlighter = await getHighlighter(currentLanguage)
+
const html = await highlighter.codeToHtml(source || "", {
lang: currentLanguage || "txt",
theme: document.body.className.toLowerCase().includes("light") ? "github-light" : "github-dark",
@@ -273,6 +277,7 @@ const CodeBlock = memo(
},
] as ShikiTransformer[],
})
+
setHighlightedCode(html)
}
@@ -285,13 +290,15 @@ const CodeBlock = memo(
// Check if content height exceeds collapsed height whenever content changes
useEffect(() => {
const codeBlock = codeBlockRef.current
+
if (codeBlock) {
const actualHeight = codeBlock.scrollHeight
setShowCollapseButton(actualHeight >= WINDOW_SHADE_SETTINGS.collapsedHeight)
}
}, [highlightedCode])
- // Ref to track if user was scrolled up *before* the source update potentially changes scrollHeight
+ // Ref to track if user was scrolled up *before* the source update
+ // potentially changes scrollHeight
const wasScrolledUpRef = useRef(false)
// Ref to track if outer container was near bottom
@@ -331,13 +338,14 @@ const CodeBlock = memo(
}
scrollContainer.addEventListener("scroll", handleOuterScroll, { passive: true })
+
// Initial check
handleOuterScroll()
return () => {
scrollContainer.removeEventListener("scroll", handleOuterScroll)
}
- }, []) // Empty dependency array: runs once on mount
+ }, [])
// Store whether we should scroll after highlighting completes
const shouldScrollAfterHighlightRef = useRef(false)
@@ -355,16 +363,24 @@ const CodeBlock = memo(
const updateCodeBlockButtonPosition = useCallback((forceHide = false) => {
const codeBlock = codeBlockRef.current
const copyWrapper = copyButtonWrapperRef.current
- if (!codeBlock) return
+
+ if (!codeBlock) {
+ return
+ }
const rectCodeBlock = codeBlock.getBoundingClientRect()
const scrollContainer = document.querySelector('[data-virtuoso-scroller="true"]')
- if (!scrollContainer) return
+
+ if (!scrollContainer) {
+ return
+ }
// Get wrapper height dynamically
let wrapperHeight
+
if (copyWrapper) {
const copyRect = copyWrapper.getBoundingClientRect()
+
// If height is 0 due to styling, estimate from children
if (copyRect.height > 0) {
wrapperHeight = copyRect.height