diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index bebe9ac76bd..577903edc0c 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 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 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(); + std::unordered_map 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,89 @@ 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 directories; + while (!directory.empty()) { - // Check if there are any cached commands in this directory. - const auto& cache{ _cwdLocalSnippetsCache.lock_shared() }; + directories.push_back(directory); + auto parentPath = directory.parent_path(); + if (directory == parentPath) + { + break; + } + directory = std::move(parentPath); + } - const auto cacheIterator = cache->find(currentWorkingDirectory); - if (cacheIterator != cache->end()) + { + // Check if all the directories are already in the cache + const auto& cache{ _cwdLocalSnippetsCache.lock_shared() }; + if (std::ranges::all_of(directories, [&](auto&& dir) { return cache->contains(dir); })) { - // 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 localSnippetsMap; + for (auto rit = directories.rbegin(); rit != directories.rend(); ++rit) + { + // register snippets from cache + for (const auto& [name, snippet] : cache->at(*rit)) + { + localSnippetsMap.insert_or_assign(name, snippet); + } + } + + std::vector 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(_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 localSnippetsMap; + for (auto 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 + for (const auto& [name, snippet] : cache->at(*rit)) + { + localSnippetsMap.insert_or_assign(name, snippet); + } + } + 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 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(_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 _updateLocalSnippetCache(winrt::hstring currentWorkingDirectory); + static std::unordered_map _loadLocalSnippets(const std::filesystem::path& currentWorkingDirectory); Windows::Foundation::Collections::IMap _AvailableActionsCache{ nullptr }; Windows::Foundation::Collections::IMap _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 _ResolvedKeyToActionMapCache{ nullptr }; - til::shared_mutex>> _cwdLocalSnippetsCache{}; + til::shared_mutex>> _cwdLocalSnippetsCache{}; std::set _changeLog;