Skip to content

Commit

Permalink
Retain (and indicate) orphaned dynamic profiles (microsoft#18188)
Browse files Browse the repository at this point in the history
The original intent with dynamic profiles was that they could be
uninstalled but that Terminal would remember your settings in case they
ever came back.

After we implemented dynamic profile _deletion_, however, we
accidentally made it so that saving your settings after a dynamic
profile disappeared scoured it from the planet _forever_ (since we
remembered that we generated it, but now it was no longer in the
settings file).

This pull request implements:

- Tracking for orphaned dynamic profiles
- A new settings page for the profile that explains what happened
- Badging on the Navigation Menu indicating which profiles are orphaned
and which are hidden

Closes microsoft#14061
Closes microsoft#11510 
Refs microsoft#13916 
Refs microsoft#9997
  • Loading branch information
DHowett authored Nov 15, 2024
1 parent a8e83c1 commit 90866c7
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 4 deletions.
33 changes: 33 additions & 0 deletions src/cascadia/TerminalSettingsEditor/MainPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation

_SetupProfileEventHandling(profile);

if (profile.Orphaned())
{
contentFrame().Navigate(xaml_typename<Editor::Profiles_Base_Orphaned>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
const auto crumb = winrt::make<Breadcrumb>(box_value(profile), profile.Name(), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
profile.CurrentPage(ProfileSubPage::Base);
return;
}

contentFrame().Navigate(xaml_typename<Editor::Profiles_Base>(), winrt::make<implementation::NavigateToProfileArgs>(profile, *this));
const auto crumb = winrt::make<Breadcrumb>(box_value(profile), profile.Name(), BreadcrumbSubPage::None);
_breadcrumbs.Append(crumb);
Expand Down Expand Up @@ -621,13 +630,33 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_Navigate(profileViewModel, BreadcrumbSubPage::None);
}

static MUX::Controls::InfoBadge _createGlyphIconBadge(wil::zwstring_view glyph)
{
MUX::Controls::InfoBadge badge;
MUX::Controls::FontIconSource icon;
icon.FontFamily(winrt::Windows::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" });
icon.FontSize(12);
icon.Glyph(glyph);
badge.IconSource(icon);
return badge;
}

MUX::Controls::NavigationViewItem MainPage::_CreateProfileNavViewItem(const Editor::ProfileViewModel& profile)
{
MUX::Controls::NavigationViewItem profileNavItem;
profileNavItem.Content(box_value(profile.Name()));
profileNavItem.Tag(box_value<Editor::ProfileViewModel>(profile));
profileNavItem.Icon(UI::IconPathConverter::IconWUX(profile.EvaluatedIcon()));

if (profile.Orphaned())
{
profileNavItem.InfoBadge(_createGlyphIconBadge(L"\xE7BA") /* Warning Triangle */);
}
else if (profile.Hidden())
{
profileNavItem.InfoBadge(_createGlyphIconBadge(L"\xED1A") /* Hide */);
}

// Update the menu item when the icon/name changes
auto weakMenuItem{ make_weak(profileNavItem) };
profile.PropertyChanged([weakMenuItem](const auto&, const WUX::Data::PropertyChangedEventArgs& args) {
Expand All @@ -642,6 +671,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
menuItem.Content(box_value(tag.Name()));
}
else if (args.PropertyName() == L"Hidden")
{
menuItem.InfoBadge(tag.Hidden() ? _createGlyphIconBadge(L"\xED1A") /* Hide */ : nullptr);
}
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Profiles_Base_Orphaned.h">
<DependentUpon>Profiles_Base_Orphaned.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="Profiles_Advanced.h">
<DependentUpon>Profiles_Advanced.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -176,6 +180,9 @@
<Page Include="Profiles_Base.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Profiles_Base_Orphaned.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="Profiles_Advanced.xaml">
<SubType>Designer</SubType>
</Page>
Expand Down Expand Up @@ -269,6 +276,10 @@
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="Profiles_Base_Orphaned.cpp">
<DependentUpon>Profiles_Base_Orphaned.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="Profiles_Advanced.cpp">
<DependentUpon>Profiles_Advanced.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down Expand Up @@ -354,6 +365,10 @@
<DependentUpon>Profiles_Base.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="Profiles_Base_Orphaned.idl">
<DependentUpon>Profiles_Base_Orphaned.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="Profiles_Advanced.idl">
<DependentUpon>Profiles_Advanced.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return !IsBaseLayer();
}

bool ProfileViewModel::Orphaned() const
{
return _profile.Orphaned();
}

Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance()
{
return _defaultAppearanceViewModel;
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool AutoMarkPromptsAvailable() const noexcept;
bool RepositionCursorWithMouseAvailable() const noexcept;

bool Orphaned() const;

til::typed_event<Editor::ProfileViewModel, Editor::DeleteProfileEventArgs> DeleteProfileRequested;

VIEW_MODEL_OBSERVABLE_PROPERTY(ProfileSubPage, CurrentPage);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ namespace Microsoft.Terminal.Settings.Editor
void CreateUnfocusedAppearance();
void DeleteUnfocusedAppearance();

Boolean Orphaned { get; };
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Name);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, Guid);
OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Source);
Expand Down
50 changes: 50 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base_Orphaned.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#include "pch.h"
#include "Profiles_Base_Orphaned.h"
#include "Profiles_Base_Orphaned.g.cpp"
#include "ProfileViewModel.h"

#include <LibraryResources.h>
#include "..\WinRTUtils\inc\Utils.h"

using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Profiles_Base_Orphaned::Profiles_Base_Orphaned()
{
InitializeComponent();
Automation::AutomationProperties::SetName(DeleteButton(), RS_(L"Profile_DeleteButton/Text"));
}

void Profiles_Base_Orphaned::OnNavigatedTo(const NavigationEventArgs& e)
{
const auto args = e.Parameter().as<Editor::NavigateToProfileArgs>();
_Profile = args.Profile();

_layoutUpdatedRevoker = LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
// This event fires every time the layout changes, but it is always the last one to fire
// in any layout change chain. That gives us great flexibility in finding the right point
// at which to initialize our renderer (and our terminal).
// Any earlier than the last layout update and we may not know the terminal's starting size.

// Only let this succeed once.
_layoutUpdatedRevoker.revoke();

if (_Profile.FocusDeleteButton())
{
DeleteButton().Focus(FocusState::Programmatic);
_Profile.FocusDeleteButton(false);
}
});
}

