| ... | ... | @@ -92,6 +92,8 @@ import { |
| 92 | 92 | buildProjectActionItems, |
| 93 | 93 | buildRootGroups, |
| 94 | 94 | buildThreadActionItems, |
| 95 | canPreloadBrowsePath, |
| 96 | createBrowseNavigationCoordinator, |
| 95 | 97 | enumerateCommandPaletteItems, |
| 96 | 98 | type CommandPaletteActionItem, |
| 97 | 99 | type CommandPaletteSubmenuItem, |
| ... | ... | @@ -486,6 +488,10 @@ function OpenCommandPaletteDialog(props: { |
| 486 | 488 | const lookupRepository = useAtomQueryRunner(sourceControlEnvironment.repository, { |
| 487 | 489 | reportFailure: false, |
| 488 | 490 | }); |
| 491 | const loadBrowsePath = useAtomQueryRunner(filesystemEnvironment.browse, { |
| 492 | reportFailure: false, |
| 493 | reportDefect: false, |
| 494 | }); |
| 489 | 495 | const cloneRepository = useAtomCommand(sourceControlEnvironment.cloneRepository, { |
| 490 | 496 | reportFailure: false, |
| 491 | 497 | }); |
| ... | ... | @@ -502,6 +508,13 @@ function OpenCommandPaletteDialog(props: { |
| 502 | 508 | const [viewStack, setViewStack] = useState<CommandPaletteView[]>([]); |
| 503 | 509 | const currentView = viewStack.at(-1) ?? null; |
| 504 | 510 | const [browseGeneration, setBrowseGeneration] = useState(0); |
| 511 | const browseNavigationRef = useRef<ReturnType<typeof createBrowseNavigationCoordinator> | null>( |
| 512 | null, |
| 513 | ); |
| 514 | if (browseNavigationRef.current === null) { |
| 515 | browseNavigationRef.current = createBrowseNavigationCoordinator(); |
| 516 | } |
| 517 | const browseNavigation = browseNavigationRef.current; |
| 505 | 518 | const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>( |
| 506 | 519 | null, |
| 507 | 520 | ); |
| ... | ... | @@ -710,6 +723,11 @@ function OpenCommandPaletteDialog(props: { |
| 710 | 723 | browseEnvironmentId && currentProjectEnvironmentId === browseEnvironmentId |
| 711 | 724 | ? currentProjectCwd |
| 712 | 725 | : null; |
| 726 | const getBrowseCwdForEnvironment = useCallback( |
| 727 | (environmentId: EnvironmentId | null): string | null => |
| 728 | environmentId && currentProjectEnvironmentId === environmentId ? currentProjectCwd : null, |
| 729 | [currentProjectCwd, currentProjectEnvironmentId], |
| 730 | ); |
| 713 | 731 | const relativePathNeedsActiveProject = |
| 714 | 732 | isExplicitRelativeProjectPath(query.trim()) && currentProjectCwdForBrowse === null; |
| 715 | 733 | const browseDirectoryPath = isBrowsing ? getBrowseDirectoryPath(query) : ""; |
| ... | ... | @@ -733,8 +751,42 @@ function OpenCommandPaletteDialog(props: { |
| 733 | 751 | const isBrowsePending = browseQuery.isPending; |
| 734 | 752 | const browseEntries = browseResult?.entries ?? EMPTY_BROWSE_ENTRIES; |
| 735 | 753 | const { filteredEntries: filteredBrowseEntries, exactEntry: exactBrowseEntry } = useMemo( |
| 736 | | () => filterBrowseEntries({ browseEntries, browseFilterQuery, highlightedItemValue }), |
| 737 | | [browseEntries, browseFilterQuery, highlightedItemValue], |
| 754 | () => filterBrowseEntries({ browseEntries, browseFilterQuery }), |
| 755 | [browseEntries, browseFilterQuery], |
| 756 | ); |
| 757 | |
| 758 | const prefetchBrowsePath = useCallback( |
| 759 | async ( |
| 760 | partialPath: string, |
| 761 | environmentId: EnvironmentId | null = browseEnvironmentId, |
| 762 | cwd: string | null = currentProjectCwdForBrowse, |
| 763 | ): Promise<void> => { |
| 764 | if (!environmentId) { |
| 765 | return; |
| 766 | } |
| 767 | const environment = environments.find( |
| 768 | (candidate) => candidate.environmentId === environmentId, |
| 769 | ); |
| 770 | if (!canPreloadBrowsePath(environment?.connection.phase)) { |
| 771 | return; |
| 772 | } |
| 773 | |
| 774 | await loadBrowsePath({ |
| 775 | environmentId, |
| 776 | input: { |
| 777 | partialPath, |
| 778 | ...(cwd ? { cwd } : {}), |
| 779 | }, |
| 780 | }); |
| 781 | }, |
| 782 | [browseEnvironmentId, currentProjectCwdForBrowse, environments, loadBrowsePath], |
| 783 | ); |
| 784 | |
| 785 | useEffect( |
| 786 | () => () => { |
| 787 | browseNavigation.invalidate(); |
| 788 | }, |
| 789 | [browseNavigation], |
| 738 | 790 | ); |
| 739 | 791 | |
| 740 | 792 | const openProjectFromSearch = useMemo( |
| ... | ... | @@ -865,18 +917,22 @@ function OpenCommandPaletteDialog(props: { |
| 865 | 917 | ); |
| 866 | 918 | const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); |
| 867 | 919 | |
| 868 | | function pushPaletteView(view: CommandPaletteView): void { |
| 869 | | setViewStack((previousViews) => [ |
| 870 | | ...previousViews, |
| 871 | | { |
| 872 | | addonIcon: view.addonIcon, |
| 873 | | groups: view.groups, |
| 874 | | ...(view.initialQuery ? { initialQuery: view.initialQuery } : {}), |
| 875 | | }, |
| 876 | | ]); |
| 877 | | setHighlightedItemValue(null); |
| 878 | | setQuery(view.initialQuery ?? ""); |
| 879 | | } |
| 920 | const pushPaletteView = useCallback( |
| 921 | (view: CommandPaletteView): void => { |
| 922 | browseNavigation.invalidate(); |
| 923 | setViewStack((previousViews) => [ |
| 924 | ...previousViews, |
| 925 | { |
| 926 | addonIcon: view.addonIcon, |
| 927 | groups: view.groups, |
| 928 | ...(view.initialQuery ? { initialQuery: view.initialQuery } : {}), |
| 929 | }, |
| 930 | ]); |
| 931 | setHighlightedItemValue(null); |
| 932 | setQuery(view.initialQuery ?? ""); |
| 933 | }, |
| 934 | [browseNavigation], |
| 935 | ); |
| 880 | 936 | |
| 881 | 937 | function pushView(item: CommandPaletteSubmenuItem): void { |
| 882 | 938 | pushPaletteView({ |
| ... | ... | @@ -887,6 +943,7 @@ function OpenCommandPaletteDialog(props: { |
| 887 | 943 | } |
| 888 | 944 | |
| 889 | 945 | function popView(): void { |
| 946 | browseNavigation.invalidate(); |
| 890 | 947 | setAddProjectCloneFlow(null); |
| 891 | 948 | if (viewStack.length <= 1) { |
| 892 | 949 | setAddProjectEnvironmentId(null); |
| ... | ... | @@ -897,6 +954,7 @@ function OpenCommandPaletteDialog(props: { |
| 897 | 954 | } |
| 898 | 955 | |
| 899 | 956 | function handleQueryChange(nextQuery: string): void { |
| 957 | browseNavigation.invalidate(); |
| 900 | 958 | setHighlightedItemValue(null); |
| 901 | 959 | setQuery(nextQuery); |
| 902 | 960 | if (nextQuery === "" && currentView?.initialQuery) { |
| ... | ... | @@ -905,16 +963,35 @@ function OpenCommandPaletteDialog(props: { |
| 905 | 963 | } |
| 906 | 964 | |
| 907 | 965 | const startAddProjectBrowse = useCallback( |
| 908 | | (environmentId: EnvironmentId): void => { |
| 909 | | setAddProjectEnvironmentId(environmentId); |
| 910 | | setAddProjectCloneFlow(null); |
| 911 | | pushPaletteView({ |
| 966 | async (environmentId: EnvironmentId): Promise<void> => { |
| 967 | const initialQuery = getAddProjectInitialQueryForEnvironment(environmentId); |
| 968 | const initialBrowsePath = getBrowseDirectoryPath(initialQuery); |
| 969 | const browseCwd = getBrowseCwdForEnvironment(environmentId); |
| 970 | const view: CommandPaletteView = { |
| 912 | 971 | addonIcon: <FolderPlusIcon className={ADDON_ICON_CLASS} />, |
| 913 | 972 | groups: [], |
| 914 | | initialQuery: getAddProjectInitialQueryForEnvironment(environmentId), |
| 973 | initialQuery, |
| 974 | }; |
| 975 | |
| 976 | await browseNavigation.run({ |
| 977 | load: () => |
| 978 | initialBrowsePath.length > 0 |
| 979 | ? prefetchBrowsePath(initialBrowsePath, environmentId, browseCwd) |
| 980 | : Promise.resolve(), |
| 981 | commit: () => { |
| 982 | setAddProjectEnvironmentId(environmentId); |
| 983 | setAddProjectCloneFlow(null); |
| 984 | pushPaletteView(view); |
| 985 | }, |
| 915 | 986 | }); |
| 916 | 987 | }, |
| 917 | | [getAddProjectInitialQueryForEnvironment], |
| 988 | [ |
| 989 | browseNavigation, |
| 990 | getAddProjectInitialQueryForEnvironment, |
| 991 | getBrowseCwdForEnvironment, |
| 992 | prefetchBrowsePath, |
| 993 | pushPaletteView, |
| 994 | ], |
| 918 | 995 | ); |
| 919 | 996 | |
| 920 | 997 | const startAddProjectClone = useCallback( |
| ... | ... | @@ -927,7 +1004,7 @@ function OpenCommandPaletteDialog(props: { |
| 927 | 1004 | initialQuery: "", |
| 928 | 1005 | }); |
| 929 | 1006 | }, |
| 930 | | [], |
| 1007 | [pushPaletteView], |
| 931 | 1008 | ); |
| 932 | 1009 | |
| 933 | 1010 | const openSourceControlSettings = useCallback(() => { |
| ... | ... | @@ -950,7 +1027,7 @@ function OpenCommandPaletteDialog(props: { |
| 950 | 1027 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, |
| 951 | 1028 | keepOpen: true, |
| 952 | 1029 | run: async () => { |
| 953 | | startAddProjectBrowse(environmentId); |
| 1030 | await startAddProjectBrowse(environmentId); |
| 954 | 1031 | }, |
| 955 | 1032 | }, |
| 956 | 1033 | ]; |
| ... | ... | @@ -1043,7 +1120,12 @@ function OpenCommandPaletteDialog(props: { |
| 1043 | 1120 | ), |
| 1044 | 1121 | }); |
| 1045 | 1122 | }, |
| 1046 | | [browseEnvironmentId, buildAddProjectSourceGroups, sourceControlDiscovery.data], |
| 1123 | [ |
| 1124 | browseEnvironmentId, |
| 1125 | buildAddProjectSourceGroups, |
| 1126 | pushPaletteView, |
| 1127 | sourceControlDiscovery.data, |
| 1128 | ], |
| 1047 | 1129 | ); |
| 1048 | 1130 | |
| 1049 | 1131 | const addProjectEnvironmentItems: CommandPaletteActionItem[] = addProjectEnvironmentOptions.map( |
| ... | ... | @@ -1098,6 +1180,7 @@ function OpenCommandPaletteDialog(props: { |
| 1098 | 1180 | addProjectEnvironmentGroups, |
| 1099 | 1181 | addProjectEnvironmentOptions.length, |
| 1100 | 1182 | defaultAddProjectEnvironmentId, |
| 1183 | pushPaletteView, |
| 1101 | 1184 | startAddProjectSourceSelection, |
| 1102 | 1185 | ]); |
| 1103 | 1186 | |
| ... | ... | @@ -1114,6 +1197,7 @@ function OpenCommandPaletteDialog(props: { |
| 1114 | 1197 | return; |
| 1115 | 1198 | } |
| 1116 | 1199 | clearOpenIntent(); |
| 1200 | browseNavigation.invalidate(); |
| 1117 | 1201 | setAddProjectCloneFlow(null); |
| 1118 | 1202 | setViewStack([]); |
| 1119 | 1203 | setQuery(""); |
| ... | ... | @@ -1139,10 +1223,12 @@ function OpenCommandPaletteDialog(props: { |
| 1139 | 1223 | }); |
| 1140 | 1224 | }, [ |
| 1141 | 1225 | clearOpenIntent, |
| 1226 | browseNavigation, |
| 1142 | 1227 | currentProjectEnvironmentId, |
| 1143 | 1228 | currentProjectId, |
| 1144 | 1229 | openIntent, |
| 1145 | 1230 | projectThreadItems, |
| 1231 | pushPaletteView, |
| 1146 | 1232 | ]); |
| 1147 | 1233 | |
| 1148 | 1234 | const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = []; |
| ... | ... | @@ -1225,7 +1311,7 @@ function OpenCommandPaletteDialog(props: { |
| 1225 | 1311 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, |
| 1226 | 1312 | keepOpen: true, |
| 1227 | 1313 | run: async () => { |
| 1228 | | startAddProjectBrowse(wslAddProjectEnvironmentOption.environmentId); |
| 1314 | await startAddProjectBrowse(wslAddProjectEnvironmentOption.environmentId); |
| 1229 | 1315 | }, |
| 1230 | 1316 | }); |
| 1231 | 1317 | } |
| ... | ... | @@ -1541,23 +1627,36 @@ function OpenCommandPaletteDialog(props: { |
| 1541 | 1627 | await handleAddProject(cloneResult.value.cwd); |
| 1542 | 1628 | } |
| 1543 | 1629 | |
| 1544 | | function browseTo(name: string): void { |
| 1545 | | const nextQuery = appendBrowsePathSegment(query, name); |
| 1546 | | setHighlightedItemValue(null); |
| 1547 | | setQuery(nextQuery); |
| 1548 | | setBrowseGeneration((generation) => generation + 1); |
| 1549 | | } |
| 1630 | const browseTo = useCallback( |
| 1631 | async (name: string): Promise<void> => { |
| 1632 | const nextQuery = appendBrowsePathSegment(query, name); |
| 1633 | await browseNavigation.run({ |
| 1634 | load: () => prefetchBrowsePath(getBrowseDirectoryPath(nextQuery)), |
| 1635 | commit: () => { |
| 1636 | setHighlightedItemValue(null); |
| 1637 | setQuery(nextQuery); |
| 1638 | setBrowseGeneration((generation) => generation + 1); |
| 1639 | }, |
| 1640 | }); |
| 1641 | }, |
| 1642 | [browseNavigation, prefetchBrowsePath, query], |
| 1643 | ); |
| 1550 | 1644 | |
| 1551 | | function browseUp(): void { |
| 1645 | const browseUp = useCallback(async (): Promise<void> => { |
| 1552 | 1646 | const parentPath = getBrowseParentPath(query); |
| 1553 | 1647 | if (parentPath === null) { |
| 1554 | 1648 | return; |
| 1555 | 1649 | } |
| 1556 | 1650 | |
| 1557 | | setHighlightedItemValue(null); |
| 1558 | | setQuery(parentPath); |
| 1559 | | setBrowseGeneration((generation) => generation + 1); |
| 1560 | | } |
| 1651 | await browseNavigation.run({ |
| 1652 | load: () => prefetchBrowsePath(parentPath), |
| 1653 | commit: () => { |
| 1654 | setHighlightedItemValue(null); |
| 1655 | setQuery(parentPath); |
| 1656 | setBrowseGeneration((generation) => generation + 1); |
| 1657 | }, |
| 1658 | }); |
| 1659 | }, [browseNavigation, prefetchBrowsePath, query]); |
| 1561 | 1660 | |
| 1562 | 1661 | // Resolve the add-project path from browse data when available. When the |
| 1563 | 1662 | // query has a trailing separator (e.g. "~/projects/foo/"), parentPath is the |