feat: vscode extension (#59)
* feat: add initial VS Code extension plan and implementation tasks * feat(vscode): added initial version of an Openchamber VSCode extension * feat(vscode): enhance VS Code extension with theme integration and session management * feat(vscode): implement connection status handling and overlay in VSCode layout * feat: move extension to secondary sidebar * chore: upgrade @opencode-ai/sdk to 1.0.150 * vscode: editor bridge, file picker, click-to-open in tool parts * vscode: layout session lifecycle, theme sync, typography overrides * ui: compact mode for vscode, model search, autocomplete width fixes * perf: scroll force flag, raf placeholder, git polling backoff * ui: tool output styling, markdown code block fix, gitignore * refactor: update typography handling for VSCode runtime, remove unused styles * docs: update README with VS Code extension details and add extension image * docs: update changelog with new features and performance improvements
This commit is contained in:
committed by
GitHub
parent
610ccf4c62
commit
bb72c0fb0c
@@ -107,9 +107,17 @@ type SessionGroup = {
|
||||
|
||||
interface SessionSidebarProps {
|
||||
mobileVariant?: boolean;
|
||||
onSessionSelected?: (sessionId: string) => void;
|
||||
allowReselect?: boolean;
|
||||
hideDirectoryControls?: boolean;
|
||||
}
|
||||
|
||||
export const SessionSidebar: React.FC<SessionSidebarProps> = ({ mobileVariant = false }) => {
|
||||
export const SessionSidebar: React.FC<SessionSidebarProps> = ({
|
||||
mobileVariant = false,
|
||||
onSessionSelected,
|
||||
allowReselect = false,
|
||||
hideDirectoryControls = false,
|
||||
}) => {
|
||||
const [editingId, setEditingId] = React.useState<string | null>(null);
|
||||
const [editTitle, setEditTitle] = React.useState('');
|
||||
const [copiedSessionId, setCopiedSessionId] = React.useState<string | null>(null);
|
||||
@@ -336,9 +344,14 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({ mobileVariant =
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
if (!allowReselect && sessionId === currentSessionId) {
|
||||
onSessionSelected?.(sessionId);
|
||||
return;
|
||||
}
|
||||
setCurrentSession(sessionId);
|
||||
onSessionSelected?.(sessionId);
|
||||
},
|
||||
[setCurrentSession],
|
||||
[allowReselect, currentSessionId, onSessionSelected, setCurrentSession],
|
||||
);
|
||||
|
||||
const handleSaveEdit = React.useCallback(async () => {
|
||||
@@ -922,48 +935,50 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({ mobileVariant =
|
||||
mobileVariant ? '' : isDesktopRuntime ? 'bg-transparent' : 'bg-sidebar',
|
||||
)}
|
||||
>
|
||||
<div className="h-14 select-none px-2 flex-shrink-0">
|
||||
<div className="flex h-full items-center gap-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenDirectoryDialog}
|
||||
className={cn(
|
||||
'group flex min-w-0 flex-1 items-center gap-2 rounded-md px-0 py-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
!isDesktopRuntime && 'hover:bg-sidebar/20',
|
||||
)}
|
||||
aria-label="Change project directory"
|
||||
title={directoryTooltip || '/'}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground group-hover:text-foreground',
|
||||
!isDesktopRuntime && 'bg-sidebar/60',
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line className="h-[1.125rem] w-[1.125rem] translate-y-px" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1 overflow-hidden">
|
||||
<p className="truncate whitespace-nowrap typography-ui font-semibold text-muted-foreground group-hover:text-foreground">
|
||||
{displayDirectory || '/'}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{isGitRepo ? (
|
||||
{!hideDirectoryControls && (
|
||||
<div className="h-14 select-none px-2 flex-shrink-0">
|
||||
<div className="flex h-full items-center gap-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenWorktreeManager}
|
||||
onClick={handleOpenDirectoryDialog}
|
||||
className={cn(
|
||||
'inline-flex h-10 w-7 flex-shrink-0 items-center justify-center rounded-xl text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
!isDesktopRuntime && 'bg-sidebar/60 hover:bg-sidebar',
|
||||
'group flex min-w-0 flex-1 items-center gap-2 rounded-md px-0 py-1 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
!isDesktopRuntime && 'hover:bg-sidebar/20',
|
||||
)}
|
||||
aria-label="Manage worktrees"
|
||||
aria-label="Change project directory"
|
||||
title={directoryTooltip || '/'}
|
||||
>
|
||||
<RiGitRepositoryLine className="h-[1.125rem] w-[1.125rem] translate-y-px" />
|
||||
<span
|
||||
className={cn(
|
||||
'flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground group-hover:text-foreground',
|
||||
!isDesktopRuntime && 'bg-sidebar/60',
|
||||
)}
|
||||
>
|
||||
<RiFolder6Line className="h-[1.125rem] w-[1.125rem] translate-y-px" />
|
||||
</span>
|
||||
<div className="min-w-0 flex-1 overflow-hidden">
|
||||
<p className="truncate whitespace-nowrap typography-ui font-semibold text-muted-foreground group-hover:text-foreground">
|
||||
{displayDirectory || '/'}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
{isGitRepo ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenWorktreeManager}
|
||||
className={cn(
|
||||
'inline-flex h-10 w-7 flex-shrink-0 items-center justify-center rounded-xl text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
!isDesktopRuntime && 'bg-sidebar/60 hover:bg-sidebar',
|
||||
)}
|
||||
aria-label="Manage worktrees"
|
||||
>
|
||||
<RiGitRepositoryLine className="h-[1.125rem] w-[1.125rem] translate-y-px" />
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ScrollableOverlay
|
||||
outerClassName="flex-1 min-h-0"
|
||||
@@ -971,6 +986,16 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({ mobileVariant =
|
||||
>
|
||||
{groupedSessions.length === 0 ? (
|
||||
emptyState
|
||||
) : hideDirectoryControls && groupedSessions.length === 1 && groupedSessions[0].isMain ? (
|
||||
<div className="space-y-[0.6rem] py-1">
|
||||
{groupedSessions[0].sessions.length === 0 ? (
|
||||
<div className="py-1 text-left typography-micro text-muted-foreground">
|
||||
No sessions yet.
|
||||
</div>
|
||||
) : (
|
||||
groupedSessions[0].sessions.map((node) => renderSessionNode(node, 0, groupedSessions[0].directory))
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
groupedSessions.map((group) => (
|
||||
<div key={group.id} className="relative">
|
||||
@@ -997,30 +1022,32 @@ export const SessionSidebar: React.FC<SessionSidebarProps> = ({ mobileVariant =
|
||||
<span className="typography-micro font-medium text-muted-foreground truncate group-hover/header:text-foreground">
|
||||
{group.label}
|
||||
</span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-disabled={isCreatingSession}
|
||||
className={cn(
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 hover:text-foreground',
|
||||
isCreatingSession && 'opacity-40 cursor-default',
|
||||
)}
|
||||
aria-label="Create session in this group"
|
||||
onClick={(e) => {
|
||||
if (isCreatingSession) return;
|
||||
e.stopPropagation();
|
||||
handleCreateSessionInGroup(group.directory);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isCreatingSession) return;
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
{!hideDirectoryControls && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-disabled={isCreatingSession}
|
||||
className={cn(
|
||||
'inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 hover:text-foreground',
|
||||
isCreatingSession && 'opacity-40 cursor-default',
|
||||
)}
|
||||
aria-label="Create session in this group"
|
||||
onClick={(e) => {
|
||||
if (isCreatingSession) return;
|
||||
e.stopPropagation();
|
||||
handleCreateSessionInGroup(group.directory);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RiAddLine className="h-4.5 w-4.5" />
|
||||
</span>
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (isCreatingSession) return;
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.stopPropagation();
|
||||
handleCreateSessionInGroup(group.directory);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RiAddLine className="h-4.5 w-4.5" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user