void Profiles_Base_Orphaned::DeleteConfirmation_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/)
{
winrt::get_self<ProfileViewModel>(_Profile)->DeleteProfile();
}
}
30 changes: 30 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base_Orphaned.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

#pragma once

#include "Profiles_Base_Orphaned.g.h"
#include "ViewModelHelpers.h"
#include "Utils.h"

namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct Profiles_Base_Orphaned : public HasScrollViewer<Profiles_Base_Orphaned>, Profiles_Base_OrphanedT<Profiles_Base_Orphaned>
{
public:
Profiles_Base_Orphaned();

void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
void DeleteConfirmation_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);

WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);

private:
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
};
};

namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(Profiles_Base_Orphaned);
}
13 changes: 13 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base_Orphaned.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import "ProfileViewModel.idl";

namespace Microsoft.Terminal.Settings.Editor
{
[default_interface] runtimeclass Profiles_Base_Orphaned : Windows.UI.Xaml.Controls.Page
{
Profiles_Base_Orphaned();
ProfileViewModel Profile { get; };
}
}
59 changes: 59 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Profiles_Base_Orphaned.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<Page x:Class="Microsoft.Terminal.Settings.Editor.Profiles_Base_Orphaned"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:mtu="using:Microsoft.Terminal.UI"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">

<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="CommonResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>

