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