Skip to content
This repository was archived by the owner on Jun 28, 2026. It is now read-only.

Commit a06a714

Browse files
olasunkanmi.raymondolasunkanmi.raymond
authored andcommitted
<feat(webview): Add code insertion and terminal execution buttons
- Add and message handlers in base.ts to support editor/terminal interactions - Remove unused streaming UI elements and BotIcon import from botMessage.tsx - Update webview.tsx to pass VS Code API to highlightCodeBlocks - Add insert/run buttons to code blocks in highlightCode.ts with language-specific terminal support - Remove isStreaming prop and associated conditional rendering from BotMessage component
1 parent 610d1cf commit a06a714

4 files changed

Lines changed: 110 additions & 41 deletions

File tree

src/webview-providers/base.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,30 @@ export abstract class BaseWebViewProvider implements vscode.Disposable {
779779
vscode.env.openExternal(vscode.Uri.parse(message.text));
780780
}
781781
break;
782+
case "insertCode":
783+
{
784+
const editor = vscode.window.activeTextEditor;
785+
if (editor) {
786+
editor.edit((editBuilder) => {
787+
editBuilder.insert(editor.selection.active, message.text);
788+
});
789+
} else {
790+
vscode.window.showErrorMessage(
791+
"No active editor to insert code into.",
792+
);
793+
}
794+
}
795+
break;
796+
case "runInTerminal":
797+
{
798+
let terminal = vscode.window.activeTerminal;
799+
if (!terminal) {
800+
terminal = vscode.window.createTerminal("CodeBuddy");
801+
}
802+
terminal.show();
803+
terminal.sendText(message.text);
804+
}
805+
break;
782806
case "update-model-event":
783807
await this.orchestrator.publish("onModelChange", message);
784808
break;

