"use client"; import { threadPullRequestLinkMode } from "@t3tools/client-runtime/thread-pull-request-compatibility"; import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment";import { canCreateProjectInEnvironment, getCloneDestinationBrowsePath, getCloneDestinationPath, getCloneDirectoryName, getDefaultCloneUrl, normalizePastedCloneUrl,} from "@t3tools/client-runtime/operations/projects";import { connectionStatusText } from "@t3tools/client-runtime/connection";import { threadSearchMatchKey } from "@t3tools/client-runtime/state/thread-search";import { resolveThreadReferenceCopyTarget } from "@t3tools/shared/threadReference";import { canPreloadBrowsePath, createBrowseNavigationCoordinator, filterFilesystemBrowseEntries, getFilesystemBrowsePath,} from "@t3tools/client-runtime/state/filesystem";import { isAtomCommandInterrupted, settlePromise, squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime";import { type DesktopWslState, type EnvironmentId, type EnvironmentMachineKind, type FilesystemBrowseResult, type ProjectId, type SourceControlDiscoveryResult, type SourceControlProviderKind, type SourceControlRepositoryInfo, PRIMARY_LOCAL_ENVIRONMENT_ID, resolveEnvironmentMachineKind,} from "@t3tools/contracts";import { useLocation, useNavigate, useParams } from "@tanstack/react-router";import * as Option from "effect/Option";import { ArrowLeftIcon, CornerLeftUpIcon, FileSearchIcon, FolderIcon, FolderPlusIcon, GitPullRequestArrowIcon, LinkIcon, MessageSquareIcon, PaletteIcon, SettingsIcon, SquarePenIcon, TextSearchIcon,} from "lucide-react";import { useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, type KeyboardEvent, type ReactNode,} from "react";import { useAtomValue } from "@effect/atom-react"; import { isDesktopLocalConnectionTarget } from "../connection/desktopLocal";import { useDesktopLocalBootstraps } from "../connection/useDesktopLocalBootstraps";import { useHandleNewThread } from "../hooks/useHandleNewThread";import { useOpenPanelPullRequestUrl } from "../hooks/useOpenPanelPullRequestUrl";import { writeTextToClipboard } from "../hooks/useCopyToClipboard";import { useClientSettings } from "../hooks/useSettings";import { useTheme } from "../hooks/useTheme";import { readLocalApi } from "../localApi";import { desktopLocalBackendId } from "../connection/desktopLocal";import { filesystemEnvironment } from "../state/filesystem";import { projectEnvironment } from "../state/projects";import { useEnvironmentQuery } from "../state/query";import { sourceControlEnvironment } from "../state/sourceControl";import { useAtomCommand } from "../state/use-atom-command";import { useAtomQueryRunner } from "../state/use-atom-query-runner";import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments";import { useProjects, useServerConfigs, useThreadShells } from "../state/entities";import { useThreadSearch } from "../state/queries";import { resolveThreadActionProjectRef, startNewThreadFromContext } from "../lib/chatThreadActions";import { appendBrowsePathSegment, ensureBrowseDirectoryPath, findProjectByPath, getBrowseDirectoryPath, hasTrailingPathSeparator, inferProjectTitleFromPath, isExplicitRelativeProjectPath, isUnsupportedWindowsProjectPath, resolveProjectPathForDispatch,} from "../lib/projectPaths";import { onOpenCommandPalette } from "../commandPaletteBus";import { isPreviewFocused } from "../lib/previewFocus";import { isTerminalFocused } from "../lib/terminalFocus";import { PULL_REQUESTS_PANEL_REF, selectActiveRightPanel, useRightPanelStore,} from "../rightPanelStore";import { getLatestThreadForProject, sortThreads } from "../lib/threadSort";import { cn, getLocalFileManagerName, isMacPlatform, isWindowsPlatform, newProjectId,} from "../lib/utils";import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore";import { buildThreadRouteParams, resolveThreadRouteTarget } from "../threadRoutes";import { useAvailableSettingsSearchItems } from "./settings/useAvailableSettingsSearchItems";import { applyWslEnvironmentConfiguration, parseWslUncPath, resolveProjectPickerTarget, resolveWslProjectSelection,} from "../wslPaths";import { ADDON_ICON_CLASS, browseInputEndPaddingClass, buildBrowseGroups, buildCommandPaletteProjectMetadata, buildProjectActionItems, buildRootGroups, buildThreadActionItems, buildLinkedThreadActionItems, enumerateCommandPaletteItems, type CommandPaletteActionItem, type CommandPaletteOpenIntent, type CommandPaletteSubmenuItem, type CommandPaletteView, filterCommandPaletteGroups, filterPinnedBrowseEntries, getCommandPaletteInputPlaceholder, getCommandPaletteMode, ITEM_ICON_CLASS, RECENT_THREAD_LIMIT, reduceCommandPaletteUiState, type SearchOverlayMode,} from "./CommandPalette.logic";import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic";import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic";import { CommandPaletteContent } from "./CommandPaletteContent";import { CommandPaletteResults } from "./CommandPaletteResults";import { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitLabIcon } from "./Icons";import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon";import { ProjectFavicon } from "./ProjectFavicon";import { ProjectFilePicker } from "./files/ProjectFilePicker";import { openLinkPullRequestDialog } from "./pullRequest/LinkPullRequestDialog";import { ProjectContentSearchDialog } from "./search/ProjectContentSearchDialog";import { toggleThemeEditorForTheme } from "./settings/themeEditorStore";import { searchSettings, SETTINGS_SECTION_LABELS } from "./settings/settingsSearch";import { COMMAND_PALETTE_META_ICON_CLASS, CommandPaletteMetaDot, ThreadCommandSubtitle,} from "./ThreadCommandSubtitle";import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators";import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server";import { deriveProviderInstanceEntries, type ProviderInstanceEntry } from "../providerInstances";import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings";import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command";import { Button } from "./ui/button";import { Kbd, KbdGroup } from "./ui/kbd";import { stackedThreadToast, toastManager } from "./ui/toast";import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext";import type { ChatComposerHandle } from "./chat/ChatComposer";import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject";import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore";import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots,} from "../sidebarProjectGrouping";import type { Project } from "../types"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; function projectFavicon(project: Project) { return <ProjectFavicon project={project} className="size-4" />;} function ProjectSearchDescription(props: { readonly environmentLabels: ReadonlyArray<string>; readonly grouped: boolean; readonly location: { readonly kind: "local" | "remote"; readonly label: string; readonly machine: EnvironmentMachineKind; }; readonly workspaceRoot: string;}) { if (!props.grouped) { return ( <span className="flex min-w-0 items-center gap-1"> <span className="inline-flex min-w-0 items-center gap-1"> {props.location.kind === "remote" ? ( <EnvironmentMachineIcon aria-hidden kind={props.location.machine} className={COMMAND_PALETTE_META_ICON_CLASS} /> ) : null} <span className="truncate">{props.location.label}</span> </span> <CommandPaletteMetaDot /> <span className="truncate">{props.workspaceRoot}</span> </span> ); } return <span className="truncate">{props.environmentLabels.join(" · ")}</span>;} function getEnvironmentBrowsePlatform(os: string | null | undefined): string { if (os === "windows") { return "Win32"; } if (os === "darwin") { return "MacIntel"; } if (os === "linux") { return "Linux"; } return typeof navigator === "undefined" ? "" : navigator.platform;} interface AddProjectEnvironmentOption { readonly environmentId: EnvironmentId; readonly label: string; readonly machine: EnvironmentMachineKind; readonly isPrimary: boolean; readonly isConnected: boolean; readonly status: string;} type AddProjectRemoteProviderKind = Extract< SourceControlProviderKind, "github" | "gitlab" | "bitbucket" | "azure-devops">;type AddProjectRemoteSource = AddProjectRemoteProviderKind | "url"; type AddProjectCloneFlow = | { readonly step: "repository"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; } | { readonly step: "confirm"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; readonly repositoryInput: string; readonly repository: SourceControlRepositoryInfo | null; readonly remoteUrl: string; }; const REMOTE_PROJECT_SOURCES: ReadonlyArray<AddProjectRemoteSource> = [ "url", "github", "gitlab", "bitbucket", "azure-devops",];const REMOTE_PROJECT_PROVIDER_SOURCES: ReadonlyArray<AddProjectRemoteProviderKind> = [ "github", "gitlab", "bitbucket", "azure-devops",]; function remoteProjectSourceLabel(source: AddProjectRemoteSource): string { switch (source) { case "github": return "GitHub"; case "gitlab": return "GitLab"; case "bitbucket": return "Bitbucket"; case "azure-devops": return "Azure DevOps"; case "url": return "Git URL"; }} function remoteProjectSourcePathHint(source: AddProjectRemoteSource): string { switch (source) { case "github": return "owner/repo"; case "gitlab": return "group/project"; case "bitbucket": return "workspace/repository"; case "azure-devops": return "project/repository"; case "url": return "URL"; }} function remoteProjectSourceProvider( source: AddProjectRemoteSource,): AddProjectRemoteProviderKind | null { return source === "url" ? null : source;} function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { switch (source) { case "github": return <GitHubIcon className={className} />; case "gitlab": return <GitLabIcon className={className} />; case "bitbucket": return <BitbucketIcon className={className} />; case "azure-devops": return <AzureDevOpsIcon className={className} />; case "url": return <LinkIcon className={className} />; }} function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { if (!flow) return null; if (flow.step === "confirm") return null; if (flow.source === "url") { return "Enter Git clone URL"; } return `Enter ${remoteProjectSourceLabel(flow.source)} repository (${remoteProjectSourcePathHint(flow.source)})`;} function sourceProviderKind(source: AddProjectRemoteSource): AddProjectRemoteProviderKind | null { return source === "url" ? null : source;} function sortAddProjectProviderSources( readinessBySource: AddProjectRemoteSourceReadiness,): ReadonlyArray<AddProjectRemoteProviderKind> { return REMOTE_PROJECT_PROVIDER_SOURCES.toSorted((left, right) => { const leftReady = readinessBySource[left].ready; const rightReady = readinessBySource[right].ready; if (leftReady !== rightReady) { return leftReady ? -1 : 1; } return remoteProjectSourceLabel(left).localeCompare(remoteProjectSourceLabel(right)); });} type AddProjectRemoteSourceReadiness = Record< AddProjectRemoteSource, { readonly ready: boolean; readonly hint: string | null }>; function buildAddProjectRemoteSourceReadiness( discovery: SourceControlDiscoveryResult | null,): AddProjectRemoteSourceReadiness { const unavailable = { ready: false, hint: "Provider status unavailable. Open Settings -> Source Control and rescan.", } as const; const defaultReadiness: AddProjectRemoteSourceReadiness = { url: { ready: true, hint: null }, github: unavailable, gitlab: unavailable, bitbucket: unavailable, "azure-devops": unavailable, }; if (!discovery) { return defaultReadiness; } const providerByKind = new Map( discovery.sourceControlProviders.map((provider) => [provider.kind, provider]), ); const readiness = { ...defaultReadiness }; for (const source of REMOTE_PROJECT_SOURCES) { const kind = sourceProviderKind(source); if (!kind) continue; const provider = providerByKind.get(kind); if (!provider) { readiness[source] = unavailable; continue; } if (provider.status !== "available") { readiness[source] = { ready: false, hint: provider.installHint }; continue; } if (provider.auth.status === "unauthenticated") { readiness[source] = { ready: false, hint: Option.getOrNull(provider.auth.detail) ?? `${provider.label} is not authenticated. Open Settings -> Source Control for setup guidance.`, }; continue; } readiness[source] = { ready: true, hint: null }; } return readiness;} function errorMessage(error: unknown): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return "An error occurred.";} const OVERLAY_MODE_BY_COMMAND = { "commandPalette.toggle": "command", "filePicker.toggle": "files", "projectSearch.toggle": "content",} as const satisfies Partial<Record<string, SearchOverlayMode>>; function overlayModeForCommand(command: string | null): SearchOverlayMode | null { if (command === null) return null; return command in OVERLAY_MODE_BY_COMMAND ? OVERLAY_MODE_BY_COMMAND[command as keyof typeof OVERLAY_MODE_BY_COMMAND] : null;} export function CommandPalette({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(reduceCommandPaletteUiState, { open: false, mode: "command", openIntent: null, }); const setOpen = useCallback((open: boolean) => dispatch({ _tag: "SetOpen", open }), []); const toggleMode = useCallback( (mode: SearchOverlayMode) => dispatch({ _tag: "ToggleMode", mode }), [], ); const openAddProject = useCallback(() => dispatch({ _tag: "OpenAddProject" }), []); const openNewThreadIn = useCallback(() => dispatch({ _tag: "OpenNewThreadIn" }), []); const clearOpenIntent = useCallback(() => dispatch({ _tag: "ClearOpenIntent" }), []); const keybindings = useAtomValue(primaryServerKeybindingsAtom); const { theme, themeHalves, resolvedTheme } = useTheme(); const composerHandleRef = useRef<ChatComposerHandle | null>(null); const routeTarget = useParams({ strict: false, select: (params) => resolveThreadRouteTarget(params), }); const routeThreadRef = routeTarget?.kind === "server" ? routeTarget.threadRef : null; const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen : false, ); const previewOpen = useRightPanelStore((state) => routeThreadRef ? selectActiveRightPanel(state.byThreadKey, routeThreadRef) === "preview" : false, ); useEffect(() => { if (!state.open || state.mode === "command") return; const onEscapeKeyDown = (event: globalThis.KeyboardEvent) => { if (event.isComposing || event.key !== "Escape") return; event.preventDefault(); event.stopPropagation(); toggleMode("command"); }; window.addEventListener("keydown", onEscapeKeyDown, true); return () => window.removeEventListener("keydown", onEscapeKeyDown, true); }, [state.mode, state.open, toggleMode]); useEffect(() => { const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented) return; // Resolve with the complete shortcut context so customized bindings // using any documented `when` condition (e.g. previewFocus) work. const command = resolveShortcutCommand(event, keybindings, { context: { terminalFocus: isTerminalFocused(), terminalOpen, previewFocus: isPreviewFocused(), previewOpen, }, }); if (command === "themeEditor.toggle") { event.preventDefault(); event.stopPropagation(); toggleThemeEditorForTheme({ theme, themeHalves, initialAppearance: resolvedTheme, }); return; } const mode = overlayModeForCommand(command);Lines 1–500 of 2760. Load more
"use client"; import { threadPullRequestLinkMode } from "@t3tools/client-runtime/thread-pull-request-compatibility"; import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment"; import { canCreateProjectInEnvironment, getCloneDestinationBrowsePath, getCloneDestinationPath, getCloneDirectoryName, getDefaultCloneUrl, normalizePastedCloneUrl, } from "@t3tools/client-runtime/operations/projects"; import { connectionStatusText } from "@t3tools/client-runtime/connection"; import { threadSearchMatchKey } from "@t3tools/client-runtime/state/thread-search"; import { resolveThreadReferenceCopyTarget } from "@t3tools/shared/threadReference"; import { canPreloadBrowsePath, createBrowseNavigationCoordinator, filterFilesystemBrowseEntries, getFilesystemBrowsePath, } from "@t3tools/client-runtime/state/filesystem"; import { isAtomCommandInterrupted, settlePromise, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import { type DesktopWslState, type EnvironmentId, type EnvironmentMachineKind, type FilesystemBrowseResult, type ProjectId, type SourceControlDiscoveryResult, type SourceControlProviderKind, type SourceControlRepositoryInfo, PRIMARY_LOCAL_ENVIRONMENT_ID, resolveEnvironmentMachineKind, } from "@t3tools/contracts"; import { useLocation, useNavigate, useParams } from "@tanstack/react-router"; import * as Option from "effect/Option"; import { ArrowLeftIcon, CornerLeftUpIcon, FileSearchIcon, FolderIcon, FolderPlusIcon, GitPullRequestArrowIcon, LinkIcon, MessageSquareIcon, PaletteIcon, SettingsIcon, SquarePenIcon, TextSearchIcon, } from "lucide-react"; import { useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, type KeyboardEvent, type ReactNode, } from "react"; import { useAtomValue } from "@effect/atom-react"; import { isDesktopLocalConnectionTarget } from "../connection/desktopLocal"; import { useDesktopLocalBootstraps } from "../connection/useDesktopLocalBootstraps"; import { useHandleNewThread } from "../hooks/useHandleNewThread"; import { useOpenPanelPullRequestUrl } from "../hooks/useOpenPanelPullRequestUrl"; import { writeTextToClipboard } from "../hooks/useCopyToClipboard"; import { useClientSettings } from "../hooks/useSettings"; import { useTheme } from "../hooks/useTheme"; import { readLocalApi } from "../localApi"; import { desktopLocalBackendId } from "../connection/desktopLocal"; import { filesystemEnvironment } from "../state/filesystem"; import { projectEnvironment } from "../state/projects"; import { useEnvironmentQuery } from "../state/query"; import { sourceControlEnvironment } from "../state/sourceControl"; import { useAtomCommand } from "../state/use-atom-command"; import { useAtomQueryRunner } from "../state/use-atom-query-runner"; import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; import { useProjects, useServerConfigs, useThreadShells } from "../state/entities"; import { useThreadSearch } from "../state/queries"; import { resolveThreadActionProjectRef, startNewThreadFromContext } from "../lib/chatThreadActions"; import { appendBrowsePathSegment, ensureBrowseDirectoryPath, findProjectByPath, getBrowseDirectoryPath, hasTrailingPathSeparator, inferProjectTitleFromPath, isExplicitRelativeProjectPath, isUnsupportedWindowsProjectPath, resolveProjectPathForDispatch, } from "../lib/projectPaths"; import { onOpenCommandPalette } from "../commandPaletteBus"; import { isPreviewFocused } from "../lib/previewFocus"; import { isTerminalFocused } from "../lib/terminalFocus"; import { PULL_REQUESTS_PANEL_REF, selectActiveRightPanel, useRightPanelStore, } from "../rightPanelStore"; import { getLatestThreadForProject, sortThreads } from "../lib/threadSort"; import { cn, getLocalFileManagerName, isMacPlatform, isWindowsPlatform, newProjectId, } from "../lib/utils"; import { selectThreadTerminalUiState, useTerminalUiStateStore } from "../terminalUiStateStore"; import { buildThreadRouteParams, resolveThreadRouteTarget } from "../threadRoutes"; import { useAvailableSettingsSearchItems } from "./settings/useAvailableSettingsSearchItems"; import { applyWslEnvironmentConfiguration, parseWslUncPath, resolveProjectPickerTarget, resolveWslProjectSelection, } from "../wslPaths"; import { ADDON_ICON_CLASS, browseInputEndPaddingClass, buildBrowseGroups, buildCommandPaletteProjectMetadata, buildProjectActionItems, buildRootGroups, buildThreadActionItems, buildLinkedThreadActionItems, enumerateCommandPaletteItems, type CommandPaletteActionItem, type CommandPaletteOpenIntent, type CommandPaletteSubmenuItem, type CommandPaletteView, filterCommandPaletteGroups, filterPinnedBrowseEntries, getCommandPaletteInputPlaceholder, getCommandPaletteMode, ITEM_ICON_CLASS, RECENT_THREAD_LIMIT, reduceCommandPaletteUiState, type SearchOverlayMode, } from "./CommandPalette.logic"; import { orderItemsByPreferredIds, sortLogicalProjectsForSidebar } from "./Sidebar.logic"; import { resolveEnvironmentOptionLabel } from "./BranchToolbar.logic"; import { CommandPaletteContent } from "./CommandPaletteContent"; import { CommandPaletteResults } from "./CommandPaletteResults"; import { AzureDevOpsIcon, BitbucketIcon, GitHubIcon, GitLabIcon } from "./Icons"; import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; import { ProjectFavicon } from "./ProjectFavicon"; import { ProjectFilePicker } from "./files/ProjectFilePicker"; import { openLinkPullRequestDialog } from "./pullRequest/LinkPullRequestDialog"; import { ProjectContentSearchDialog } from "./search/ProjectContentSearchDialog"; import { toggleThemeEditorForTheme } from "./settings/themeEditorStore"; import { searchSettings, SETTINGS_SECTION_LABELS } from "./settings/settingsSearch"; import { COMMAND_PALETTE_META_ICON_CLASS, CommandPaletteMetaDot, ThreadCommandSubtitle, } from "./ThreadCommandSubtitle"; import { ThreadRowLeadingStatus, ThreadRowTrailingStatus } from "./ThreadStatusIndicators"; import { primaryServerKeybindingsAtom, primaryServerProvidersAtom } from "../state/server"; import { deriveProviderInstanceEntries, type ProviderInstanceEntry } from "../providerInstances"; import { resolveShortcutCommand, threadJumpIndexFromCommand } from "../keybindings"; import { CommandDialog, CommandDialogPopup, CommandFooterAction } from "./ui/command"; import { Button } from "./ui/button"; import { Kbd, KbdGroup } from "./ui/kbd"; import { stackedThreadToast, toastManager } from "./ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; import { ComposerHandleContext, useComposerHandleContext } from "../composerHandleContext"; import type { ChatComposerHandle } from "./chat/ChatComposer"; import { getProjectOrderKey, selectProjectGroupingSettings } from "../logicalProject"; import { legacyProjectCwdPreferenceKey, useUiStateStore } from "../uiStateStore"; import { buildSidebarProjectPickerEntries, buildSidebarProjectSnapshots, } from "../sidebarProjectGrouping"; import type { Project } from "../types"; const EMPTY_BROWSE_ENTRIES: FilesystemBrowseResult["entries"] = []; function projectFavicon(project: Project) { return <ProjectFavicon project={project} className="size-4" />; } function ProjectSearchDescription(props: { readonly environmentLabels: ReadonlyArray<string>; readonly grouped: boolean; readonly location: { readonly kind: "local" | "remote"; readonly label: string; readonly machine: EnvironmentMachineKind; }; readonly workspaceRoot: string; }) { if (!props.grouped) { return ( <span className="flex min-w-0 items-center gap-1"> <span className="inline-flex min-w-0 items-center gap-1"> {props.location.kind === "remote" ? ( <EnvironmentMachineIcon aria-hidden kind={props.location.machine} className={COMMAND_PALETTE_META_ICON_CLASS} /> ) : null} <span className="truncate">{props.location.label}</span> </span> <CommandPaletteMetaDot /> <span className="truncate">{props.workspaceRoot}</span> </span> ); } return <span className="truncate">{props.environmentLabels.join(" · ")}</span>; } function getEnvironmentBrowsePlatform(os: string | null | undefined): string { if (os === "windows") { return "Win32"; } if (os === "darwin") { return "MacIntel"; } if (os === "linux") { return "Linux"; } return typeof navigator === "undefined" ? "" : navigator.platform; } interface AddProjectEnvironmentOption { readonly environmentId: EnvironmentId; readonly label: string; readonly machine: EnvironmentMachineKind; readonly isPrimary: boolean; readonly isConnected: boolean; readonly status: string; } type AddProjectRemoteProviderKind = Extract< SourceControlProviderKind, "github" | "gitlab" | "bitbucket" | "azure-devops" >; type AddProjectRemoteSource = AddProjectRemoteProviderKind | "url"; type AddProjectCloneFlow = | { readonly step: "repository"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; } | { readonly step: "confirm"; readonly environmentId: EnvironmentId; readonly source: AddProjectRemoteSource; readonly repositoryInput: string; readonly repository: SourceControlRepositoryInfo | null; readonly remoteUrl: string; }; const REMOTE_PROJECT_SOURCES: ReadonlyArray<AddProjectRemoteSource> = [ "url", "github", "gitlab", "bitbucket", "azure-devops", ]; const REMOTE_PROJECT_PROVIDER_SOURCES: ReadonlyArray<AddProjectRemoteProviderKind> = [ "github", "gitlab", "bitbucket", "azure-devops", ]; function remoteProjectSourceLabel(source: AddProjectRemoteSource): string { switch (source) { case "github": return "GitHub"; case "gitlab": return "GitLab"; case "bitbucket": return "Bitbucket"; case "azure-devops": return "Azure DevOps"; case "url": return "Git URL"; } } function remoteProjectSourcePathHint(source: AddProjectRemoteSource): string { switch (source) { case "github": return "owner/repo"; case "gitlab": return "group/project"; case "bitbucket": return "workspace/repository"; case "azure-devops": return "project/repository"; case "url": return "URL"; } } function remoteProjectSourceProvider( source: AddProjectRemoteSource, ): AddProjectRemoteProviderKind | null { return source === "url" ? null : source; } function remoteProjectSourceIcon(source: AddProjectRemoteSource, className: string): ReactNode { switch (source) { case "github": return <GitHubIcon className={className} />; case "gitlab": return <GitLabIcon className={className} />; case "bitbucket": return <BitbucketIcon className={className} />; case "azure-devops": return <AzureDevOpsIcon className={className} />; case "url": return <LinkIcon className={className} />; } } function remoteProjectInputPlaceholder(flow: AddProjectCloneFlow | null): string | null { if (!flow) return null; if (flow.step === "confirm") return null; if (flow.source === "url") { return "Enter Git clone URL"; } return `Enter ${remoteProjectSourceLabel(flow.source)} repository (${remoteProjectSourcePathHint(flow.source)})`; } function sourceProviderKind(source: AddProjectRemoteSource): AddProjectRemoteProviderKind | null { return source === "url" ? null : source; } function sortAddProjectProviderSources( readinessBySource: AddProjectRemoteSourceReadiness, ): ReadonlyArray<AddProjectRemoteProviderKind> { return REMOTE_PROJECT_PROVIDER_SOURCES.toSorted((left, right) => { const leftReady = readinessBySource[left].ready; const rightReady = readinessBySource[right].ready; if (leftReady !== rightReady) { return leftReady ? -1 : 1; } return remoteProjectSourceLabel(left).localeCompare(remoteProjectSourceLabel(right)); }); } type AddProjectRemoteSourceReadiness = Record< AddProjectRemoteSource, { readonly ready: boolean; readonly hint: string | null } >; function buildAddProjectRemoteSourceReadiness( discovery: SourceControlDiscoveryResult | null, ): AddProjectRemoteSourceReadiness { const unavailable = { ready: false, hint: "Provider status unavailable. Open Settings -> Source Control and rescan.", } as const; const defaultReadiness: AddProjectRemoteSourceReadiness = { url: { ready: true, hint: null }, github: unavailable, gitlab: unavailable, bitbucket: unavailable, "azure-devops": unavailable, }; if (!discovery) { return defaultReadiness; } const providerByKind = new Map( discovery.sourceControlProviders.map((provider) => [provider.kind, provider]), ); const readiness = { ...defaultReadiness }; for (const source of REMOTE_PROJECT_SOURCES) { const kind = sourceProviderKind(source); if (!kind) continue; const provider = providerByKind.get(kind); if (!provider) { readiness[source] = unavailable; continue; } if (provider.status !== "available") { readiness[source] = { ready: false, hint: provider.installHint }; continue; } if (provider.auth.status === "unauthenticated") { readiness[source] = { ready: false, hint: Option.getOrNull(provider.auth.detail) ?? `${provider.label} is not authenticated. Open Settings -> Source Control for setup guidance.`, }; continue; } readiness[source] = { ready: true, hint: null }; } return readiness; } function errorMessage(error: unknown): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return "An error occurred."; } const OVERLAY_MODE_BY_COMMAND = { "commandPalette.toggle": "command", "filePicker.toggle": "files", "projectSearch.toggle": "content", } as const satisfies Partial<Record<string, SearchOverlayMode>>; function overlayModeForCommand(command: string | null): SearchOverlayMode | null { if (command === null) return null; return command in OVERLAY_MODE_BY_COMMAND ? OVERLAY_MODE_BY_COMMAND[command as keyof typeof OVERLAY_MODE_BY_COMMAND] : null; } export function CommandPalette({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(reduceCommandPaletteUiState, { open: false, mode: "command", openIntent: null, }); const setOpen = useCallback((open: boolean) => dispatch({ _tag: "SetOpen", open }), []); const toggleMode = useCallback( (mode: SearchOverlayMode) => dispatch({ _tag: "ToggleMode", mode }), [], ); const openAddProject = useCallback(() => dispatch({ _tag: "OpenAddProject" }), []); const openNewThreadIn = useCallback(() => dispatch({ _tag: "OpenNewThreadIn" }), []); const clearOpenIntent = useCallback(() => dispatch({ _tag: "ClearOpenIntent" }), []); const keybindings = useAtomValue(primaryServerKeybindingsAtom); const { theme, themeHalves, resolvedTheme } = useTheme(); const composerHandleRef = useRef<ChatComposerHandle | null>(null); const routeTarget = useParams({ strict: false, select: (params) => resolveThreadRouteTarget(params), }); const routeThreadRef = routeTarget?.kind === "server" ? routeTarget.threadRef : null; const terminalOpen = useTerminalUiStateStore((state) => routeThreadRef ? selectThreadTerminalUiState(state.terminalUiStateByThreadKey, routeThreadRef).terminalOpen : false, ); const previewOpen = useRightPanelStore((state) => routeThreadRef ? selectActiveRightPanel(state.byThreadKey, routeThreadRef) === "preview" : false, ); useEffect(() => { if (!state.open || state.mode === "command") return; const onEscapeKeyDown = (event: globalThis.KeyboardEvent) => { if (event.isComposing || event.key !== "Escape") return; event.preventDefault(); event.stopPropagation(); toggleMode("command"); }; window.addEventListener("keydown", onEscapeKeyDown, true); return () => window.removeEventListener("keydown", onEscapeKeyDown, true); }, [state.mode, state.open, toggleMode]); useEffect(() => { const onKeyDown = (event: globalThis.KeyboardEvent) => { if (event.defaultPrevented) return; // Resolve with the complete shortcut context so customized bindings // using any documented `when` condition (e.g. previewFocus) work. const command = resolveShortcutCommand(event, keybindings, { context: { terminalFocus: isTerminalFocused(), terminalOpen, previewFocus: isPreviewFocused(), previewOpen, }, }); if (command === "themeEditor.toggle") { event.preventDefault(); event.stopPropagation(); toggleThemeEditorForTheme({ theme, themeHalves, initialAppearance: resolvedTheme, }); return; } const mode = overlayModeForCommand(command);