Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/cascadia/TerminalApp/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,24 @@
<data name="CloseAllDialog.Title" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
<data name="CloseTabDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="CloseTabDialog.PrimaryButtonText" xml:space="preserve">
<value>Close tab</value>
</data>
<data name="CloseTabDialog.Title" xml:space="preserve">
<value>Do you want to close this tab?</value>
</data>
<data name="ClosePaneDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="ClosePaneDialog.PrimaryButtonText" xml:space="preserve">
<value>Close pane</value>
</data>
<data name="ClosePaneDialog.Title" xml:space="preserve">
<value>Do you want to close this pane?</value>
</data>
<data name="CloseReadOnlyDialog.CloseButtonText" xml:space="preserve">
<value>Cancel</value>
</data>
Expand Down
41 changes: 41 additions & 0 deletions src/cascadia/TerminalApp/TabManagement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,25 @@ namespace winrt::TerminalApp::implementation
}
}

// Check if we should warn before closing this tab based on the
// confirmCloseOn flags (e.g. multiple panes, always, etc.)
{
const auto tabImpl = _GetTabImpl(tab);
if (tabImpl && _ShouldWarnOnCloseTab(tabImpl))
{
const auto weak = get_weak();

auto warningResult = co_await _ShowCloseTabWarningDialog();

strong = weak.get();

if (!strong || warningResult != ContentDialogResult::Primary)
{
co_return;
}
}
}

auto t = winrt::get_self<implementation::Tab>(tab);
auto actions = t->BuildStartupActions(BuildStartupKind::None);
_AddPreviouslyClosedPaneOrTab(std::move(actions));
Expand Down Expand Up @@ -782,6 +801,28 @@ namespace winrt::TerminalApp::implementation
if (const auto pane{ activeTab->GetActivePane() })
{
const auto weak = get_weak();

// Check if we should warn before closing a single pane
// (only triggers on Always — or MultipleProcesses in the future)
const auto flags = _settings.GlobalSettings().ConfirmCloseOn();
if (WI_IsFlagSet(flags, ConfirmCloseOn::Always))
{
auto warningResult = co_await _ShowClosePaneWarningDialog();

if (const auto strong = weak.get())
{
if (warningResult != ContentDialogResult::Primary)
{
co_return;
}
}
else
{
co_return;
}
Comment on lines +812 to +822
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I kinda liked the way you did it earlier in this file more

Suggested change
if (const auto strong = weak.get())
{
if (warningResult != ContentDialogResult::Primary)
{
co_return;
}
}
else
{
co_return;
}
const auto strong = weak.get();
if (!string || warningResult != ContentDialogResult::Primary)
{
co_return;
}

}
// TODO GH#6549: ConfirmCloseOn::MultipleProcesses check here

if (co_await _PaneConfirmCloseReadOnly(pane))
{
if (const auto strong = weak.get())
Expand Down
96 changes: 92 additions & 4 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,22 @@ namespace winrt::TerminalApp::implementation
return _ShowDialogHelper(L"CloseAllDialog");
}

// Method Description:
// - Displays a dialog for warnings found while closing a tab that has
// multiple panes or other conditions that warrant a warning.
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalPage::_ShowCloseTabWarningDialog()
{
return _ShowDialogHelper(L"CloseTabDialog");
}

// Method Description:
// - Displays a dialog for warnings found while closing a single pane
// (e.g. when confirmCloseOn includes "always").
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalPage::_ShowClosePaneWarningDialog()
{
return _ShowDialogHelper(L"ClosePaneDialog");
}

// Method Description:
// - Displays a dialog for warnings found while closing the terminal tab marked as read-only
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> TerminalPage::_ShowCloseReadOnlyDialog()
Expand Down Expand Up @@ -2308,12 +2324,84 @@ namespace winrt::TerminalApp::implementation
}

