From ea45ab5df2560e053b276e45785b5eacb284fd19 Mon Sep 17 00:00:00 2001
From: Carlos Zamora <cazamor@microsoft.com>
Date: Tue, 13 May 2025 13:56:07 -0700
Subject: [PATCH 1/2] Load .wt.json snippets from parent directories

---
 .../TerminalSettingsModel/ActionMap.cpp       | 100 ++++++++++++++----
 .../TerminalSettingsModel/ActionMap.h         |   4 +-
 2 files changed, 83 insertions(+), 21 deletions(-)

diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp
index bebe9ac76bd..805dec35b95 100644
--- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp
@@ -916,14 +916,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         AddAction(*cmd, keys);
     }
 
-    // Update ActionMap's cache of actions for this directory. We'll look for a
-    // .wt.json in this directory. If it exists, we'll read it, parse it's JSON,
-    // then take all the sendInput actions in it and store them in our
-    // _cwdLocalSnippetsCache
-    std::vector<Model::Command> ActionMap::_updateLocalSnippetCache(winrt::hstring currentWorkingDirectory)
+    // Look for a .wt.json file in the given directory. If it exists,
+    // read it, parse it's JSON, and retrieve all the sendInput actions.
+    std::unordered_map<hstring, Model::Command> ActionMap::_loadLocalSnippets(const std::filesystem::path& currentWorkingDirectory)
     {
         // This returns an empty string if we fail to load the file.
-        std::filesystem::path localSnippetsPath{ std::wstring_view{ currentWorkingDirectory + L"\\.wt.json" } };
+        std::filesystem::path localSnippetsPath = currentWorkingDirectory / std::filesystem::path{ ".wt.json" };
         const auto data = til::io::read_file_as_utf8_string_if_exists(localSnippetsPath);
         if (data.empty())
         {
@@ -943,12 +941,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
             return {};
         }
 
-        auto result = std::vector<Model::Command>();
+        std::unordered_map<hstring, Model::Command> result;
         if (auto actions{ root[JsonKey("snippets")] })
         {
             for (const auto& json : actions)
             {
-                result.push_back(*Command::FromSnippetJson(json));
+                const auto snippet = Command::FromSnippetJson(json);
+                result.insert_or_assign(snippet->Name(), *snippet);
             }
         }
         return result;
@@ -958,34 +957,97 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         winrt::hstring currentCommandline,
         winrt::hstring currentWorkingDirectory)
     {
+        // enumerate all the parent directories we want to import snippets from
+        std::filesystem::path directory{ std::wstring_view{ currentWorkingDirectory } };
+        std::vector<std::filesystem::path> directories;
+        while (!directory.empty())
         {
-            // Check if there are any cached commands in this directory.
+            directories.push_back(directory);
+            const auto parentPath = directory.parent_path();
+            if (directory == parentPath)
+            {
+                break;
+            }
+            directory = parentPath;
+        }
+
+        {
+            // Check if all the directories are already in the cache
             const auto& cache{ _cwdLocalSnippetsCache.lock_shared() };
 
-            const auto cacheIterator = cache->find(currentWorkingDirectory);
-            if (cacheIterator != cache->end())
+            bool allSnippetsCached = true;
+            for (const auto& dir : directories)
+            {
+                if (!cache->contains(dir))
+                {
+                    allSnippetsCached = false;
+                    break;
+                }
+            }
+            if (allSnippetsCached)
             {
-                // We found something in the cache! return it.
+                // Load snippets from directories in reverse order.
+                // This ensures that we prioritize snippets closer to the cwd.
+                // The map makes it easy to avoid duplicates.
+                std::unordered_map<hstring, Model::Command> localSnippetsMap;
+                for (decltype(directories)::reverse_iterator rit = directories.rbegin(); rit != directories.rend(); ++rit)
+                {
+                    // register snippets from cache
+                    std::ranges::for_each(cache->at(*rit), [&localSnippetsMap](const auto& kvPair) {
+                        localSnippetsMap.insert_or_assign(kvPair.first, kvPair.second);
+                    });
+                }
+
+                std::vector<Model::Command> localSnippets;
+                localSnippets.reserve(localSnippetsMap.size());
+                std::ranges::transform(localSnippetsMap,
+                                       std::back_inserter(localSnippets),
+                                       [](const auto& kvPair) { return kvPair.second; });
                 co_return winrt::single_threaded_vector<Model::Command>(_filterToSnippets(NameMap(),
                                                                                           currentCommandline,
-                                                                                          cacheIterator->second));
+                                                                                          localSnippets));
             }
         } // release the lock on the cache
 
         // Don't do I/O on the main thread
         co_await winrt::resume_background();
 
-        auto result = _updateLocalSnippetCache(currentWorkingDirectory);
-        if (!result.empty())
+        // Load snippets from directories in reverse order.
+        // This ensures that we prioritize snippets closer to the cwd.
+        // The map makes it easy to avoid duplicates.
+        const auto& cache{ _cwdLocalSnippetsCache.lock() };
+        std::unordered_map<hstring, Model::Command> localSnippetsMap;
+        for (decltype(directories)::reverse_iterator rit = directories.rbegin(); rit != directories.rend(); ++rit)
         {
-            // We found something! Add it to the cache
-            auto cache{ _cwdLocalSnippetsCache.lock() };
-            cache->insert_or_assign(currentWorkingDirectory, result);
+            const auto& dir = *rit;
+            if (const auto cacheIterator = cache->find(dir); cacheIterator != cache->end())
+            {
+                // register snippets from cache
+                std::ranges::for_each(cacheIterator->second, [&localSnippetsMap](const auto& kvPair) {
+                    localSnippetsMap.insert_or_assign(kvPair.first, kvPair.second);
+                });
+            }
+            else
+            {
+                // we don't have this directory in the cache, so we need to load it
+                auto result = _loadLocalSnippets(dir);
+                cache->insert_or_assign(dir, result);
+
+                // register snippets from cache
+                std::ranges::for_each(result, [&localSnippetsMap](const auto& kvPair) {
+                    localSnippetsMap.insert_or_assign(kvPair.first, kvPair.second);
+                });
+            }
         }
 
