mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-19 03:10:49 +00:00
Merge branch 'main' into dev/lhecker/17179-endsession-persistence
This commit is contained in:
1
.github/actions/spelling/allow/apis.txt
vendored
1
.github/actions/spelling/allow/apis.txt
vendored
@@ -144,6 +144,7 @@ NCHITTEST
|
||||
NCLBUTTONDBLCLK
|
||||
NCMOUSELEAVE
|
||||
NCMOUSEMOVE
|
||||
NCPOINTERUPDATE
|
||||
NCRBUTTONDBLCLK
|
||||
NIF
|
||||
NIN
|
||||
|
||||
@@ -400,17 +400,6 @@ Microsoft::Console::ICU::unique_utext Microsoft::Console::ICU::UTextFromTextBuff
|
||||
return ut;
|
||||
}
|
||||
|
||||
Microsoft::Console::ICU::unique_uregex Microsoft::Console::ICU::CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
const auto re = uregex_open(reinterpret_cast<const char16_t*>(pattern.data()), gsl::narrow_cast<int32_t>(pattern.size()), flags, nullptr, status);
|
||||
// ICU describes the time unit as being dependent on CPU performance and "typically [in] the order of milliseconds",
|
||||
// but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms.
|
||||
uregex_setTimeLimit(re, 4096, status);
|
||||
uregex_setStackLimit(re, 4 * 1024 * 1024, status);
|
||||
return unique_uregex{ re };
|
||||
}
|
||||
|
||||
// Returns a half-open [beg,end) range given a text start and end position.
|
||||
// This function is designed to be used with uregex_start64/uregex_end64.
|
||||
til::point_span Microsoft::Console::ICU::BufferRangeFromMatch(UText* ut, URegularExpression* re)
|
||||
|
||||
@@ -9,10 +9,8 @@ class TextBuffer;
|
||||
|
||||
namespace Microsoft::Console::ICU
|
||||
{
|
||||
using unique_uregex = wistd::unique_ptr<URegularExpression, wil::function_deleter<decltype(&uregex_close), &uregex_close>>;
|
||||
using unique_utext = wil::unique_struct<UText, decltype(&utext_close), &utext_close>;
|
||||
|
||||
unique_utext UTextFromTextBuffer(const TextBuffer& textBuffer, til::CoordType rowBeg, til::CoordType rowEnd) noexcept;
|
||||
unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept;
|
||||
til::point_span BufferRangeFromMatch(UText* ut, URegularExpression* re);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "../../types/inc/CodepointWidthDetector.hpp"
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include <til/regex.h>
|
||||
#include "search.h"
|
||||
|
||||
// BODGY: Misdiagnosis in MSVC 17.11: Referencing global constants in the member
|
||||
@@ -3353,7 +3354,7 @@ std::optional<std::vector<til::point_span>> TextBuffer::SearchText(const std::ws
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const auto re = ICU::CreateRegex(needle, icuFlags, &status);
|
||||
const auto re = til::ICU::CreateRegex(needle, icuFlags, &status);
|
||||
if (status > U_ZERO_ERROR)
|
||||
{
|
||||
return std::nullopt;
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace TerminalApp
|
||||
None,
|
||||
Content,
|
||||
MovePane,
|
||||
Persist,
|
||||
PersistLayout,
|
||||
PersistAll
|
||||
};
|
||||
|
||||
runtimeclass BellEventArgs
|
||||
|
||||
@@ -944,4 +944,7 @@
|
||||
<data name="TabMoveRight" xml:space="preserve">
|
||||
<value>Move right</value>
|
||||
</data>
|
||||
<data name="InvalidRegex" xml:space="preserve">
|
||||
<value>An invalid regex was found.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1953,7 +1953,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::PersistState()
|
||||
void TerminalPage::PersistState(bool serializeBuffer)
|
||||
{
|
||||
// This method may be called for a window even if it hasn't had a tab yet or lost all of them.
|
||||
// We shouldn't persist such windows.
|
||||
@@ -1968,7 +1968,7 @@ namespace winrt::TerminalApp::implementation
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
auto t = winrt::get_self<implementation::TabBase>(tab);
|
||||
auto tabActions = t->BuildStartupActions(BuildStartupKind::Persist);
|
||||
auto tabActions = t->BuildStartupActions(serializeBuffer ? BuildStartupKind::PersistAll : BuildStartupKind::PersistLayout);
|
||||
actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end()));
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
safe_void_coroutine RequestQuit();
|
||||
safe_void_coroutine CloseWindow();
|
||||
void PersistState();
|
||||
void PersistState(bool serializeBuffer);
|
||||
|
||||
void ToggleFocusMode();
|
||||
void ToggleFullscreen();
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// "attach existing" rather than a "create"
|
||||
args.ContentId(_control.ContentId());
|
||||
break;
|
||||
case BuildStartupKind::Persist:
|
||||
case BuildStartupKind::PersistAll:
|
||||
{
|
||||
const auto connection = _control.Connection();
|
||||
const auto id = connection ? connection.SessionId() : winrt::guid{};
|
||||
@@ -156,6 +156,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BuildStartupKind::PersistLayout:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ static const std::array settingsLoadWarningsLabels{
|
||||
USES_RESOURCE(L"UnknownTheme"),
|
||||
USES_RESOURCE(L"DuplicateRemainingProfilesEntry"),
|
||||
USES_RESOURCE(L"InvalidUseOfContent"),
|
||||
USES_RESOURCE(L"InvalidRegex"),
|
||||
};
|
||||
|
||||
static_assert(settingsLoadWarningsLabels.size() == static_cast<size_t>(SettingsLoadWarnings::WARNINGS_SIZE));
|
||||
@@ -265,11 +266,11 @@ namespace winrt::TerminalApp::implementation
|
||||
AppLogic::Current()->NotifyRootInitialized();
|
||||
}
|
||||
|
||||
void TerminalWindow::PersistState()
|
||||
void TerminalWindow::PersistState(bool serializeBuffer)
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
_root->PersistState();
|
||||
_root->PersistState(serializeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void Create();
|
||||
|
||||
void PersistState();
|
||||
void PersistState(bool serializeBuffer);
|
||||
|
||||
void UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace TerminalApp
|
||||
Boolean ShouldImmediatelyHandoffToElevated();
|
||||
void HandoffToElevated();
|
||||
|
||||
void PersistState();
|
||||
void PersistState(Boolean serializeBuffer);
|
||||
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "../../buffer/out/UTextAdapter.h"
|
||||
|
||||
#include <til/hash.h>
|
||||
#include <til/regex.h>
|
||||
#include <winrt/Microsoft.Terminal.Core.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Core;
|
||||
@@ -1375,7 +1376,7 @@ struct URegularExpressionInterner
|
||||
//
|
||||
// An alternative approach would be to not make this method thread-safe and give each
|
||||
// Terminal instance its own cache. I'm not sure which approach would have been better.
|
||||
ICU::unique_uregex Intern(const std::wstring_view& pattern)
|
||||
til::ICU::unique_uregex Intern(const std::wstring_view& pattern)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
@@ -1383,14 +1384,14 @@ struct URegularExpressionInterner
|
||||
const auto guard = _lock.lock_shared();
|
||||
if (const auto it = _cache.find(pattern); it != _cache.end())
|
||||
{
|
||||
return ICU::unique_uregex{ uregex_clone(it->second.re.get(), &status) };
|
||||
return til::ICU::unique_uregex{ uregex_clone(it->second.re.get(), &status) };
|
||||
}
|
||||
}
|
||||
|
||||
// Even if the URegularExpression creation failed, we'll insert it into the cache, because there's no point in retrying.
|
||||
// (Apart from OOM but in that case this application will crash anyways in 3.. 2.. 1..)
|
||||
auto re = ICU::CreateRegex(pattern, 0, &status);
|
||||
ICU::unique_uregex clone{ uregex_clone(re.get(), &status) };
|
||||
auto re = til::ICU::CreateRegex(pattern, 0, &status);
|
||||
til::ICU::unique_uregex clone{ uregex_clone(re.get(), &status) };
|
||||
std::wstring key{ pattern };
|
||||
|
||||
const auto guard = _lock.lock_exclusive();
|
||||
@@ -1412,7 +1413,7 @@ struct URegularExpressionInterner
|
||||
private:
|
||||
struct CacheValue
|
||||
{
|
||||
ICU::unique_uregex re;
|
||||
til::ICU::unique_uregex re;
|
||||
size_t generation = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
CompatibilityViewModel::CompatibilityViewModel(Model::GlobalAppSettings globalSettings) :
|
||||
_GlobalSettings{ globalSettings }
|
||||
CompatibilityViewModel::CompatibilityViewModel(Model::CascadiaSettings settings) :
|
||||
_settings{ settings }
|
||||
{
|
||||
INITIALIZE_BINDABLE_ENUM_SETTING(TextMeasurement, TextMeasurement, winrt::Microsoft::Terminal::Control::TextMeasurement, L"Globals_TextMeasurement_", L"Text");
|
||||
}
|
||||
@@ -23,6 +23,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
return Feature_DebugModeUI::IsEnabled();
|
||||
}
|
||||
|
||||
void CompatibilityViewModel::ResetApplicationState()
|
||||
{
|
||||
_settings.ResetApplicationState();
|
||||
}
|
||||
|
||||
void CompatibilityViewModel::ResetToDefaultSettings()
|
||||
{
|
||||
_settings.ResetToDefaultSettings();
|
||||
}
|
||||
|
||||
Compatibility::Compatibility()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -32,4 +42,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
_ViewModel = e.Parameter().as<Editor::CompatibilityViewModel>();
|
||||
}
|
||||
|
||||
void Compatibility::ResetApplicationStateButton_Click(const Windows::Foundation::IInspectable& /*sender*/, const Windows::UI::Xaml::RoutedEventArgs& /*e*/)
|
||||
{
|
||||
_ViewModel.ResetApplicationState();
|
||||
ResetCacheFlyout().Hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
struct CompatibilityViewModel : CompatibilityViewModelT<CompatibilityViewModel>, ViewModelHelper<CompatibilityViewModel>
|
||||
{
|
||||
public:
|
||||
CompatibilityViewModel(Model::GlobalAppSettings globalSettings);
|
||||
CompatibilityViewModel(Model::CascadiaSettings settings);
|
||||
|
||||
bool DebugFeaturesAvailable() const noexcept;
|
||||
|
||||
// DON'T YOU DARE ADD A `WINRT_CALLBACK(PropertyChanged` TO A CLASS DERIVED FROM ViewModelHelper. Do this instead:
|
||||
using ViewModelHelper<CompatibilityViewModel>::PropertyChanged;
|
||||
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, AllowHeadless);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, DebugFeaturesEnabled);
|
||||
GETSET_BINDABLE_ENUM_SETTING(TextMeasurement, winrt::Microsoft::Terminal::Control::TextMeasurement, _GlobalSettings.TextMeasurement);
|
||||
void ResetApplicationState();
|
||||
void ResetToDefaultSettings();
|
||||
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), AllowHeadless);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_settings.GlobalSettings(), DebugFeaturesEnabled);
|
||||
GETSET_BINDABLE_ENUM_SETTING(TextMeasurement, winrt::Microsoft::Terminal::Control::TextMeasurement, _settings.GlobalSettings().TextMeasurement);
|
||||
|
||||
private:
|
||||
Model::GlobalAppSettings _GlobalSettings;
|
||||
Model::CascadiaSettings _settings;
|
||||
};
|
||||
|
||||
struct Compatibility : public HasScrollViewer<Compatibility>, CompatibilityT<Compatibility>
|
||||
@@ -33,6 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
Compatibility();
|
||||
|
||||
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
|
||||
void ResetApplicationStateButton_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e);
|
||||
|
||||
til::property_changed_event PropertyChanged;
|
||||
WINRT_OBSERVABLE_PROPERTY(Editor::CompatibilityViewModel, ViewModel, PropertyChanged.raise, nullptr);
|
||||
|
||||
@@ -9,9 +9,11 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
runtimeclass CompatibilityViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
CompatibilityViewModel(Microsoft.Terminal.Settings.Model.GlobalAppSettings globalSettings);
|
||||
CompatibilityViewModel(Microsoft.Terminal.Settings.Model.CascadiaSettings settings);
|
||||
|
||||
Boolean DebugFeaturesAvailable { get; };
|
||||
void ResetApplicationState();
|
||||
void ResetToDefaultSettings();
|
||||
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, AllowHeadless);
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, DebugFeaturesEnabled);
|
||||
|
||||
@@ -46,5 +46,47 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.DebugFeaturesEnabled, Mode=TwoWay}"
|
||||
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Reset Application State -->
|
||||
<local:SettingContainer x:Uid="Settings_ResetApplicationState">
|
||||
<Button x:Uid="Settings_ResetApplicationStateButton"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<Flyout x:Name="ResetCacheFlyout"
|
||||
FlyoutPresenterStyle="{StaticResource CustomFlyoutPresenterStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="Settings_ResetApplicationStateConfirmationMessageHeader"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<TextBlock x:Uid="Settings_ResetApplicationStateConfirmationMessageBody"
|
||||
FontWeight="Normal"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<Button x:Uid="Settings_ResetApplicationStateConfirmationButton"
|
||||
Click="ResetApplicationStateButton_Click" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</local:SettingContainer>
|
||||
|
||||
<!-- Reset to Default Settings -->
|
||||
<local:SettingContainer x:Uid="Settings_ResetToDefaultSettings">
|
||||
<Button x:Uid="Settings_ResetToDefaultSettingsButton"
|
||||
Style="{StaticResource DeleteButtonStyle}">
|
||||
<Button.Flyout>
|
||||
<Flyout FlyoutPresenterStyle="{StaticResource CustomFlyoutPresenterStyle}">
|
||||
<StackPanel>
|
||||
<TextBlock x:Uid="Settings_ResetToDefaultSettingsConfirmationMessageHeader"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<TextBlock x:Uid="Settings_ResetToDefaultSettingsConfirmationMessageBody"
|
||||
FontWeight="Normal"
|
||||
Style="{StaticResource CustomFlyoutTextStyle}" />
|
||||
<Button x:Uid="Settings_ResetToDefaultSettingsConfirmationButton"
|
||||
Click="{x:Bind ViewModel.ResetToDefaultSettings}" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</local:SettingContainer>
|
||||
|
||||
</StackPanel>
|
||||
</Page>
|
||||
|
||||
@@ -431,7 +431,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
else if (clickedItemTag == compatibilityTag)
|
||||
{
|
||||
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<CompatibilityViewModel>(_settingsClone.GlobalSettings()));
|
||||
contentFrame().Navigate(xaml_typename<Editor::Compatibility>(), winrt::make<CompatibilityViewModel>(_settingsClone));
|
||||
const auto crumb = winrt::make<Breadcrumb>(box_value(clickedItemTag), RS_(L"Nav_Compatibility/Content"), BreadcrumbSubPage::None);
|
||||
_breadcrumbs.Append(crumb);
|
||||
}
|
||||
|
||||
@@ -449,6 +449,8 @@
|
||||
FontIconGlyph=""
|
||||
Style="{StaticResource ExpanderSettingContainerStyleWithIcon}">
|
||||
<StackPanel Spacing="10">
|
||||
<HyperlinkButton x:Uid="NewTabMenu_AddMatchProfiles_Help"
|
||||
NavigateUri="https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-language-quick-reference" />
|
||||
<TextBox x:Uid="NewTabMenu_AddMatchProfiles_Name"
|
||||
Text="{x:Bind ViewModel.ProfileMatcherName, Mode=TwoWay}" />
|
||||
<TextBox x:Uid="NewTabMenu_AddMatchProfiles_Source"
|
||||
|
||||
@@ -2105,7 +2105,7 @@
|
||||
<comment>Header for a control that adds any remaining profiles to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles.HelpText" xml:space="preserve">
|
||||
<value>Add a group of profiles that match at least one of the defined properties</value>
|
||||
<value>Add a group of profiles that match at least one of the defined regex properties</value>
|
||||
<comment>Additional information for a control that adds a terminal profile matcher to the new tab menu. Presented near "NewTabMenu_AddMatchProfiles".</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddRemainingProfiles.HelpText" xml:space="preserve">
|
||||
@@ -2121,15 +2121,15 @@
|
||||
<comment>Header for a control that adds a folder to the new tab menu.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Name.Header" xml:space="preserve">
|
||||
<value>Profile name</value>
|
||||
<value>Profile name (Regex)</value>
|
||||
<comment>Header for a text box used to define a regex for the names of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Source.Header" xml:space="preserve">
|
||||
<value>Profile source</value>
|
||||
<value>Profile source (Regex)</value>
|
||||
<comment>Header for a text box used to define a regex for the sources of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Commandline.Header" xml:space="preserve">
|
||||
<value>Commandline</value>
|
||||
<value>Commandline (Regex)</value>
|
||||
<comment>Header for a text box used to define a regex for the commandlines of profiles to add.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfilesTextBlock.Text" xml:space="preserve">
|
||||
@@ -2344,6 +2344,9 @@
|
||||
<value>This option is managed by enterprise policy and cannot be changed here.</value>
|
||||
<comment>This is displayed in concordance with Globals_StartOnUserLogin if the enterprise administrator has taken control of this setting.</comment>
|
||||
</data>
|
||||
<data name="NewTabMenu_AddMatchProfiles_Help.Content" xml:space="preserve">
|
||||
<value>Learn more about regular expressions</value>
|
||||
</data>
|
||||
<data name="Appearance_BackgroundImageNone" xml:space="preserve">
|
||||
<value>None</value>
|
||||
<comment>Text displayed when the background image path is not defined.</comment>
|
||||
@@ -2352,4 +2355,37 @@
|
||||
<value>None</value>
|
||||
<comment>Text displayed when the answerback message is not defined.</comment>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Settings_ResetToDefaultSettings.Header" xml:space="preserve">
|
||||
<value>Reset to default settings</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationState.Header" xml:space="preserve">
|
||||
<value>Clear cache</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationState.HelpText" xml:space="preserve">
|
||||
<value>The cache stores data related to persisting sessions and automatic profile generation.</value>
|
||||
</data>
|
||||
<data name="Settings_ResetToDefaultSettingsButton.Content" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationStateButton.Content" xml:space="preserve">
|
||||
<value>Clear</value>
|
||||
</data>
|
||||
<data name="Settings_ResetToDefaultSettingsConfirmationMessageBody.Text" xml:space="preserve">
|
||||
<value>This action is applied immediately and cannot be undone.</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationStateConfirmationMessageBody.Text" xml:space="preserve">
|
||||
<value>This action is applied immediately and cannot be undone.</value>
|
||||
</data>
|
||||
<data name="Settings_ResetToDefaultSettingsConfirmationMessageHeader.Text" xml:space="preserve">
|
||||
<value>Are you sure you want to reset your settings?</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationStateConfirmationMessageHeader.Text" xml:space="preserve">
|
||||
<value>Are you sure you want to clear your cache?</value>
|
||||
</data>
|
||||
<data name="Settings_ResetToDefaultSettingsConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, reset my settings</value>
|
||||
</data>
|
||||
<data name="Settings_ResetApplicationStateConfirmationButton.Content" xml:space="preserve">
|
||||
<value>Yes, clear the cache</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -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,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<std::filesystem::path> directories;
|
||||
while (!directory.empty())
|
||||
{
|
||||
// Check if there are any cached commands in this directory.
|
||||
const auto& cache{ _cwdLocalSnippetsCache.lock_shared() };
|
||||
|
||||
const auto cacheIterator = cache->find(currentWorkingDirectory);
|
||||
if (cacheIterator != cache->end())
|
||||
directories.push_back(directory);
|
||||
auto parentPath = directory.parent_path();
|
||||
if (directory == parentPath)
|
||||
{
|
||||
// We found something in the cache! return it.
|
||||
break;
|
||||
}
|
||||
directory = std::move(parentPath);
|
||||
}
|
||||
|
||||
{
|
||||
// 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); }))
|
||||
{
|
||||
// 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 (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<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 (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<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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "CascadiaSettings.h"
|
||||
#include "CascadiaSettings.g.cpp"
|
||||
#include "MatchProfilesEntry.h"
|
||||
|
||||
#include "DefaultTerminal.h"
|
||||
#include "FileUtils.h"
|
||||
@@ -429,6 +430,7 @@ void CascadiaSettings::_validateSettings()
|
||||
_validateColorSchemesInCommands();
|
||||
_validateThemeExists();
|
||||
_validateProfileEnvironmentVariables();
|
||||
_validateRegexes();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -583,6 +585,41 @@ void CascadiaSettings::_validateProfileEnvironmentVariables()
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if all regexes in the new tab menu are valid, false otherwise
|
||||
static bool _validateNTMEntries(const IVector<Model::NewTabMenuEntry>& entries)
|
||||
{
|
||||
if (!entries)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (const auto& ntmEntry : entries)
|
||||
{
|
||||
if (const auto& folderEntry = ntmEntry.try_as<Model::FolderEntry>())
|
||||
{
|
||||
if (!_validateNTMEntries(folderEntry.RawEntries()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (const auto& matchProfilesEntry = ntmEntry.try_as<Model::MatchProfilesEntry>())
|
||||
{
|
||||
if (!winrt::get_self<Model::implementation::MatchProfilesEntry>(matchProfilesEntry)->ValidateRegexes())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CascadiaSettings::_validateRegexes()
|
||||
{
|
||||
if (!_validateNTMEntries(_globals->NewTabMenu()))
|
||||
{
|
||||
_warnings.Append(SettingsLoadWarnings::InvalidRegex);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper to get the GUID of a profile, given an optional index and a possible
|
||||
// "profile" value to override that.
|
||||
|
||||
@@ -127,6 +127,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> AllProfiles() const noexcept;
|
||||
winrt::Windows::Foundation::Collections::IObservableVector<Model::Profile> ActiveProfiles() const noexcept;
|
||||
Model::ActionMap ActionMap() const noexcept;
|
||||
void ResetApplicationState() const;
|
||||
void ResetToDefaultSettings();
|
||||
void WriteSettingsToDisk();
|
||||
Json::Value ToJson() const;
|
||||
Model::Profile ProfileDefaults() const;
|
||||
@@ -162,6 +164,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
winrt::com_ptr<implementation::Profile> _createNewProfile(const std::wstring_view& name) const;
|
||||
Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const;
|
||||
void _refreshDefaultTerminals();
|
||||
void _writeSettingsToDisk(std::string_view contents);
|
||||
|
||||
void _resolveDefaultProfile() const;
|
||||
void _resolveNewTabMenuProfiles() const;
|
||||
@@ -175,6 +178,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
void _validateColorSchemesInCommands() const;
|
||||
bool _hasInvalidColorScheme(const Model::Command& command) const;
|
||||
void _validateThemeExists();
|
||||
void _validateRegexes();
|
||||
|
||||
void _researchOnLoad();
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
CascadiaSettings(String userJSON, String inboxJSON);
|
||||
|
||||
CascadiaSettings Copy();
|
||||
void ResetApplicationState();
|
||||
void ResetToDefaultSettings();
|
||||
void WriteSettingsToDisk();
|
||||
void LogSettingChanges(Boolean isJsonLoad);
|
||||
|
||||
|
||||
@@ -1345,6 +1345,21 @@ winrt::hstring CascadiaSettings::DefaultSettingsPath()
|
||||
return winrt::hstring{ path.native() };
|
||||
}
|
||||
|
||||
void CascadiaSettings::ResetApplicationState() const
|
||||
{
|
||||
auto state = ApplicationState::SharedInstance();
|
||||
const auto hash = state.SettingsHash();
|
||||
state.Reset();
|
||||
state.SettingsHash(hash);
|
||||
state.Flush();
|
||||
}
|
||||
|
||||
void CascadiaSettings::ResetToDefaultSettings()
|
||||
{
|
||||
ApplicationState::SharedInstance().Reset();
|
||||
_writeSettingsToDisk(LoadStringResource(IDR_USER_DEFAULTS));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Write the current state of CascadiaSettings to our settings file
|
||||
// - Create a backup file with the current contents, if one does not exist
|
||||
@@ -1355,19 +1370,21 @@ winrt::hstring CascadiaSettings::DefaultSettingsPath()
|
||||
// - <none>
|
||||
void CascadiaSettings::WriteSettingsToDisk()
|
||||
{
|
||||
const auto settingsPath = _settingsPath();
|
||||
|
||||
// write current settings to current settings file
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons
|
||||
wbuilder.settings_["indentation"] = " ";
|
||||
wbuilder.settings_["precision"] = 6; // prevent values like 1.1000000000000001
|
||||
|
||||
FILETIME lastWriteTime{};
|
||||
const auto styledString{ Json::writeString(wbuilder, ToJson()) };
|
||||
til::io::write_utf8_string_to_file_atomic(settingsPath, styledString, &lastWriteTime);
|
||||
_writeSettingsToDisk(Json::writeString(wbuilder, ToJson()));
|
||||
}
|
||||
|
||||
_hash = _calculateHash(styledString, lastWriteTime);
|
||||
void CascadiaSettings::_writeSettingsToDisk(std::string_view contents)
|
||||
{
|
||||
FILETIME lastWriteTime{};
|
||||
til::io::write_utf8_string_to_file_atomic(_settingsPath(), contents, &lastWriteTime);
|
||||
|
||||
_hash = _calculateHash(contents, lastWriteTime);
|
||||
|
||||
// Persists the default terminal choice
|
||||
// GH#10003 - Only do this if _currentDefaultTerminal was actually initialized.
|
||||
|
||||
@@ -36,41 +36,71 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto entry = winrt::make_self<MatchProfilesEntry>();
|
||||
|
||||
JsonUtils::GetValueForKey(json, NameKey, entry->_Name);
|
||||
entry->_validateName();
|
||||
|
||||
JsonUtils::GetValueForKey(json, CommandlineKey, entry->_Commandline);
|
||||
entry->_validateCommandline();
|
||||
|
||||
JsonUtils::GetValueForKey(json, SourceKey, entry->_Source);
|
||||
entry->_validateSource();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Returns true if all regexes are valid, false otherwise
|
||||
bool MatchProfilesEntry::ValidateRegexes() const
|
||||
{
|
||||
return !(_invalidName || _invalidCommandline || _invalidSource);
|
||||
}
|
||||
|
||||
#define DEFINE_VALIDATE_FUNCTION(name) \
|
||||
void MatchProfilesEntry::_validate##name() noexcept \
|
||||
{ \
|
||||
_invalid##name = false; \
|
||||
if (_##name.empty()) \
|
||||
{ \
|
||||
/* empty field is valid*/ \
|
||||
return; \
|
||||
} \
|
||||
UErrorCode status = U_ZERO_ERROR; \
|
||||
_##name##Regex = til::ICU::CreateRegex(_##name, 0, &status); \
|
||||
if (U_FAILURE(status)) \
|
||||
{ \
|
||||
_invalid##name = true; \
|
||||
_##name##Regex.reset(); \
|
||||
} \
|
||||
}
|
||||
|
||||
DEFINE_VALIDATE_FUNCTION(Name);
|
||||
DEFINE_VALIDATE_FUNCTION(Commandline);
|
||||
DEFINE_VALIDATE_FUNCTION(Source);
|
||||
|
||||
bool MatchProfilesEntry::MatchesProfile(const Model::Profile& profile)
|
||||
{
|
||||
// We use an optional here instead of a simple bool directly, since there is no
|
||||
// sensible default value for the desired semantics: the first property we want
|
||||
// to match on should always be applied (so one would set "true" as a default),
|
||||
// but if none of the properties are set, the default return value should be false
|
||||
// since this entry type is expected to behave like a positive match/whitelist.
|
||||
//
|
||||
// The easiest way to deal with this neatly is to use an optional, then for any
|
||||
// property to match we consider a null value to be "true", and for the return
|
||||
// value of the function we consider the null value to be "false".
|
||||
auto isMatching = std::optional<bool>{};
|
||||
auto isMatch = [](const til::ICU::unique_uregex& regex, std::wstring_view text) {
|
||||
if (text.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
uregex_setText(regex.get(), reinterpret_cast<const UChar*>(text.data()), static_cast<int32_t>(text.size()), &status);
|
||||
const auto match = uregex_matches(regex.get(), 0, &status);
|
||||
return status == U_ZERO_ERROR && match;
|
||||
};
|
||||
|
||||
if (!_Name.empty())
|
||||
if (!_Name.empty() && isMatch(_NameRegex, profile.Name()))
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Name == profile.Name() };
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_Source.empty())
|
||||
else if (!_Source.empty() && isMatch(_SourceRegex, profile.Source()))
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Source == profile.Source() };
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
else if (!_Commandline.empty() && isMatch(_CommandlineRegex, profile.Commandline()))
|
||||
{
|
||||
isMatching = { isMatching.value_or(true) && _Commandline == profile.Commandline() };
|
||||
return true;
|
||||
}
|
||||
|
||||
return isMatching.value_or(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
Model::NewTabMenuEntry MatchProfilesEntry::Copy() const
|
||||
|
||||
@@ -17,6 +17,30 @@ Author(s):
|
||||
|
||||
#include "ProfileCollectionEntry.h"
|
||||
#include "MatchProfilesEntry.g.h"
|
||||
#include <til/regex.h>
|
||||
|
||||
// This macro defines the getter and setter for a regex property.
|
||||
// The setter tries to instantiate the regex immediately and caches
|
||||
// it if successful. If it fails, it sets a boolean flag to track that
|
||||
// it failed.
|
||||
#define DEFINE_MATCH_PROFILE_REGEX_PROPERTY(name) \
|
||||
public: \
|
||||
hstring name() const noexcept \
|
||||
{ \
|
||||
return _##name; \
|
||||
} \
|
||||
void name(const hstring& value) noexcept \
|
||||
{ \
|
||||
_##name = value; \
|
||||
_validate##name(); \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
void _validate##name() noexcept; \
|
||||
\
|
||||
hstring _##name; \
|
||||
til::ICU::unique_uregex _##name##Regex; \
|
||||
bool _invalid##name{ false };
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
@@ -30,11 +54,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
Json::Value ToJson() const override;
|
||||
static com_ptr<NewTabMenuEntry> FromJson(const Json::Value& json);
|
||||
|
||||
bool ValidateRegexes() const;
|
||||
bool MatchesProfile(const Model::Profile& profile);
|
||||
|
||||
WINRT_PROPERTY(winrt::hstring, Name);
|
||||
WINRT_PROPERTY(winrt::hstring, Commandline);
|
||||
WINRT_PROPERTY(winrt::hstring, Source);
|
||||
DEFINE_MATCH_PROFILE_REGEX_PROPERTY(Name)
|
||||
DEFINE_MATCH_PROFILE_REGEX_PROPERTY(Commandline)
|
||||
DEFINE_MATCH_PROFILE_REGEX_PROPERTY(Source)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Microsoft.Terminal.Settings.Model
|
||||
UnknownTheme,
|
||||
DuplicateRemainingProfilesEntry,
|
||||
InvalidUseOfContent,
|
||||
InvalidRegex,
|
||||
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,10 @@ void IslandWindow::HideCursor() noexcept
|
||||
|
||||
void IslandWindow::ShowCursorMaybe(const UINT message) noexcept
|
||||
{
|
||||
if (_cursorHidden && (message == WM_ACTIVATE || message == WM_POINTERUPDATE))
|
||||
if (_cursorHidden &&
|
||||
(message == WM_ACTIVATE ||
|
||||
message == WM_POINTERUPDATE ||
|
||||
message == WM_NCPOINTERUPDATE))
|
||||
{
|
||||
_cursorHidden = false;
|
||||
ShowCursor(TRUE);
|
||||
|
||||
@@ -314,6 +314,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
|
||||
_createMessageWindow(windowClassName.c_str());
|
||||
_setupGlobalHotkeys();
|
||||
_checkWindowsForNotificationIcon();
|
||||
_setupSessionPersistence(_app.Logic().Settings().GlobalSettings().ShouldUsePersistedLayout());
|
||||
|
||||
// When the settings change, we'll want to update our global hotkeys
|
||||
// and our notification icon based on the new settings.
|
||||
@@ -323,6 +324,7 @@ void WindowEmperor::HandleCommandlineArgs(int nCmdShow)
|
||||
_assertIsMainThread();
|
||||
_setupGlobalHotkeys();
|
||||
_checkWindowsForNotificationIcon();
|
||||
_setupSessionPersistence(args.NewSettings().GlobalSettings().ShouldUsePersistedLayout());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -922,17 +924,22 @@ LRESULT WindowEmperor::_messageHandler(HWND window, UINT const message, WPARAM c
|
||||
return DefWindowProcW(window, message, wParam, lParam);
|
||||
}
|
||||
|
||||
void WindowEmperor::_finalizeSessionPersistence() const
|
||||
void WindowEmperor::_setupSessionPersistence(bool enabled)
|
||||
{
|
||||
if (_skipPersistence)
|
||||
if (!enabled)
|
||||
{
|
||||
// We received WM_ENDSESSION and persisted the state.
|
||||
// We don't need to persist it again.
|
||||
_persistStateTimer.Stop();
|
||||
return;
|
||||
}
|
||||
_persistStateTimer.Interval(std::chrono::minutes(5));
|
||||
_persistStateTimer.Tick([&](auto&&, auto&&) {
|
||||
_persistState(ApplicationState::SharedInstance(), false);
|
||||
});
|
||||
_persistStateTimer.Start();
|
||||
}
|
||||
|
||||
const auto state = ApplicationState::SharedInstance();
|
||||
|
||||
void WindowEmperor::_persistState(const ApplicationState& state, bool serializeBuffer) const
|
||||
{
|
||||
// Calling an `ApplicationState` setter triggers a write to state.json.
|
||||
// With this if condition we avoid an unnecessary write when persistence is disabled.
|
||||
if (state.PersistedWindowLayouts())
|
||||
@@ -944,12 +951,26 @@ void WindowEmperor::_finalizeSessionPersistence() const
|
||||
{
|
||||
for (const auto& w : _windows)
|
||||
{
|
||||
w->Logic().PersistState();
|
||||
w->Logic().PersistState(serializeBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure to write the state.json before we TerminateProcess()
|
||||
// Ensure to write the state.json
|
||||
state.Flush();
|
||||
}
|
||||
|
||||
void WindowEmperor::_finalizeSessionPersistence() const
|
||||
{
|
||||
if (_skipPersistence)
|
||||
{
|
||||
// We received WM_ENDSESSION and persisted the state.
|
||||
// We don't need to persist it again.
|
||||
return;
|
||||
}
|
||||
|
||||
const auto state = ApplicationState::SharedInstance();
|
||||
|
||||
_persistState(state, true);
|
||||
|
||||
if (_needsPersistenceCleanup)
|
||||
{
|
||||
|
||||
@@ -64,6 +64,8 @@ private:
|
||||
void _registerHotKey(int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
|
||||
void _unregisterHotKey(int index) noexcept;
|
||||
void _setupGlobalHotkeys();
|
||||
void _setupSessionPersistence(bool enabled);
|
||||
void _persistState(const winrt::Microsoft::Terminal::Settings::Model::ApplicationState& state, bool serializeBuffer) const;
|
||||
void _finalizeSessionPersistence() const;
|
||||
void _checkWindowsForNotificationIcon();
|
||||
|
||||
@@ -77,6 +79,7 @@ private:
|
||||
bool _notificationIconShown = false;
|
||||
bool _skipPersistence = false;
|
||||
bool _needsPersistenceCleanup = false;
|
||||
SafeDispatcherTimer _persistStateTimer;
|
||||
std::optional<bool> _currentSystemThemeIsDark;
|
||||
int32_t _windowCount = 0;
|
||||
int32_t _messageBoxCount = 0;
|
||||
|
||||
22
src/inc/til/regex.h
Normal file
22
src/inc/til/regex.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <icu.h>
|
||||
|
||||
namespace til::ICU // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
using unique_uregex = wistd::unique_ptr<URegularExpression, wil::function_deleter<decltype(&uregex_close), &uregex_close>>;
|
||||
|
||||
_TIL_INLINEPREFIX unique_uregex CreateRegex(const std::wstring_view& pattern, uint32_t flags, UErrorCode* status) noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
const auto re = uregex_open(reinterpret_cast<const char16_t*>(pattern.data()), gsl::narrow_cast<int32_t>(pattern.size()), flags, nullptr, status);
|
||||
// ICU describes the time unit as being dependent on CPU performance and "typically [in] the order of milliseconds",
|
||||
// but this claim seems highly outdated already. On my CPU from 2021, a limit of 4096 equals roughly 600ms.
|
||||
uregex_setTimeLimit(re, 4096, status);
|
||||
uregex_setStackLimit(re, 4 * 1024 * 1024, status);
|
||||
return unique_uregex{ re };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user