| ... | ... | @@ -163,6 +163,7 @@ import { |
| 163 | 163 | findHighlightedCommandPaletteItem, |
| 164 | 164 | type CommandPaletteActionItem, |
| 165 | 165 | type CommandPaletteOpenIntent, |
| 166 | type CommandPaletteProject, |
| 166 | 167 | type CommandPaletteSubmenuItem, |
| 167 | 168 | type CommandPaletteView, |
| 168 | 169 | filterCommandPaletteGroups, |
| ... | ... | @@ -1309,89 +1310,94 @@ function OpenCommandPaletteDialog(props: { |
| 1309 | 1310 | ], |
| 1310 | 1311 | ); |
| 1311 | 1312 | |
| 1312 | | const projectThreadItems = useMemo( |
| 1313 | | () => |
| 1314 | | enumerateCommandPaletteItems([ |
| 1315 | | ...buildProjectActionItems({ |
| 1316 | | // The no-project home shows once, as the "No project" item below. |
| 1317 | | projects: pickerProjects.filter( |
| 1318 | | (project) => !isScratchProject(project, scratchWorkspaceRootFor(project.environmentId)), |
| 1319 | | ), |
| 1320 | | valuePrefix: "new-thread-in", |
| 1321 | | searchTerms: (project) => { |
| 1322 | | const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); |
| 1323 | | const location = projectEnvironmentLocationById.get(project.environmentId); |
| 1324 | | return [ |
| 1325 | | ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? |
| 1326 | | []), |
| 1327 | | ...(location ? [location.label] : []), |
| 1328 | | ]; |
| 1329 | | }, |
| 1330 | | renderDescription: (project) => { |
| 1331 | | const location = projectEnvironmentLocationById.get(project.environmentId) ?? { |
| 1332 | | kind: "remote", |
| 1333 | | label: "Remote", |
| 1334 | | machine: "server" as const, |
| 1335 | | }; |
| 1336 | | return ( |
| 1337 | | <span className="flex min-w-0 items-center gap-1"> |
| 1338 | | <span className="inline-flex min-w-0 items-center gap-1"> |
| 1339 | | {location.kind === "remote" ? ( |
| 1340 | | <EnvironmentMachineIcon |
| 1341 | | aria-hidden |
| 1342 | | kind={location.machine} |
| 1343 | | className={COMMAND_PALETTE_META_ICON_CLASS} |
| 1344 | | /> |
| 1345 | | ) : null} |
| 1346 | | <span className="truncate">{location.label}</span> |
| 1347 | | </span> |
| 1348 | | <CommandPaletteMetaDot /> |
| 1349 | | <span className="truncate">{project.workspaceRoot}</span> |
| 1313 | const projectThreadItems = useMemo(() => { |
| 1314 | const isScratch = (project: CommandPaletteProject) => |
| 1315 | isScratchProject(project, scratchWorkspaceRootFor(project.environmentId)); |
| 1316 | const projectItems = enumerateCommandPaletteItems( |
| 1317 | buildProjectActionItems({ |
| 1318 | // The no-project home shows once, as the "No project" item below. |
| 1319 | projects: pickerProjects.filter((project) => !isScratch(project)), |
| 1320 | valuePrefix: "new-thread-in", |
| 1321 | searchTerms: (project) => { |
| 1322 | const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); |
| 1323 | const location = projectEnvironmentLocationById.get(project.environmentId); |
| 1324 | return [ |
| 1325 | ...(group?.memberProjects.flatMap((member) => [member.title, member.workspaceRoot]) ?? |
| 1326 | []), |
| 1327 | ...(location ? [location.label] : []), |
| 1328 | ]; |
| 1329 | }, |
| 1330 | renderDescription: (project) => { |
| 1331 | const location = projectEnvironmentLocationById.get(project.environmentId) ?? { |
| 1332 | kind: "remote", |
| 1333 | label: "Remote", |
| 1334 | machine: "server" as const, |
| 1335 | }; |
| 1336 | return ( |
| 1337 | <span className="flex min-w-0 items-center gap-1"> |
| 1338 | <span className="inline-flex min-w-0 items-center gap-1"> |
| 1339 | {location.kind === "remote" ? ( |
| 1340 | <EnvironmentMachineIcon |
| 1341 | aria-hidden |
| 1342 | kind={location.machine} |
| 1343 | className={COMMAND_PALETTE_META_ICON_CLASS} |
| 1344 | /> |
| 1345 | ) : null} |
| 1346 | <span className="truncate">{location.label}</span> |
| 1350 | 1347 | </span> |
| 1348 | <CommandPaletteMetaDot /> |
| 1349 | <span className="truncate">{project.workspaceRoot}</span> |
| 1350 | </span> |
| 1351 | ); |
| 1352 | }, |
| 1353 | icon: projectFaviconIcon, |
| 1354 | runProject: async (project) => { |
| 1355 | const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); |
| 1356 | const contextualRefBelongsToGroup = |
| 1357 | contextualProjectRef !== null && |
| 1358 | group?.memberProjectRefs.some( |
| 1359 | (projectRef) => |
| 1360 | projectRef.environmentId === contextualProjectRef.environmentId && |
| 1361 | projectRef.projectId === contextualProjectRef.projectId, |
| 1351 | 1362 | ); |
| 1352 | | }, |
| 1353 | | icon: projectFaviconIcon, |
| 1354 | | runProject: async (project) => { |
| 1355 | | const group = projectGroupByTargetKey.get(`${project.environmentId}:${project.id}`); |
| 1356 | | const contextualRefBelongsToGroup = |
| 1357 | | contextualProjectRef !== null && |
| 1358 | | group?.memberProjectRefs.some( |
| 1359 | | (projectRef) => |
| 1360 | | projectRef.environmentId === contextualProjectRef.environmentId && |
| 1361 | | projectRef.projectId === contextualProjectRef.projectId, |
| 1362 | | ); |
| 1363 | | await handleNewThread( |
| 1364 | | contextualRefBelongsToGroup |
| 1365 | | ? contextualProjectRef |
| 1366 | | : scopeProjectRef(project.environmentId, project.id), |
| 1367 | | ); |
| 1368 | | }, |
| 1369 | | }), |
| 1370 | | ...(scratchTargetEnvironmentId === null |
| 1371 | | ? [] |
| 1372 | | : [ |
| 1373 | | { |
| 1374 | | kind: "action" as const, |
| 1375 | | value: "new-thread-in:no-project", |
| 1376 | | searchTerms: ["no project", "without project", "none"], |
| 1377 | | title: "No project", |
| 1378 | | icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />, |
| 1379 | | shortcutCommand: "chat.newWithoutProject" as const, |
| 1380 | | run: () => startScratchThread(scratchTargetEnvironmentId), |
| 1381 | | }, |
| 1382 | | ]), |
| 1383 | | ]), |
| 1384 | | [ |
| 1385 | | contextualProjectRef, |
| 1386 | | handleNewThread, |
| 1387 | | pickerProjects, |
| 1388 | | projectEnvironmentLocationById, |
| 1389 | | projectGroupByTargetKey, |
| 1390 | | scratchTargetEnvironmentId, |
| 1391 | | scratchWorkspaceRootFor, |
| 1392 | | startScratchThread, |
| 1393 | | ], |
| 1394 | | ); |
| 1363 | await handleNewThread( |
| 1364 | contextualRefBelongsToGroup |
| 1365 | ? contextualProjectRef |
| 1366 | : scopeProjectRef(project.environmentId, project.id), |
| 1367 | ); |
| 1368 | }, |
| 1369 | }), |
| 1370 | ); |
| 1371 | if (scratchTargetEnvironmentId === null) return projectItems; |
| 1372 | |
| 1373 | // "No project" goes right after the current project: visible without |
| 1374 | // scrolling past every project, while Enter still starts in the current |
| 1375 | // one. When the current thread has no project, it is the current entry and |
| 1376 | // goes first. It keeps its own shortcut, so the projects' mod+1..9 hold. |
| 1377 | const noProjectIndex = pickerProjects[0] !== undefined && isScratch(pickerProjects[0]) ? 0 : 1; |
| 1378 | return [ |
| 1379 | ...projectItems.slice(0, noProjectIndex), |
| 1380 | { |
| 1381 | kind: "action" as const, |
| 1382 | value: "new-thread-in:no-project", |
| 1383 | searchTerms: ["no project", "without project", "none"], |
| 1384 | title: "No project", |
| 1385 | icon: <MessageSquareDashedIcon className={ITEM_ICON_CLASS} />, |
| 1386 | shortcutCommand: "chat.newWithoutProject" as const, |
| 1387 | run: () => startScratchThread(scratchTargetEnvironmentId), |
| 1388 | }, |
| 1389 | ...projectItems.slice(noProjectIndex), |
| 1390 | ]; |
| 1391 | }, [ |
| 1392 | contextualProjectRef, |
| 1393 | handleNewThread, |
| 1394 | pickerProjects, |
| 1395 | projectEnvironmentLocationById, |
| 1396 | projectGroupByTargetKey, |
| 1397 | scratchTargetEnvironmentId, |
| 1398 | scratchWorkspaceRootFor, |
| 1399 | startScratchThread, |
| 1400 | ]); |
| 1395 | 1401 | |
| 1396 | 1402 | const allThreadItems = useMemo( |
| 1397 | 1403 | () => |
| ... | ... | @@ -1848,35 +1854,12 @@ function OpenCommandPaletteDialog(props: { |
| 1848 | 1854 | setNewProjectFlow(null); |
| 1849 | 1855 | setViewStack([]); |
| 1850 | 1856 | setQuery(""); |
| 1851 | | const currentPrefix = |
| 1852 | | currentProjectEnvironmentId && currentProjectId |
| 1853 | | ? `new-thread-in:${currentProjectEnvironmentId}:${currentProjectId}` |
| 1854 | | : null; |
| 1855 | | const prioritized = currentPrefix |
| 1856 | | ? [ |
| 1857 | | ...projectThreadItems.filter((item) => item.value === currentPrefix), |
| 1858 | | ...projectThreadItems.filter((item) => item.value !== currentPrefix), |
| 1859 | | ] |
| 1860 | | : projectThreadItems; |
| 1857 | // projectThreadItems already lists the current project first. |
| 1861 | 1858 | pushPaletteView({ |
| 1862 | 1859 | addonIcon: <SquarePenIcon className={ADDON_ICON_CLASS} />, |
| 1863 | | groups: [ |
| 1864 | | { |
| 1865 | | value: "projects", |
| 1866 | | label: "Projects", |
| 1867 | | items: enumerateCommandPaletteItems(prioritized), |
| 1868 | | }, |
| 1869 | | ], |
| 1860 | groups: [{ value: "projects", label: "Projects", items: projectThreadItems }], |
| 1870 | 1861 | }); |
| 1871 | | }, [ |
| 1872 | | clearOpenIntent, |
| 1873 | | browseNavigation, |
| 1874 | | currentProjectEnvironmentId, |
| 1875 | | currentProjectId, |
| 1876 | | openIntent, |
| 1877 | | projectThreadItems, |
| 1878 | | pushPaletteView, |
| 1879 | | ]); |
| 1862 | }, [clearOpenIntent, browseNavigation, openIntent, projectThreadItems, pushPaletteView]); |
| 1880 | 1863 | |
| 1881 | 1864 | const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = []; |
| 1882 | 1865 | |