feat: add OpenChamber sidebar command and icon for VS Code title bar
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<svg width="24" height="24" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- OpenChamber logo - tuned for VS Code title bar (no theming) -->
|
||||
<g transform="translate(16, 16) scale(0.48)">
|
||||
<!-- Left face -->
|
||||
<path
|
||||
d="M0 0 L-26 -15 L-26 15 L0 30 Z"
|
||||
fill="none"
|
||||
stroke="#A0AF54"
|
||||
stroke-width="4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<!-- Right face -->
|
||||
<path
|
||||
d="M0 0 L26 -15 L26 15 L0 30 Z"
|
||||
fill="none"
|
||||
stroke="#A0AF54"
|
||||
stroke-width="4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<!-- Top face -->
|
||||
<path
|
||||
d="M0 -30 L-26 -15 L0 0 L26 -15 Z"
|
||||
fill="none"
|
||||
stroke="#A0AF54"
|
||||
stroke-width="4"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<!-- O logo - hollow rectangle frame with soft fill -->
|
||||
<g transform="matrix(0.866, 0.5, -0.866, 0.5, 0, -15) scale(0.55)">
|
||||
<rect
|
||||
x="-12"
|
||||
y="-14"
|
||||
width="24"
|
||||
height="28"
|
||||
fill="#C4D36A"
|
||||
fill-opacity="0.32"
|
||||
stroke="#A0AF54"
|
||||
stroke-width="8"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -54,6 +54,19 @@
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "openchamber.openSidebar",
|
||||
"title": "Open OpenChamber Sidebar",
|
||||
"icon": {
|
||||
"light": "assets/icon.svg",
|
||||
"dark": "assets/icon-titlebar.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "openchamber.focusChat",
|
||||
"title": "OpenChamber: Focus Chat"
|
||||
},
|
||||
|
||||
{
|
||||
"command": "openchamber.restartApi",
|
||||
"title": "OpenChamber: Restart API Connection"
|
||||
@@ -88,6 +101,12 @@
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "openchamber.openSidebar",
|
||||
"group": "navigation@1"
|
||||
}
|
||||
],
|
||||
"openchamber.submenu": [
|
||||
{
|
||||
"command": "openchamber.explain"
|
||||
|
||||
@@ -8,7 +8,11 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
|
||||
public static readonly viewType = 'openchamber.chatView';
|
||||
|
||||
private _view?: vscode.WebviewView;
|
||||
|
||||
|
||||
public isVisible() {
|
||||
return this._view?.visible ?? false;
|
||||
}
|
||||
|
||||
// Cache latest status/URL for when webview is resolved after connection is ready
|
||||
private _cachedStatus: ConnectionStatus = 'connecting';
|
||||
private _cachedError?: string;
|
||||
|
||||
@@ -26,6 +26,143 @@ const formatDurationMs = (value: number | null | undefined) => {
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
outputChannel = vscode.window.createOutputChannel('OpenChamber');
|
||||
|
||||
let moveToRightSidebarScheduled = false;
|
||||
|
||||
const isCursorLikeHost = () => /\bcursor\b/i.test(vscode.env.appName);
|
||||
|
||||
const findMoveToRightSidebarCommandId = async (): Promise<string | null> => {
|
||||
const commands = await vscode.commands.getCommands(true);
|
||||
|
||||
const preferred = [
|
||||
// Newer VS Code naming
|
||||
'workbench.action.moveViewToSecondarySideBar',
|
||||
'workbench.action.moveViewToSecondarySidebar',
|
||||
'workbench.action.moveFocusedViewToSecondarySideBar',
|
||||
'workbench.action.moveFocusedViewToSecondarySidebar',
|
||||
|
||||
// Some builds use "Auxiliary Bar" naming
|
||||
'workbench.action.moveViewToAuxiliaryBar',
|
||||
'workbench.action.moveFocusedViewToAuxiliaryBar',
|
||||
];
|
||||
|
||||
for (const commandId of preferred) {
|
||||
if (commands.includes(commandId)) return commandId;
|
||||
}
|
||||
|
||||
const fuzzy = commands.find((commandId) => {
|
||||
const id = commandId.toLowerCase();
|
||||
const looksLikeMoveView = id.includes('workbench.action') && id.includes('move') && id.includes('view');
|
||||
if (!looksLikeMoveView) return false;
|
||||
|
||||
// Support both "secondary sidebar" and "auxiliary bar" naming.
|
||||
return (id.includes('secondary') && id.includes('side') && id.includes('bar')) || (id.includes('auxiliary') && id.includes('bar'));
|
||||
});
|
||||
|
||||
return fuzzy || null;
|
||||
};
|
||||
|
||||
const attemptMoveChatToRightSidebar = async (): Promise<'moved' | 'unsupported' | 'failed'> => {
|
||||
const moveCommandId = await findMoveToRightSidebarCommandId();
|
||||
if (!moveCommandId) return 'unsupported';
|
||||
|
||||
try {
|
||||
await vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
await vscode.commands.executeCommand(moveCommandId);
|
||||
return 'moved';
|
||||
} catch (error) {
|
||||
outputChannel?.appendLine(
|
||||
`[OpenChamber] Failed moving chat view to right sidebar (command=${moveCommandId}): ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
return 'failed';
|
||||
}
|
||||
};
|
||||
|
||||
const maybeMoveChatToRightSidebarOnStartup = async () => {
|
||||
if (isCursorLikeHost()) return;
|
||||
|
||||
const attempted = context.globalState.get<boolean>('openchamber.sidebarAutoMoveAttempted') || false;
|
||||
if (attempted) return;
|
||||
await context.globalState.update('openchamber.sidebarAutoMoveAttempted', true);
|
||||
|
||||
if (moveToRightSidebarScheduled) return;
|
||||
moveToRightSidebarScheduled = true;
|
||||
|
||||
// Defer until after activation to avoid stealing focus during startup.
|
||||
setTimeout(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
await attemptMoveChatToRightSidebar();
|
||||
} finally {
|
||||
moveToRightSidebarScheduled = false;
|
||||
}
|
||||
})();
|
||||
}, 800);
|
||||
};
|
||||
|
||||
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const tryExecuteCommand = async (commandId: string) => {
|
||||
try {
|
||||
await vscode.commands.executeCommand(commandId);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePrimarySidebar = async () => {
|
||||
return await tryExecuteCommand('workbench.action.toggleSidebarVisibility');
|
||||
};
|
||||
|
||||
const toggleSecondarySidebar = async () => {
|
||||
const candidates = ['workbench.action.toggleSecondarySideBarVisibility', 'workbench.action.toggleAuxiliaryBar'];
|
||||
for (const commandId of candidates) {
|
||||
if (await tryExecuteCommand(commandId)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openchamber.openSidebar', async () => {
|
||||
const isOpen = chatViewProvider?.isVisible() ?? false;
|
||||
|
||||
// Second click hides the sidebar that currently hosts the chat view.
|
||||
if (isOpen) {
|
||||
if (isCursorLikeHost()) {
|
||||
await togglePrimarySidebar();
|
||||
await sleep(50);
|
||||
if (chatViewProvider?.isVisible()) {
|
||||
await toggleSecondarySidebar();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await toggleSecondarySidebar();
|
||||
await sleep(50);
|
||||
if (chatViewProvider?.isVisible()) {
|
||||
await togglePrimarySidebar();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Best-effort: open the container (if available), then focus the chat view.
|
||||
try {
|
||||
await vscode.commands.executeCommand('workbench.view.extension.openchamber');
|
||||
} catch {
|
||||
// Ignore: not all VS Code forks expose this command.
|
||||
}
|
||||
|
||||
await vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openchamber.focusChat', async () => {
|
||||
await vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Migration: clear legacy auto-set API URLs (ports 47680-47689 were auto-assigned by older extension versions)
|
||||
const config = vscode.workspace.getConfiguration('openchamber');
|
||||
const legacyApiUrl = config.get<string>('apiUrl') || '';
|
||||
@@ -48,6 +185,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
)
|
||||
);
|
||||
|
||||
void maybeMoveChatToRightSidebarOnStartup();
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openchamber.restartApi', async () => {
|
||||
try {
|
||||
@@ -91,7 +230,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
chatViewProvider?.addTextToInput(contextText);
|
||||
|
||||
// Focus the chat panel
|
||||
vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
vscode.commands.executeCommand('openchamber.focusChat');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -123,7 +262,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Create new session and send the prompt
|
||||
chatViewProvider?.createNewSessionWithPrompt(prompt);
|
||||
vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
vscode.commands.executeCommand('openchamber.focusChat');
|
||||
})
|
||||
);
|
||||
|
||||
@@ -153,7 +292,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// Create new session and send the prompt
|
||||
chatViewProvider?.createNewSessionWithPrompt(prompt);
|
||||
vscode.commands.executeCommand('openchamber.chatView.focus');
|
||||
vscode.commands.executeCommand('openchamber.focusChat');
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user