<StackPanel Style="{StaticResource SettingsStackStyle}">
<!-- Delete Button -->
<local:SettingContainer x:Uid="Profile_Delete_Orphaned">
<local:SettingContainer.Content>
<Button x:Name="DeleteButton"
Click="DeleteConfirmation_Click"
Style="{StaticResource DeleteButtonStyle}">
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon FontSize="{StaticResource StandardIconSize}"
Glyph="&#xE74D;" />
<TextBlock x:Uid="Profile_DeleteButton"
Margin="10,0,0,0" />
</StackPanel>
</Button.Content>
</Button>
</local:SettingContainer.Content>
</local:SettingContainer>

<local:SettingContainer x:Uid="Profile_Name">
<local:SettingContainer.Content>
<TextBlock FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{x:Bind Profile.Name, Mode=OneTime}" />
</local:SettingContainer.Content>
</local:SettingContainer>

<local:SettingContainer x:Uid="Profile_Source_Orphaned">
<local:SettingContainer.Content>
<TextBlock FontFamily="Segoe UI, Segoe Fluent Icons, Segoe MDL2 Assets"
Style="{StaticResource SettingsPageItemDescriptionStyle}"
Text="{x:Bind Profile.Source, Mode=OneTime}" />
</local:SettingContainer.Content>
</local:SettingContainer>
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
Expand Up @@ -1961,4 +1961,16 @@
<value>Display a shield in the title bar when Windows Terminal is running as Administrator</value>
<comment>Header for a control to toggle displaying a shield in the title bar of the app. "Admin" refers to elevated sessions like "run as Admin"</comment>
</data>
</root>
<data name="Profile_Delete_Orphaned.Header" xml:space="preserve">
<value>Profile no longer detected</value>
</data>
<data name="Profile_Delete_Orphaned.HelpText" xml:space="preserve">
<value>This automatically-detected profile appears to have been uninstalled. Changes you have made to it are preserved, but it cannot be used until it has been reinstalled.</value>
</data>
<data name="Profile_Source_Orphaned.Header" xml:space="preserve">
<value>Original Source</value>
</data>
<data name="Profile_Source_Orphaned.HelpText" xml:space="preserve">
<value>Indicates the software that originally created this profile.</value>
</data>
</root>
2 changes: 1 addition & 1 deletion src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Model::CascadiaSettings CascadiaSettings::Copy() const
for (const auto& profile : targetProfiles)
{
allProfiles.emplace_back(*profile);
if (!profile->Hidden())
if (!profile->Hidden() && !profile->Orphaned())
{
activeProfiles.emplace_back(*profile);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1226,12 +1226,12 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) :
const auto& parents = profile->Parents();
if (std::none_of(parents.begin(), parents.end(), [&](const auto& parent) { return parent->Source() == source; }))
{
continue;
profile->Orphaned(true);
}
}

allProfiles.emplace_back(*profile);
if (!profile->Hidden())
if (!profile->Hidden() && !profile->Orphaned())
{
activeProfiles.emplace_back(*profile);
}
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/Profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ winrt::com_ptr<Profile> Profile::CopySettings() const
const auto defaultAppearance = AppearanceConfig::CopyAppearance(winrt::get_self<AppearanceConfig>(_DefaultAppearance), weakProfile);

profile->_Deleted = _Deleted;
profile->_Orphaned = _Orphaned;
profile->_Updates = _Updates;
profile->_Guid = _Guid;
profile->_Name = _Name;
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/Profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void Icon(const hstring& value);

WINRT_PROPERTY(bool, Deleted, false);
WINRT_PROPERTY(bool, Orphaned, false);
WINRT_PROPERTY(OriginTag, Origin, OriginTag::None);
WINRT_PROPERTY(guid, Updates);

Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalSettingsModel/Profile.idl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ namespace Microsoft.Terminal.Settings.Model

// True if the user explicitly removed this Profile from settings.json.
Boolean Deleted { get; };
// True if the user *kept* this Profile, but it disappeared from the system.
Boolean Orphaned { get; };

// Helper for magically using a commandline for an icon for a profile
// without an explicit icon.
Expand Down

0 comments on commit 90866c7

Please sign in to comment.