+        std::vector<Model::Command> localSnippets;
+        localSnippets.reserve(localSnippetsMap.size());
+        std::ranges::transform(localSnippetsMap,
+                               std::back_inserter(localSnippets),
+                               [](const auto& kvPair) { return kvPair.second; });
         co_return winrt::single_threaded_vector<Model::Command>(_filterToSnippets(NameMap(),
                                                                                   currentCommandline,
-                                                                                  result));
+                                                                                  localSnippets));
     }
 #pragma endregion
 }
diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h
index 8da139a30d8..c0a6e4ac46a 100644
--- a/src/cascadia/TerminalSettingsModel/ActionMap.h
+++ b/src/cascadia/TerminalSettingsModel/ActionMap.h
@@ -103,7 +103,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         void _TryUpdateActionMap(const Model::Command& cmd);
         void _TryUpdateKeyChord(const Model::Command& cmd, const Control::KeyChord& keys);
 
-        std::vector<Model::Command> _updateLocalSnippetCache(winrt::hstring currentWorkingDirectory);
+        static std::unordered_map<hstring, Model::Command> _loadLocalSnippets(const std::filesystem::path& currentWorkingDirectory);
 
         Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
         Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
@@ -137,7 +137,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         // we can give the SUI a view of the key chords and the commands they map to
         Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _ResolvedKeyToActionMapCache{ nullptr };
 
-        til::shared_mutex<std::unordered_map<hstring, std::vector<Model::Command>>> _cwdLocalSnippetsCache{};
+        til::shared_mutex<std::unordered_map<std::filesystem::path, std::unordered_map<hstring, Model::Command>>> _cwdLocalSnippetsCache{};
 
         std::set<std::string> _changeLog;
 

From 0602e84b2473f6faa68bf813083667d2133d33dc Mon Sep 17 00:00:00 2001
From: Carlos Zamora <cazamor@microsoft.com>
Date: Tue, 13 May 2025 17:05:19 -0700
Subject: [PATCH 2/2] apply feedback from Leonard

---
 .../TerminalSettingsModel/ActionMap.cpp       | 34 +++++++------------
 1 file changed, 13 insertions(+), 21 deletions(-)

diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp
index 805dec35b95..577903edc0c 100644
--- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp
+++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp
@@ -963,39 +963,30 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         while (!directory.empty())
         {
             directories.push_back(directory);
-            const auto parentPath = directory.parent_path();
+            auto parentPath = directory.parent_path();
             if (directory == parentPath)
             {
                 break;
             }
-            directory = parentPath;
+            directory = std::move(parentPath);
         }
 
         {
             // Check if all the directories are already in the cache
             const auto& cache{ _cwdLocalSnippetsCache.lock_shared() };
-
-            bool allSnippetsCached = true;
-            for (const auto& dir : directories)
-            {
-                if (!cache->contains(dir))
-                {
-                    allSnippetsCached = false;
-                    break;
-                }
-            }
-            if (allSnippetsCached)
+            if (std::ranges::all_of(directories, [&](auto&& dir) { return cache->contains(dir); }))
             {
                 // Load snippets from directories in reverse order.
                 // This ensures that we prioritize snippets closer to the cwd.
                 // The map makes it easy to avoid duplicates.
                 std::unordered_map<hstring, Model::Command> localSnippetsMap;
-                for (decltype(directories)::reverse_iterator rit = directories.rbegin(); rit != directories.rend(); ++rit)
+                for (auto rit = directories.rbegin(); rit != directories.rend(); ++rit)
                 {
                     // register snippets from cache
-                    std::ranges::for_each(cache->at(*rit), [&localSnippetsMap](const auto& kvPair) {
-                        localSnippetsMap.insert_or_assign(kvPair.first, kvPair.second);
-                    });
+                    for (const auto& [name, snippet] : cache->at(*rit))
+                    {
+                        localSnippetsMap.insert_or_assign(name, snippet);
+                    }
                 }
 
                 std::vector<Model::Command> localSnippets;
@@ -1017,15 +1008,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         // The map makes it easy to avoid duplicates.
         const auto& cache{ _cwdLocalSnippetsCache.lock() };
         std::unordered_map<hstring, Model::Command> localSnippetsMap;
-        for (decltype(directories)::reverse_iterator rit = directories.rbegin(); rit != directories.rend(); ++rit)
+        for (auto rit = directories.rbegin(); rit != directories.rend(); ++rit)
         {
             const auto& dir = *rit;
             if (const auto cacheIterator = cache->find(dir); cacheIterator != cache->end())
             {
                 // register snippets from cache
-                std::ranges::for_each(cacheIterator->second, [&localSnippetsMap](const auto& kvPair) {
-                    localSnippetsMap.insert_or_assign(kvPair.first, kvPair.second);
-                });
+                for (const auto& [name, snippet] : cache->at(*rit))
+                {
+                    localSnippetsMap.insert_or_assign(name, snippet);
+                }
             }
             else
             {