| ... | @@ -92,6 +92,8 @@ import { | ... | @@ -92,6 +92,8 @@ import { |
| 92 | buildProjectActionItems, | 92 | buildProjectActionItems, |
| 93 | buildRootGroups, | 93 | buildRootGroups, |
| 94 | buildThreadActionItems, | 94 | buildThreadActionItems, |
| | 95 | canPreloadBrowsePath, |
| | 96 | createBrowseNavigationCoordinator, |
| 95 | enumerateCommandPaletteItems, | 97 | enumerateCommandPaletteItems, |
| 96 | type CommandPaletteActionItem, | 98 | type CommandPaletteActionItem, |
| 97 | type CommandPaletteSubmenuItem, | 99 | type CommandPaletteSubmenuItem, |
| ... | @@ -486,6 +488,10 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -486,6 +488,10 @@ function OpenCommandPaletteDialog(props: { |
| 486 | const lookupRepository = useAtomQueryRunner(sourceControlEnvironment.repository, { | 488 | const lookupRepository = useAtomQueryRunner(sourceControlEnvironment.repository, { |
| 487 | reportFailure: false, | 489 | reportFailure: false, |
| 488 | }); | 490 | }); |
| | 491 | const loadBrowsePath = useAtomQueryRunner(filesystemEnvironment.browse, { |
| | 492 | reportFailure: false, |
| | 493 | reportDefect: false, |
| | 494 | }); |
| 489 | const cloneRepository = useAtomCommand(sourceControlEnvironment.cloneRepository, { | 495 | const cloneRepository = useAtomCommand(sourceControlEnvironment.cloneRepository, { |
| 490 | reportFailure: false, | 496 | reportFailure: false, |
| 491 | }); | 497 | }); |
| ... | @@ -502,6 +508,13 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -502,6 +508,13 @@ function OpenCommandPaletteDialog(props: { |
| 502 | const [viewStack, setViewStack] = useState<CommandPaletteView[]>([]); | 508 | const [viewStack, setViewStack] = useState<CommandPaletteView[]>([]); |
| 503 | const currentView = viewStack.at(-1) ?? null; | 509 | const currentView = viewStack.at(-1) ?? null; |
| 504 | const [browseGeneration, setBrowseGeneration] = useState(0); | 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 | const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>( | 518 | const [addProjectEnvironmentId, setAddProjectEnvironmentId] = useState<EnvironmentId | null>( |
| 506 | null, | 519 | null, |
| 507 | ); | 520 | ); |
| ... | @@ -710,6 +723,11 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -710,6 +723,11 @@ function OpenCommandPaletteDialog(props: { |
| 710 | browseEnvironmentId && currentProjectEnvironmentId === browseEnvironmentId | 723 | browseEnvironmentId && currentProjectEnvironmentId === browseEnvironmentId |
| 711 | ? currentProjectCwd | 724 | ? currentProjectCwd |
| 712 | : null; | 725 | : null; |
| | 726 | const getBrowseCwdForEnvironment = useCallback( |
| | 727 | (environmentId: EnvironmentId | null): string | null => |
| | 728 | environmentId && currentProjectEnvironmentId === environmentId ? currentProjectCwd : null, |
| | 729 | [currentProjectCwd, currentProjectEnvironmentId], |
| | 730 | ); |
| 713 | const relativePathNeedsActiveProject = | 731 | const relativePathNeedsActiveProject = |
| 714 | isExplicitRelativeProjectPath(query.trim()) && currentProjectCwdForBrowse === null; | 732 | isExplicitRelativeProjectPath(query.trim()) && currentProjectCwdForBrowse === null; |
| 715 | const browseDirectoryPath = isBrowsing ? getBrowseDirectoryPath(query) : ""; | 733 | const browseDirectoryPath = isBrowsing ? getBrowseDirectoryPath(query) : ""; |
| ... | @@ -733,8 +751,42 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -733,8 +751,42 @@ function OpenCommandPaletteDialog(props: { |
| 733 | const isBrowsePending = browseQuery.isPending; | 751 | const isBrowsePending = browseQuery.isPending; |
| 734 | const browseEntries = browseResult?.entries ?? EMPTY_BROWSE_ENTRIES; | 752 | const browseEntries = browseResult?.entries ?? EMPTY_BROWSE_ENTRIES; |
| 735 | const { filteredEntries: filteredBrowseEntries, exactEntry: exactBrowseEntry } = useMemo( | 753 | const { filteredEntries: filteredBrowseEntries, exactEntry: exactBrowseEntry } = useMemo( |
| 736 | () => filterBrowseEntries({ browseEntries, browseFilterQuery, highlightedItemValue }), | 754 | () => filterBrowseEntries({ browseEntries, browseFilterQuery }), |
| 737 | [browseEntries, browseFilterQuery, highlightedItemValue], | 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 | const openProjectFromSearch = useMemo( | 792 | const openProjectFromSearch = useMemo( |
| ... | @@ -865,18 +917,22 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -865,18 +917,22 @@ function OpenCommandPaletteDialog(props: { |
| 865 | ); | 917 | ); |
| 866 | const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); | 918 | const recentThreadItems = allThreadItems.slice(0, RECENT_THREAD_LIMIT); |
| 867 | | 919 | |
| 868 | function pushPaletteView(view: CommandPaletteView): void { | 920 | const pushPaletteView = useCallback( |
| 869 | setViewStack((previousViews) => [ | 921 | (view: CommandPaletteView): void => { |
| 870 | ...previousViews, | 922 | browseNavigation.invalidate(); |
| 871 | { | 923 | setViewStack((previousViews) => [ |
| 872 | addonIcon: view.addonIcon, | 924 | ...previousViews, |
| 873 | groups: view.groups, | 925 | { |
| 874 | ...(view.initialQuery ? { initialQuery: view.initialQuery } : {}), | 926 | addonIcon: view.addonIcon, |
| 875 | }, | 927 | groups: view.groups, |
| 876 | ]); | 928 | ...(view.initialQuery ? { initialQuery: view.initialQuery } : {}), |
| 877 | setHighlightedItemValue(null); | 929 | }, |
| 878 | setQuery(view.initialQuery ?? ""); | 930 | ]); |
| 879 | } | 931 | setHighlightedItemValue(null); |
| | 932 | setQuery(view.initialQuery ?? ""); |
| | 933 | }, |
| | 934 | [browseNavigation], |
| | 935 | ); |
| 880 | | 936 | |
| 881 | function pushView(item: CommandPaletteSubmenuItem): void { | 937 | function pushView(item: CommandPaletteSubmenuItem): void { |
| 882 | pushPaletteView({ | 938 | pushPaletteView({ |
| ... | @@ -887,6 +943,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -887,6 +943,7 @@ function OpenCommandPaletteDialog(props: { |
| 887 | } | 943 | } |
| 888 | | 944 | |
| 889 | function popView(): void { | 945 | function popView(): void { |
| | 946 | browseNavigation.invalidate(); |
| 890 | setAddProjectCloneFlow(null); | 947 | setAddProjectCloneFlow(null); |
| 891 | if (viewStack.length <= 1) { | 948 | if (viewStack.length <= 1) { |
| 892 | setAddProjectEnvironmentId(null); | 949 | setAddProjectEnvironmentId(null); |
| ... | @@ -897,6 +954,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -897,6 +954,7 @@ function OpenCommandPaletteDialog(props: { |
| 897 | } | 954 | } |
| 898 | | 955 | |
| 899 | function handleQueryChange(nextQuery: string): void { | 956 | function handleQueryChange(nextQuery: string): void { |
| | 957 | browseNavigation.invalidate(); |
| 900 | setHighlightedItemValue(null); | 958 | setHighlightedItemValue(null); |
| 901 | setQuery(nextQuery); | 959 | setQuery(nextQuery); |
| 902 | if (nextQuery === "" && currentView?.initialQuery) { | 960 | if (nextQuery === "" && currentView?.initialQuery) { |
| ... | @@ -905,16 +963,35 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -905,16 +963,35 @@ function OpenCommandPaletteDialog(props: { |
| 905 | } | 963 | } |
| 906 | | 964 | |
| 907 | const startAddProjectBrowse = useCallback( | 965 | const startAddProjectBrowse = useCallback( |
| 908 | (environmentId: EnvironmentId): void => { | 966 | async (environmentId: EnvironmentId): Promise<void> => { |
| 909 | setAddProjectEnvironmentId(environmentId); | 967 | const initialQuery = getAddProjectInitialQueryForEnvironment(environmentId); |
| 910 | setAddProjectCloneFlow(null); | 968 | const initialBrowsePath = getBrowseDirectoryPath(initialQuery); |
| 911 | pushPaletteView({ | 969 | const browseCwd = getBrowseCwdForEnvironment(environmentId); |
| | 970 | const view: CommandPaletteView = { |
| 912 | addonIcon: <FolderPlusIcon className={ADDON_ICON_CLASS} />, | 971 | addonIcon: <FolderPlusIcon className={ADDON_ICON_CLASS} />, |
| 913 | groups: [], | 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 | const startAddProjectClone = useCallback( | 997 | const startAddProjectClone = useCallback( |
| ... | @@ -927,7 +1004,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -927,7 +1004,7 @@ function OpenCommandPaletteDialog(props: { |
| 927 | initialQuery: "", | 1004 | initialQuery: "", |
| 928 | }); | 1005 | }); |
| 929 | }, | 1006 | }, |
| 930 | [], | 1007 | [pushPaletteView], |
| 931 | ); | 1008 | ); |
| 932 | | 1009 | |
| 933 | const openSourceControlSettings = useCallback(() => { | 1010 | const openSourceControlSettings = useCallback(() => { |
| ... | @@ -950,7 +1027,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -950,7 +1027,7 @@ function OpenCommandPaletteDialog(props: { |
| 950 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, | 1027 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, |
| 951 | keepOpen: true, | 1028 | keepOpen: true, |
| 952 | run: async () => { | 1029 | run: async () => { |
| 953 | startAddProjectBrowse(environmentId); | 1030 | await startAddProjectBrowse(environmentId); |
| 954 | }, | 1031 | }, |
| 955 | }, | 1032 | }, |
| 956 | ]; | 1033 | ]; |
| ... | @@ -1043,7 +1120,12 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -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 | const addProjectEnvironmentItems: CommandPaletteActionItem[] = addProjectEnvironmentOptions.map( | 1131 | const addProjectEnvironmentItems: CommandPaletteActionItem[] = addProjectEnvironmentOptions.map( |
| ... | @@ -1098,6 +1180,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1098,6 +1180,7 @@ function OpenCommandPaletteDialog(props: { |
| 1098 | addProjectEnvironmentGroups, | 1180 | addProjectEnvironmentGroups, |
| 1099 | addProjectEnvironmentOptions.length, | 1181 | addProjectEnvironmentOptions.length, |
| 1100 | defaultAddProjectEnvironmentId, | 1182 | defaultAddProjectEnvironmentId, |
| | 1183 | pushPaletteView, |
| 1101 | startAddProjectSourceSelection, | 1184 | startAddProjectSourceSelection, |
| 1102 | ]); | 1185 | ]); |
| 1103 | | 1186 | |
| ... | @@ -1114,6 +1197,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1114,6 +1197,7 @@ function OpenCommandPaletteDialog(props: { |
| 1114 | return; | 1197 | return; |
| 1115 | } | 1198 | } |
| 1116 | clearOpenIntent(); | 1199 | clearOpenIntent(); |
| | 1200 | browseNavigation.invalidate(); |
| 1117 | setAddProjectCloneFlow(null); | 1201 | setAddProjectCloneFlow(null); |
| 1118 | setViewStack([]); | 1202 | setViewStack([]); |
| 1119 | setQuery(""); | 1203 | setQuery(""); |
| ... | @@ -1139,10 +1223,12 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1139,10 +1223,12 @@ function OpenCommandPaletteDialog(props: { |
| 1139 | }); | 1223 | }); |
| 1140 | }, [ | 1224 | }, [ |
| 1141 | clearOpenIntent, | 1225 | clearOpenIntent, |
| | 1226 | browseNavigation, |
| 1142 | currentProjectEnvironmentId, | 1227 | currentProjectEnvironmentId, |
| 1143 | currentProjectId, | 1228 | currentProjectId, |
| 1144 | openIntent, | 1229 | openIntent, |
| 1145 | projectThreadItems, | 1230 | projectThreadItems, |
| | 1231 | pushPaletteView, |
| 1146 | ]); | 1232 | ]); |
| 1147 | | 1233 | |
| 1148 | const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = []; | 1234 | const actionItems: Array<CommandPaletteActionItem | CommandPaletteSubmenuItem> = []; |
| ... | @@ -1225,7 +1311,7 @@ function OpenCommandPaletteDialog(props: { | ... | @@ -1225,7 +1311,7 @@ function OpenCommandPaletteDialog(props: { |
| 1225 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, | 1311 | icon: <FolderPlusIcon className={ITEM_ICON_CLASS} />, |
| 1226 | keepOpen: true, | 1312 | keepOpen: true, |
| 1227 | run: async () => { | 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,23 +1627,36 @@ function OpenCommandPaletteDialog(props: { |
| 1541 | await handleAddProject(cloneResult.value.cwd); | 1627 | await handleAddProject(cloneResult.value.cwd); |
| 1542 | } | 1628 | } |
| 1543 | | 1629 | |
| 1544 | function browseTo(name: string): void { | 1630 | const browseTo = useCallback( |
| 1545 | const nextQuery = appendBrowsePathSegment(query, name); | 1631 | async (name: string): Promise<void> => { |
| 1546 | setHighlightedItemValue(null); | 1632 | const nextQuery = appendBrowsePathSegment(query, name); |
| 1547 | setQuery(nextQuery); | 1633 | await browseNavigation.run({ |
| 1548 | setBrowseGeneration((generation) => generation + 1); | 1634 | load: () => prefetchBrowsePath(getBrowseDirectoryPath(nextQuery)), |
| 1549 | } | 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 | const parentPath = getBrowseParentPath(query); | 1646 | const parentPath = getBrowseParentPath(query); |
| 1553 | if (parentPath === null) { | 1647 | if (parentPath === null) { |
| 1554 | return; | 1648 | return; |
| 1555 | } | 1649 | } |
| 1556 | | 1650 | |
| 1557 | setHighlightedItemValue(null); | 1651 | await browseNavigation.run({ |
| 1558 | setQuery(parentPath); | 1652 | load: () => prefetchBrowsePath(parentPath), |
| 1559 | setBrowseGeneration((generation) => generation + 1); | 1653 | commit: () => { |
| 1560 | } | 1654 | setHighlightedItemValue(null); |
| | 1655 | setQuery(parentPath); |
| | 1656 | setBrowseGeneration((generation) => generation + 1); |
| | 1657 | }, |
| | 1658 | }); |
| | 1659 | }, [browseNavigation, prefetchBrowsePath, query]); |
| 1561 | | 1660 | |
| 1562 | // Resolve the add-project path from browse data when available. When the | 1661 | // Resolve the add-project path from browse data when available. When the |
| 1563 | // query has a trailing separator (e.g. "~/projects/foo/"), parentPath is the | 1662 | // query has a trailing separator (e.g. "~/projects/foo/"), parentPath is the |