2760 lines · 93.6 KB · TypeScript
RawDownload
"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.