feat(vscode): add context menu commands (#83)

* Fix bun lock

* Add Context Menu

* Make it always visible

* Add Improve and Explain Command
This commit is contained in:
wienans
2025-12-30 17:32:27 +02:00
committed by GitHub
parent b331a31414
commit f3a00bc8f4
5 changed files with 234 additions and 34 deletions
+37
View File
@@ -61,8 +61,45 @@
{
"command": "openchamber.showOpenCodeStatus",
"title": "OpenChamber: Show OpenCode Status"
},
{
"command": "openchamber.addToContext",
"title": "Add to Context"
},
{
"command": "openchamber.explain",
"title": "Explain"
},
{
"command": "openchamber.improveCode",
"title": "Improve Code"
}
],
"submenus": [
{
"id": "openchamber.submenu",
"label": "OpenChamber"
}
],
"menus": {
"editor/context": [
{
"submenu": "openchamber.submenu",
"group": "navigation"
}
],
"openchamber.submenu": [
{
"command": "openchamber.explain"
},
{
"command": "openchamber.improveCode"
},
{
"command": "openchamber.addToContext"
}
]
},
"configuration": {
"title": "OpenChamber",
"properties": {
+26
View File
@@ -86,6 +86,32 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
// Send to webview if it exists
this._sendCachedState();
}
public addTextToInput(text: string) {
if (this._view) {
// Reveal the webview panel
this._view.show(true);
this._view.webview.postMessage({
type: 'command',
command: 'addToContext',
payload: { text }
});
}
}
public createNewSessionWithPrompt(prompt: string) {
if (this._view) {
// Reveal the webview panel
this._view.show(true);
this._view.webview.postMessage({
type: 'command',
command: 'createSessionWithPrompt',
payload: { prompt }
});
}
}
private _sendCachedState() {
if (!this._view) {
+98
View File
@@ -59,6 +59,104 @@ export async function activate(context: vscode.ExtensionContext) {
})
);
context.subscriptions.push(
vscode.commands.registerCommand('openchamber.addToContext', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('OpenChamber [Add to Context]:No active editor');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage('OpenChamber [Add to Context]: No text selected');
return;
}
// Get file info for context
const filePath = vscode.workspace.asRelativePath(editor.document.uri);
const languageId = editor.document.languageId;
// Get line numbers (1-based for display)
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
// Format as file path with line numbers, followed by markdown code block
const contextText = `${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
// Send to webview and reveal the panel
chatViewProvider?.addTextToInput(contextText);
// Focus the chat panel
vscode.commands.executeCommand('openchamber.chatView.focus');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('openchamber.explain', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('OpenChamber [Explain]: No active editor');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
const filePath = vscode.workspace.asRelativePath(editor.document.uri);
const languageId = editor.document.languageId;
let prompt: string;
if (selectedText) {
// Selection exists - explain the selected code
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
prompt = `Explain the following Code / Text:\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
} else {
// No selection - explain the entire file
prompt = `Explain the following Code / Text:\n\n${filePath}`;
}
// Create new session and send the prompt
chatViewProvider?.createNewSessionWithPrompt(prompt);
vscode.commands.executeCommand('openchamber.chatView.focus');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('openchamber.improveCode', async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('OpenChamber [Improve Code]: No active editor');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage('OpenChamber [Improve Code]: No text selected');
return;
}
const filePath = vscode.workspace.asRelativePath(editor.document.uri);
const languageId = editor.document.languageId;
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
const lineRange = startLine === endLine ? `${startLine}` : `${startLine}-${endLine}`;
const prompt = `Improve the following Code:\n\n${filePath}:${lineRange}\n\`\`\`${languageId}\n${selectedText}\n\`\`\``;
// Create new session and send the prompt
chatViewProvider?.createNewSessionWithPrompt(prompt);
vscode.commands.executeCommand('openchamber.chatView.focus');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('openchamber.showOpenCodeStatus', async () => {
const config = vscode.workspace.getConfiguration('openchamber');
+52 -1
View File
@@ -1,5 +1,5 @@
import { createVSCodeAPIs } from './api';
import { onThemeChange, proxyApiRequest, sendBridgeMessage, startSseProxy, stopSseProxy } from './api/bridge';
import { onCommand, onThemeChange, proxyApiRequest, sendBridgeMessage, startSseProxy, stopSseProxy } from './api/bridge';
import type { RuntimeAPIs } from '../../ui/src/lib/api/types';
import {
buildVSCodeThemeFromPalette,
@@ -566,6 +566,57 @@ window.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
return originalFetch(input as RequestInfo, init);
};
// Listen for addToContext command from extension
onCommand('addToContext', (payload) => {
const { text } = payload as { text: string };
// Import the store dynamically to avoid circular dependencies
import('../../ui/src/stores/useSessionStore').then(({ useSessionStore }) => {
const store = useSessionStore.getState();
const currentText = store.pendingInputText || '';
// Append to existing text with double newline separator
const newText = currentText ? `${currentText}\n\n${text}` : text;
store.setPendingInputText(newText);
});
});
// Listen for createSessionWithPrompt command from extension (Explain, Improve Code)
onCommand('createSessionWithPrompt', (payload) => {
const { prompt } = payload as { prompt: string };
Promise.all([
import('../../ui/src/stores/useSessionStore'),
import('../../ui/src/stores/useConfigStore'),
]).then(([{ useSessionStore }, { useConfigStore }]) => {
const sessionStore = useSessionStore.getState();
const configStore = useConfigStore.getState();
// Open a new session draft first
sessionStore.openNewSessionDraft();
// Get current provider/model/agent configuration
const { currentProviderId, currentModelId, currentAgentName } = configStore;
if (currentProviderId && currentModelId) {
// Send the message - this will create the session from the draft and send
sessionStore.sendMessage(
prompt,
currentProviderId,
currentModelId,
currentAgentName ?? undefined,
undefined, // attachments
undefined, // agentMentionName
undefined // additionalParts
).catch((error: unknown) => {
console.error('[OpenChamber] Failed to send prompt:', error);
});
} else {
// If no provider/model configured, just set the text and let user send manually
sessionStore.setPendingInputText(prompt);
}
});
});
import('../../ui/src/main')
.then(async () => {
await waitForUiMount();