// Method Description:
// - Close the terminal app. If there is more
// than one tab opened, show a warning dialog.
// - Determines whether a close-window action should show a confirmation
// dialog, based on the confirmCloseOn flags and the current window state.
// Arguments:
// - <none>
// Return Value:
// - true if a warning dialog should be shown before closing the window.
bool TerminalPage::_ShouldWarnOnClose() const
{
const auto flags = _settings.GlobalSettings().ConfirmCloseOn();

// Always flag means warn unconditionally.
if (WI_IsFlagSet(flags, ConfirmCloseOn::Always))
{
return true;
}

// MultipleTabs: warn if there's more than one tab.
if (WI_IsFlagSet(flags, ConfirmCloseOn::MultipleTabs) && _HasMultipleTabs())
{
return true;
}

// MultiplePanes: warn if any tab has more than one pane.
if (WI_IsFlagSet(flags, ConfirmCloseOn::MultiplePanes))
{
for (const auto tab : _tabs)
{
if (const auto impl = _GetTabImpl(tab))
{
if (impl->GetLeafPaneCount() > 1)
{
return true;
}
}
}
}

// TODO GH#6549: ConfirmCloseOn::MultipleProcesses
// Needs TermControl to expose whether >1 client process is connected.

return false;
}

// Method Description:
// - Determines whether closing a specific tab should show a confirmation
// dialog, based on the confirmCloseOn flags and the tab's state.
// Arguments:
// - tab: The tab being closed.
// Return Value:
// - true if a warning dialog should be shown before closing the tab.
bool TerminalPage::_ShouldWarnOnCloseTab(const winrt::com_ptr<Tab>& tab) const
{
const auto flags = _settings.GlobalSettings().ConfirmCloseOn();

// Always flag means warn unconditionally.
if (WI_IsFlagSet(flags, ConfirmCloseOn::Always))
{
return true;
}

// MultiplePanes: warn if this tab has more than one pane.
if (WI_IsFlagSet(flags, ConfirmCloseOn::MultiplePanes) && tab->GetLeafPaneCount() > 1)
{
return true;
}

// TODO GH#6549: ConfirmCloseOn::MultipleProcesses
// Needs TermControl to expose whether >1 client process is connected.

return false;
}

