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 {