Skip to content

Commit 1b2aad6

Browse files
Add SSH folder to NTM for dynamic SSH profiles (#19239)
Automatically generates an "SSH" folder in the new tab menu that contains all profiles generated by the SSH profile generator. This folder is created if the SSH generator created some profiles and the folder hasn't been created before. Detecting if the folder was generated is done via the new `bool ApplicationState::SSHFolderGenerated`. The logic is similar to `SettingsLoader::DisableDeletedProfiles()`. Found a bug on new tab menu's folder inlining feature where we were counting the number of raw entries to determine whether to inline or not. Since the folder only contained the match profiles entry, this bug made it so that the profile entries would always be inlined. The fix was very simple: count the number of _resolved_ entries instead of the raw entries. This can be pulled into its own PR and serviced, if desired. ## References and Relevant Issues #18814 #14042 ## Validation Steps Performed ✅ Existing users get an SSH folder if profiles were generated ## PR Checklist Closes #19043
1 parent abaa948 commit 1b2aad6

File tree

4 files changed

+37
-3
lines changed

4 files changed

+37
-3
lines changed

src/cascadia/TerminalApp/TerminalPage.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,7 @@ namespace winrt::TerminalApp::implementation
10691069
auto folderEntryItems = _CreateNewTabFlyoutItems(folderEntries);
10701070

10711071
// If the folder should auto-inline and there is only one item, do so.
1072-
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntries.Size() == 1)
1072+
if (folderEntry.Inlining() == FolderEntryInlining::Auto && folderEntryItems.size() == 1)
10731073
{
10741074
for (auto const& folderEntryItem : folderEntryItems)
10751075
{

src/cascadia/TerminalSettingsModel/ApplicationState.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
4141
X(FileSource::Shared, Windows::Foundation::Collections::IVector<hstring>, RecentCommands, "recentCommands") \
4242
X(FileSource::Shared, Windows::Foundation::Collections::IVector<winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage>, DismissedMessages, "dismissedMessages") \
4343
X(FileSource::Local, Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines") \
44-
X(FileSource::Local, std::unordered_set<hstring>, DismissedBadges, "dismissedBadges")
44+
X(FileSource::Local, std::unordered_set<hstring>, DismissedBadges, "dismissedBadges") \
45+
X(FileSource::Shared, bool, SSHFolderGenerated, "sshFolderGenerated", false)
4546

4647
struct WindowLayout : WindowLayoutT<WindowLayout>
4748
{

src/cascadia/TerminalSettingsModel/CascadiaSettings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
9393
void MergeFragmentIntoUserSettings(const winrt::hstring& source, const winrt::hstring& basePath, const std::string_view& content);
9494
void FinalizeLayering();
9595
bool DisableDeletedProfiles();
96+
bool AddDynamicProfileFolders();
9697
bool RemapColorSchemeForProfile(const winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Profile>& profile);
9798
bool FixupUserSettings();
9899

99100
ParsedSettings inboxSettings;
100101
ParsedSettings userSettings;
101102
std::unordered_map<hstring, winrt::com_ptr<implementation::ExtensionPackage>> extensionPackageMap;
102103
bool duplicateProfile = false;
104+
bool sshProfilesGenerated = false;
103105

104106
private:
105107
struct JsonSettings

src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,11 @@ void SettingsLoader::GenerateProfiles()
213213
auto generateProfiles = [&](const IDynamicProfileGenerator& generator) {
214214
if (!_ignoredNamespaces.contains(generator.GetNamespace()))
215215
{
216+
const auto oldProfileCount = inboxSettings.profiles.size();
216217
_executeGenerator(generator, inboxSettings.profiles);
218+
return oldProfileCount != inboxSettings.profiles.size();
217219
}
220+
return false;
218221
};
219222

220223
// Generate profiles for each generator and add them to the inbox settings.
@@ -224,7 +227,7 @@ void SettingsLoader::GenerateProfiles()
224227
generateProfiles(AzureCloudShellGenerator{});
225228
generateProfiles(VisualStudioGenerator{});
226229
#if TIL_FEATURE_DYNAMICSSHPROFILES_ENABLED
227-
generateProfiles(SshHostGenerator{});
230+
sshProfilesGenerated = generateProfiles(SshHostGenerator{});
228231
#endif
229232
}
230233

@@ -536,6 +539,33 @@ bool SettingsLoader::DisableDeletedProfiles()
536539
return newGeneratedProfiles;
537540
}
538541

542+
// Returns true if something got changed and
543+
// the settings need to be saved to disk.
544+
bool SettingsLoader::AddDynamicProfileFolders()
545+
{
546+
// Keep track of generated folders to avoid regenerating them
547+
const auto state = get_self<ApplicationState>(ApplicationState::SharedInstance());
548+
549+
// If the SSH generator is enabled, try to create an "SSH" folder with all the generated profiles
550+
if (sshProfilesGenerated && !state->SSHFolderGenerated())
551+
{
552+
SshHostGenerator sshGenerator;
553+
auto matchProfilesEntry = make_self<implementation::MatchProfilesEntry>();
554+
matchProfilesEntry->Source(hstring{ sshGenerator.GetNamespace() });
555+
556+
auto folderEntry = make_self<implementation::FolderEntry>();
557+
folderEntry->Name(L"SSH");
558+
folderEntry->Icon(MediaResource::FromString(hstring{ sshGenerator.GetIcon() }));
559+
folderEntry->Inlining(FolderEntryInlining::Auto);
560+
folderEntry->RawEntries(winrt::single_threaded_vector<Model::NewTabMenuEntry>({ *matchProfilesEntry }));
561+
562+
userSettings.globals->NewTabMenu().Append(folderEntry.as<Model::NewTabMenuEntry>());
563+
state->SSHFolderGenerated(true);
564+
return true;
565+
}
566+
return false;
567+
}
568+
539569
bool winrt::Microsoft::Terminal::Settings::Model::implementation::SettingsLoader::RemapColorSchemeForProfile(const winrt::com_ptr<winrt::Microsoft::Terminal::Settings::Model::implementation::Profile>& profile)
540570
{
541571
bool modified{ false };
@@ -1229,6 +1259,7 @@ try
12291259
// DisableDeletedProfiles returns true whenever we encountered any new generated/dynamic profiles.
12301260
// Similarly FixupUserSettings returns true, when it encountered settings that were patched up.
12311261
mustWriteToDisk |= loader.DisableDeletedProfiles();
1262+
mustWriteToDisk |= loader.AddDynamicProfileFolders();
12321263
mustWriteToDisk |= loader.FixupUserSettings();
12331264

12341265
// If this throws, the app will catch it and use the default settings.

0 commit comments

Comments
 (0)