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
@@ -56,7 +56,7 @@ interface UseChatScrollManagerResult {
|
||||
handleMessageContentChange: (reason?: ContentChangeReason) => void;
|
||||
getAnimationHandlers: (messageId: string) => AnimationHandlers;
|
||||
showScrollButton: boolean;
|
||||
scrollToBottom: (options?: { instant?: boolean }) => void;
|
||||
scrollToBottom: (options?: { instant?: boolean; force?: boolean }) => void;
|
||||
spacerHeight: number;
|
||||
pendingAnchorId: string | null;
|
||||
hasActiveAnchor: boolean;
|
||||
@@ -116,6 +116,7 @@ export const useChatScrollManager = ({
|
||||
const anchorIdRef = React.useRef<string | null>(null);
|
||||
|
||||
const hasAnchoredOnceRef = React.useRef<boolean>(false);
|
||||
const userScrollOverrideRef = React.useRef<boolean>(false);
|
||||
|
||||
const currentPhase = currentSessionId
|
||||
? sessionActivityPhase?.get(currentSessionId) ?? 'idle'
|
||||
@@ -238,13 +239,30 @@ export const useChatScrollManager = ({
|
||||
}
|
||||
}, [pendingAnchorId]);
|
||||
|
||||
const scrollToBottom = React.useCallback((options?: { instant?: boolean }) => {
|
||||
const scrollToBottom = React.useCallback((options?: { instant?: boolean; force?: boolean }) => {
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
|
||||
|
||||
const shouldRespectUserScroll =
|
||||
userScrollOverrideRef.current &&
|
||||
currentPhase === 'idle' &&
|
||||
!isSyncing &&
|
||||
!options?.force &&
|
||||
distanceFromBottom > DEFAULT_SCROLL_BUTTON_THRESHOLD;
|
||||
|
||||
if (shouldRespectUserScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.force) {
|
||||
userScrollOverrideRef.current = false;
|
||||
}
|
||||
|
||||
const bottom = container.scrollHeight - container.clientHeight;
|
||||
scrollEngine.scrollToPosition(Math.max(0, bottom), options);
|
||||
}, [scrollEngine]);
|
||||
}, [currentPhase, isSyncing, scrollEngine]);
|
||||
|
||||
const scrollToNewAnchor = React.useCallback((messageId: string) => {
|
||||
if (lastScrolledAnchorIdRef.current === messageId) {
|
||||
@@ -294,12 +312,16 @@ export const useChatScrollManager = ({
|
||||
});
|
||||
}, [scrollEngine, updateSpacerHeight]);
|
||||
|
||||
const handleScrollEvent = React.useCallback(() => {
|
||||
const handleScrollEvent = React.useCallback((event?: Event) => {
|
||||
const container = scrollRef.current;
|
||||
if (!container || !currentSessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event?.isTrusted) {
|
||||
userScrollOverrideRef.current = true;
|
||||
}
|
||||
|
||||
scrollEngine.handleScroll();
|
||||
updateScrollButtonVisibility();
|
||||
|
||||
@@ -328,10 +350,10 @@ export const useChatScrollManager = ({
|
||||
const container = scrollRef.current;
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener('scroll', handleScrollEvent, { passive: true });
|
||||
container.addEventListener('scroll', handleScrollEvent as EventListener, { passive: true });
|
||||
|
||||
return () => {
|
||||
container.removeEventListener('scroll', handleScrollEvent);
|
||||
container.removeEventListener('scroll', handleScrollEvent as EventListener);
|
||||
};
|
||||
}, [handleScrollEvent]);
|
||||
|
||||
@@ -376,6 +398,7 @@ export const useChatScrollManager = ({
|
||||
|
||||
spacerHeightRef.current = 0;
|
||||
setSpacerHeight(0);
|
||||
userScrollOverrideRef.current = false;
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- only run on session change, not message changes
|
||||
}, [currentSessionId, sessionMessages.length]);
|
||||
|
||||
@@ -16,3 +16,5 @@ export const useRuntimeAPI = <TValue,>(selector: RuntimeAPISelector<TValue>): TV
|
||||
};
|
||||
|
||||
export const useIsDesktopRuntime = (): boolean => useRuntimeAPI((api) => api.runtime.isDesktop);
|
||||
|
||||
export const useIsVSCodeRuntime = (): boolean => useRuntimeAPI((api) => api.runtime.isVSCode);
|
||||
|
||||
@@ -75,25 +75,15 @@ export const useUpdateCheck = (checkOnMount = true): UseUpdateCheckReturn => {
|
||||
error: null,
|
||||
});
|
||||
|
||||
const [mockMode, setMockMode] = useState(shouldMockUpdate);
|
||||
const [mockState, setMockState] = useState<UpdateState | null>(null);
|
||||
|
||||
// Check for mock mode changes (for console toggling)
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const shouldMock = shouldMockUpdate();
|
||||
if (shouldMock !== mockMode) {
|
||||
setMockMode(shouldMock);
|
||||
if (shouldMock) {
|
||||
const config = getMockConfig();
|
||||
if (config) {
|
||||
setMockState(createMockUpdate(config));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
return () => clearInterval(interval);
|
||||
}, [mockMode]);
|
||||
// Only check mock mode once at startup - no polling
|
||||
const [mockMode] = useState(shouldMockUpdate);
|
||||
const [mockState, setMockState] = useState<UpdateState | null>(() => {
|
||||
if (shouldMockUpdate()) {
|
||||
const config = getMockConfig();
|
||||
return config ? createMockUpdate(config) : null;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const checkForUpdates = useCallback(async () => {
|
||||
if (mockMode) {
|
||||
|
||||
Reference in New Issue
Block a user