webviewUi/src/components/botMessage.tsx

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import DOMPurify from "dompurify";
33
import React from "react";
4-
import { BotIcon } from "./botIcon";
54
import { DownloadIcon } from "./downloadIcon";
65
import { IParseURL, parseUrl } from "../utils/parseUrl";
76
import UrlCardList from "./urlCardList";
@@ -15,7 +14,6 @@ interface BotMessageProps {
1514

1615
export const BotMessage: React.FC<BotMessageProps> = ({
1716
content,
18-
isStreaming = false
1917
}) => {
2018
const sanitizedContent = DOMPurify.sanitize(content);
2119
// const action = "Researching...";
@@ -113,36 +111,20 @@ export const BotMessage: React.FC<BotMessageProps> = ({
113111
};
114112

115113
// Show streaming indicator
116-
if (isStreaming && !content) {
117-
return (
118-
<div className="doc-content">
119-
<span style={{ display: "flex", alignItems: "center" }}>
120-
<small>Generating response...</small> <BotIcon isBlinking={true} />
121-
</span>
122-
</div>
123-
);
124-
}
125-
126-
// Show thinking state
127-
// if (content.includes("thinking")) {
114+
// if (isStreaming && !content) {
128115
// return (
129116
// <div className="doc-content">
130117
// <span style={{ display: "flex", alignItems: "center" }}>
131-
// <small>{action}</small> <BotIcon isBlinking={true} />
118+
// <small>Generating response...</small> <BotIcon isBlinking={true} />
132119
// </span>
133120
// </div>
134121
// );
135122
// }
136-
123+
137124
// Show normal message with streaming cursor if applicable
138125
return (
139126
<div className="bot-message">
140127
<div className="bot-message-actions">
141-
{/* {isStreaming && (
142-
<span className="streaming-status">
143-
Generating...
144-
</span>
145-
)} */}
146128
<div className="action-buttons">
147129
<DownloadIcon onClick={handleCopyMarkdown} />
148130
</div>
@@ -155,19 +137,6 @@ export const BotMessage: React.FC<BotMessageProps> = ({
155137
) : (
156138
<div style={{ position: "relative" }}>
157139
<ThinkingComponent content={content} />
158-
{/* {isStreaming && (
159-
<span
160-
className="streaming-cursor"
161-
style={{
162-
display: "inline-block",
163-
width: "8px",
164-
height: "16px",
165-
backgroundColor: "currentColor",
166-
marginLeft: "2px",
167-
animation: "blink 1s infinite",
168-
}}
169-
/>
170-
)} */}
171140
</div>
172141
)}
173142
</div>

webviewUi/src/components/webview.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ export const WebviewUI = () => {
349349

350350
// Highlight code blocks when messages update
351351
useEffect(() => {
352-
highlightCodeBlocks(hljsApi, streamedMessages);
352+
highlightCodeBlocks(hljsApi, streamedMessages, vsCode);
353353
}, [streamedMessages]);
354354

355355
// Clear command execution state when streaming completes

webviewUi/src/utils/highlightCode.ts

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ function decodeHtml(html: string): string {
88
return txt.value;
99
}
1010

11-
export const highlightCodeBlocks = (hljsApi: HLJSApi, messages: any) => {
11+
export const highlightCodeBlocks = (
12+
hljsApi: HLJSApi,
13+
messages: any,
14+
vscodeApi?: any,
15+
) => {
1216
if (!hljsApi || messages?.length <= 0) return;
1317
document
1418
.querySelectorAll("pre code:not(.hljs-done):not(.mermaid-processed)")
@@ -38,10 +42,19 @@ export const highlightCodeBlocks = (hljsApi: HLJSApi, messages: any) => {
3842
block.setHTMLUnsafe(highlightedCode);
3943
block.classList.add("hljs-done");
4044

45+
// Create container for buttons
46+
const buttonContainer = document.createElement("div");
47+
buttonContainer.style.display = "flex";
48+
buttonContainer.style.gap = "8px";
49+
buttonContainer.style.alignItems = "center";
50+
51+
// Create Copy Button
4152
const copyButton = document.createElement("button");
4253
copyButton.innerHTML = "Copy";
4354
copyButton.classList.add("copy-button");
4455
copyButton.setAttribute("aria-label", "Copy code to clipboard");
56+
copyButton.style.position = "static";
57+
copyButton.style.margin = "0";
4558

4659
copyButton.addEventListener("click", () => {
4760
try {
@@ -56,6 +69,73 @@ export const highlightCodeBlocks = (hljsApi: HLJSApi, messages: any) => {
5669
}
5770
});
5871

72+
// Create Insert Button
73+
let insertButton: HTMLButtonElement | null = null;
74+
let runButton: HTMLButtonElement | null = null;
75+
76+
if (vscodeApi) {
77+
insertButton = document.createElement("button");
78+
insertButton.innerHTML = "Insert";
79+
insertButton.classList.add("copy-button"); // Recycle copy-button styles for now
80+
insertButton.setAttribute("aria-label", "Insert code into editor");
81+
insertButton.style.position = "static";
82+
insertButton.style.margin = "0";
83+
84+
insertButton.addEventListener("click", () => {
85+
vscodeApi.postMessage({
86+
command: "insertCode",
87+
text: block.textContent ?? "",
88+
});
89+
insertButton!.textContent = "Inserted!";
90+
setTimeout(() => {
91+
insertButton!.textContent = "Insert";
92+
}, 2000);
93+
});
94+
95+
// Create Run Button for terminal-compatible languages
96+
const terminalLanguages = [
97+
"bash",
98+
"sh",
99+
"shell",
100+
"zsh",
101+
"python",
102+
"node",
103+
"js",
104+
"javascript",
105+
];
106+
if (
107+
detectedLanguage &&
108+
terminalLanguages.includes(detectedLanguage.toLowerCase())
109+
) {
110+
runButton = document.createElement("button");
111+
runButton.innerHTML = "Run";
112+
runButton.classList.add("copy-button");
113+
runButton.setAttribute("aria-label", "Run code in terminal");
114+
runButton.style.position = "static";
115+
runButton.style.margin = "0";
116+
117+
runButton.addEventListener("click", () => {
118+
vscodeApi.postMessage({
119+
command: "runInTerminal",
120+
text: block.textContent ?? "",
121+
language: detectedLanguage,
122+
});
123+
runButton!.textContent = "Running...";
124+
setTimeout(() => {
125+
runButton!.textContent = "Run";
126+
}, 2000);
127+
});
128+
}
129+
}
130+
131+
buttonContainer.appendChild(copyButton);
132+
if (insertButton) {
133+
buttonContainer.appendChild(insertButton);
134+
}
135+
if (runButton) {
136+
buttonContainer.appendChild(runButton);
137+
}
138+
59139
// Create a wrapper for the code block with its own copy button
60140
const preElement = block.closest("pre");
61141
if (preElement && !preElement.querySelector(".code-block-wrapper")) {
@@ -84,12 +164,8 @@ export const highlightCodeBlocks = (hljsApi: HLJSApi, messages: any) => {
84164
languageLabel.style.color = "var(--vscode-editor-foreground)";
85165
languageLabel.style.opacity = "0.8";
86166

87-
// Add copy button to the header
88-
copyButton.style.position = "static";
89-
copyButton.style.margin = "0";
90-
91167
codeHeader.appendChild(languageLabel);
92-
codeHeader.appendChild(copyButton);
168+
codeHeader.appendChild(buttonContainer);
93169

94170
// Wrap the pre element
95171
preElement.parentNode?.insertBefore(wrapper, preElement);

0 commit comments

Comments
 (0)