// Method Description:
// - Close the terminal app. If the confirmCloseOn flags indicate we should
// warn for the current window state, show a warning dialog.
safe_void_coroutine TerminalPage::CloseWindow()
{
if (_HasMultipleTabs() &&
_settings.GlobalSettings().ConfirmCloseAllTabs() &&
if (_ShouldWarnOnClose() &&
!_displayingCloseDialog)
{
if (_newTabButton && _newTabButton.Flyout())
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ namespace winrt::TerminalApp::implementation
void _ShowAboutDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowQuitDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseTabWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowClosePaneWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowCloseReadOnlyDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowMultiLinePasteWarningDialog();
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowLargePasteWarningDialog();
Expand Down Expand Up @@ -400,6 +402,8 @@ namespace winrt::TerminalApp::implementation
TerminalApp::Tab _GetTabByTabViewItem(const IInspectable& tabViewItem) const noexcept;

void _HandleClosePaneRequested(std::shared_ptr<Pane> pane);
bool _ShouldWarnOnClose() const;
bool _ShouldWarnOnCloseTab(const winrt::com_ptr<Tab>& tab) const;
safe_void_coroutine _SetFocusedTab(const winrt::TerminalApp::Tab tab);
safe_void_coroutine _CloseFocusedPane();
void _ClosePanes(weak_ref<Tab> weakTab, std::vector<uint32_t> paneIds);
Expand Down
12 changes: 12 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@
x:Load="False"
DefaultButton="Primary" />

<ContentDialog x:Name="CloseTabDialog"
x:Uid="CloseTabDialog"
Grid.Row="2"
x:Load="False"
DefaultButton="Primary" />

<ContentDialog x:Name="ClosePaneDialog"
x:Uid="ClosePaneDialog"
Grid.Row="2"
x:Load="False"
DefaultButton="Primary" />

<ContentDialog x:Name="CloseReadOnlyDialog"
x:Uid="CloseReadOnlyDialog"
Grid.Row="2"
Expand Down
18 changes: 13 additions & 5 deletions src/cascadia/TerminalSettingsEditor/Interaction.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,19 @@
<TextBlock x:Uid="Globals_WarningsHeader"
Style="{StaticResource TextBlockSubHeaderStyle}" />

<!-- Close All Tabs Warning -->
<local:SettingContainer x:Name="ConfirmCloseAllTabs"
x:Uid="Globals_ConfirmCloseAllTabs">
<ToggleSwitch IsOn="{x:Bind ViewModel.ConfirmCloseAllTabs, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
<!-- Confirm Close On -->
<local:SettingContainer x:Name="ConfirmCloseOn"
x:Uid="Globals_ConfirmCloseOn"
CurrentValue="{x:Bind ViewModel.ConfirmCloseOnPreview, Mode=OneWay}"
Style="{StaticResource ExpanderSettingContainerStyle}">
<StackPanel>
<CheckBox x:Uid="Globals_ConfirmCloseOnAlways"
IsChecked="{x:Bind ViewModel.IsConfirmCloseOnFlagSet(1), BindBack=ViewModel.SetConfirmCloseOnAlways, Mode=TwoWay}" />
<CheckBox x:Uid="Globals_ConfirmCloseOnMultipleTabs"
IsChecked="{x:Bind ViewModel.IsConfirmCloseOnFlagSet(2), BindBack=ViewModel.SetConfirmCloseOnMultipleTabs, Mode=TwoWay}" />
<CheckBox x:Uid="Globals_ConfirmCloseOnMultiplePanes"
IsChecked="{x:Bind ViewModel.IsConfirmCloseOnFlagSet(4), BindBack=ViewModel.SetConfirmCloseOnMultiplePanes, Mode=TwoWay}" />
</StackPanel>
</local:SettingContainer>

<!-- Input Service Warning -->
Expand Down
64 changes: 64 additions & 0 deletions src/cascadia/TerminalSettingsEditor/InteractionViewModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,68 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
INITIALIZE_BINDABLE_ENUM_SETTING(TabSwitcherMode, TabSwitcherMode, TabSwitcherMode, L"Globals_TabSwitcherMode", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(CopyFormat, CopyFormat, winrt::Microsoft::Terminal::Control::CopyFormat, L"Globals_CopyFormat", L"Content");
}

hstring InteractionViewModel::ConfirmCloseOnPreview() const
{
const auto flags = ConfirmCloseOn();
if (WI_IsFlagSet(flags, Model::ConfirmCloseOn::Always))
{
return RS_(L"Globals_ConfirmCloseOnAlways/Content");
}
else if (flags == Model::ConfirmCloseOn::Never)
{
return RS_(L"Globals_ConfirmCloseOnNever");
}

std::vector<hstring> resultList;
resultList.reserve(2);
if (WI_IsFlagSet(flags, Model::ConfirmCloseOn::MultipleTabs))
{
resultList.emplace_back(RS_(L"Globals_ConfirmCloseOnMultipleTabs/Content"));
}
if (WI_IsFlagSet(flags, Model::ConfirmCloseOn::MultiplePanes))
{
resultList.emplace_back(RS_(L"Globals_ConfirmCloseOnMultiplePanes/Content"));
}

hstring result{};
for (auto&& entry : resultList)
{
if (result.empty())
{
result = entry;
}
else
{
result = result + L", " + entry;
}
}
return result;
}

bool InteractionViewModel::IsConfirmCloseOnFlagSet(const uint32_t flag)
{
return (WI_EnumValue(ConfirmCloseOn()) & flag) == flag;
}

void InteractionViewModel::SetConfirmCloseOnAlways(winrt::Windows::Foundation::IReference<bool> on)
{
auto current = ConfirmCloseOn();
WI_UpdateFlag(current, Model::ConfirmCloseOn::Always, winrt::unbox_value<bool>(on));
ConfirmCloseOn(current);
}

void InteractionViewModel::SetConfirmCloseOnMultipleTabs(winrt::Windows::Foundation::IReference<bool> on)
{
auto current = ConfirmCloseOn();
WI_UpdateFlag(current, Model::ConfirmCloseOn::MultipleTabs, winrt::unbox_value<bool>(on));
ConfirmCloseOn(current);
}

void InteractionViewModel::SetConfirmCloseOnMultiplePanes(winrt::Windows::Foundation::IReference<bool> on)
{
auto current = ConfirmCloseOn();
WI_UpdateFlag(current, Model::ConfirmCloseOn::MultiplePanes, winrt::unbox_value<bool>(on));
ConfirmCloseOn(current);
}
Comment on lines +65 to +84
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated preview doesn't get displayed when you call any of these.

Easy fix though, just add this:
_NotifyChanges(L"ConfirmCloseOnPreview")

}
7 changes: 6 additions & 1 deletion src/cascadia/TerminalSettingsEditor/InteractionViewModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, DetectURLs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, SearchWebDefaultQueryUrl);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, WordDelimiters);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ConfirmCloseAllTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ConfirmCloseOn);
hstring ConfirmCloseOnPreview() const;
bool IsConfirmCloseOnFlagSet(const uint32_t flag);
void SetConfirmCloseOnAlways(winrt::Windows::Foundation::IReference<bool> on);
void SetConfirmCloseOnMultipleTabs(winrt::Windows::Foundation::IReference<bool> on);
void SetConfirmCloseOnMultiplePanes(winrt::Windows::Foundation::IReference<bool> on);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, InputServiceWarning);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, WarnAboutLargePaste);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, WarnAboutMultiLinePaste);
Expand Down
7 changes: 6 additions & 1 deletion src/cascadia/TerminalSettingsEditor/InteractionViewModel.idl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ namespace Microsoft.Terminal.Settings.Editor
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, DetectURLs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(String, SearchWebDefaultQueryUrl);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(String, WordDelimiters);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ConfirmCloseAllTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Settings.Model.ConfirmCloseOn, ConfirmCloseOn);
String ConfirmCloseOnPreview { get; };
Boolean IsConfirmCloseOnFlagSet(UInt32 flag);
void SetConfirmCloseOnAlways(Windows.Foundation.IReference<Boolean> on);
void SetConfirmCloseOnMultipleTabs(Windows.Foundation.IReference<Boolean> on);
void SetConfirmCloseOnMultiplePanes(Windows.Foundation.IReference<Boolean> on);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, InputServiceWarning);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, WarnAboutLargePaste);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Control.WarnAboutMultiLinePaste, WarnAboutMultiLinePaste);
Expand Down
20 changes: 20 additions & 0 deletions src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -2197,6 +2197,26 @@
<value>Warn when closing more than one tab</value>
<comment>Header for a control to toggle whether to show a confirm dialog box when closing the application with multiple tabs open.</comment>
</data>
<data name="Globals_ConfirmCloseOn.Header" xml:space="preserve">
<value>Confirm before closing</value>
<comment>Header for a group of checkboxes controlling when to show a confirmation dialog before closing.</comment>
</data>
<data name="Globals_ConfirmCloseOnAlways.Content" xml:space="preserve">
<value>Always</value>
<comment>Checkbox label: always confirm before closing a window, tab, or pane.</comment>
</data>
<data name="Globals_ConfirmCloseOnMultipleTabs.Content" xml:space="preserve">
<value>Multiple tabs</value>
<comment>Checkbox label: confirm before closing a window with more than one tab.</comment>
</data>
<data name="Globals_ConfirmCloseOnMultiplePanes.Content" xml:space="preserve">
<value>Multiple panes</value>
<comment>Checkbox label: confirm before closing a tab with more than one pane.</comment>
</data>
<data name="Globals_ConfirmCloseOnNever" xml:space="preserve">
<value>Never</value>
<comment>Preview text shown when no confirm-close-on flags are set.</comment>
</data>
<data name="Globals_InputServiceWarning.Header" xml:space="preserve">
<value>Warn when "Touch Keyboard and Handwriting Panel Service" is disabled</value>
</data>
Expand Down
11 changes: 10 additions & 1 deletion src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,16 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyInputServiceWarningKey, _InputServiceWarning) || _fixupsAppliedDuringLoad;
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutLargePasteKey, _WarnAboutLargePaste) || _fixupsAppliedDuringLoad;
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyWarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste) || _fixupsAppliedDuringLoad;
_fixupsAppliedDuringLoad = JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, _ConfirmCloseAllTabs) || _fixupsAppliedDuringLoad;
// GH#6549 - Migrate legacy "confirmCloseAllTabs" boolean to the new
// "confirmCloseOn" flags enum. true -> MultipleTabs|MultiplePanes, false -> Never.
{
std::optional<bool> legacyConfirmClose;
if (JsonUtils::GetValueForKey(json, LegacyConfirmCloseAllTabsKey, legacyConfirmClose))
{
_ConfirmCloseOn = legacyConfirmClose.value() ? winrt::Microsoft::Terminal::Settings::Model::ConfirmCloseOn::MultipleTabs | winrt::Microsoft::Terminal::Settings::Model::ConfirmCloseOn::MultiplePanes : winrt::Microsoft::Terminal::Settings::Model::ConfirmCloseOn::Never;
_fixupsAppliedDuringLoad = true;
}
}

#define GLOBAL_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
JsonUtils::GetValueForKey(json, jsonKey, _##name); \
Expand Down
Loading