diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d044cbe13f2..fbe27ea6525 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -6,6 +6,7 @@ #include "TerminalPage.h" #include "ScratchpadContent.h" +#include "TasksPaneContent.h" #include "../WinRTUtils/inc/WtExeUtils.h" #include "../../types/inc/utils.hpp" #include "Utils.h" @@ -1473,4 +1474,25 @@ namespace winrt::TerminalApp::implementation _ShowAboutDialog(); args.Handled(true); } + + void TerminalPage::_HandleOpenTasksPane(const IInspectable& sender, + const ActionEventArgs& args) + { + if (Feature_ScratchpadPane::IsEnabled()) + { + const auto& scratchPane{ winrt::make_self() }; + scratchPane->UpdateSettings(_settings); + // This is maybe a little wacky - add our key event handler to the pane + // we made. So that we can get actions for keys that the content didn't + // handle. + scratchPane->GetRoot().KeyDown({ this, &TerminalPage::_KeyDownHandler }); + + scratchPane->DispatchCommandRequested({ this, &TerminalPage::_OnDispatchCommandRequested }); + + const auto resultPane = std::make_shared(*scratchPane); + _SplitPane(_senderOrFocusedTab(sender), SplitDirection::Automatic, 0.5f, resultPane); + args.Handled(true); + } + } + } diff --git a/src/cascadia/TerminalApp/FilteredCommand.cpp b/src/cascadia/TerminalApp/FilteredCommand.cpp index 98a7b53de4b..d6c6c38a284 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.cpp +++ b/src/cascadia/TerminalApp/FilteredCommand.cpp @@ -21,11 +21,22 @@ namespace winrt::TerminalApp::implementation { // This class is a wrapper of PaletteItem, that is used as an item of a filterable list in CommandPalette. // It manages a highlighted text that is computed by matching search filter characters to item name - FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item) : - _Item(item), - _Filter(L""), - _Weight(0) + FilteredCommand::FilteredCommand(const winrt::TerminalApp::PaletteItem& item) { + // Actually implement the ctor in _constructFilteredCommand + _constructFilteredCommand(item); + } + + // We need to actually implement the ctor in a separate helper. This is + // because we have a FilteredTask class which derives from FilteredCommand. + // HOWEVER, for cppwinrt ~ r e a s o n s ~, it doesn't actually derive from + // FilteredCommand directly, so we can't just use the FilteredCommand ctor + // directly in the base class. + void FilteredCommand::_constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item) + { + _Item = item; + _Filter = L""; + _Weight = 0; _HighlightedName = _computeHighlightedName(); // Recompute the highlighted name if the item name changes diff --git a/src/cascadia/TerminalApp/FilteredCommand.h b/src/cascadia/TerminalApp/FilteredCommand.h index e5df7bb018a..f304ad032a3 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.h +++ b/src/cascadia/TerminalApp/FilteredCommand.h @@ -19,7 +19,7 @@ namespace winrt::TerminalApp::implementation FilteredCommand() = default; FilteredCommand(const winrt::TerminalApp::PaletteItem& item); - void UpdateFilter(const winrt::hstring& filter); + virtual void UpdateFilter(const winrt::hstring& filter); static int Compare(const winrt::TerminalApp::FilteredCommand& first, const winrt::TerminalApp::FilteredCommand& second); @@ -29,6 +29,9 @@ namespace winrt::TerminalApp::implementation WINRT_OBSERVABLE_PROPERTY(winrt::TerminalApp::HighlightedText, HighlightedName, PropertyChanged.raise); WINRT_OBSERVABLE_PROPERTY(int, Weight, PropertyChanged.raise); + protected: + void _constructFilteredCommand(const winrt::TerminalApp::PaletteItem& item); + private: winrt::TerminalApp::HighlightedText _computeHighlightedName(); int _computeWeight(); diff --git a/src/cascadia/TerminalApp/FilteredCommand.idl b/src/cascadia/TerminalApp/FilteredCommand.idl index ce2b04b92c1..a63e6e81100 100644 --- a/src/cascadia/TerminalApp/FilteredCommand.idl +++ b/src/cascadia/TerminalApp/FilteredCommand.idl @@ -6,7 +6,7 @@ import "HighlightedTextControl.idl"; namespace TerminalApp { - [default_interface] runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged + [default_interface] unsealed runtimeclass FilteredCommand : Windows.UI.Xaml.Data.INotifyPropertyChanged { FilteredCommand(); FilteredCommand(PaletteItem item); diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index 3053d746df2..ac635bb2fd2 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -137,6 +137,16 @@ namespace winrt::TerminalApp::implementation // for it. The Title change will be propagated upwards through the tab's // PropertyChanged event handler. newTabImpl->ActivePaneChanged([weakTab, weakThis{ get_weak() }]() { + // TODO! + // + // * Make this a method on TerminalPage. + // * Convert ActivePaneChanged to a typed event, so it sends the sender (so we don't need to make all these lambdas) + // * Stash the task pane as a member on the Terminal? if one was opened. + // * If the tab does have a taskpane, then tell the taskpane the active pane changed + // + // wait don't do any of that. just do that in TerminalTab directly + // before we even raise the event you donkey + auto page{ weakThis.get() }; auto tab{ weakTab.get() }; diff --git a/src/cascadia/TerminalApp/TasksPaneContent.cpp b/src/cascadia/TerminalApp/TasksPaneContent.cpp new file mode 100644 index 00000000000..1f941a17ad1 --- /dev/null +++ b/src/cascadia/TerminalApp/TasksPaneContent.cpp @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "TasksPaneContent.h" +#include "TasksPaneContent.g.cpp" +#include "FilteredTask.g.cpp" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::Settings::Model; + +namespace winrt +{ + namespace WUX = Windows::UI::Xaml; + namespace MUX = Microsoft::UI::Xaml; + using IInspectable = Windows::Foundation::IInspectable; +} + +namespace winrt::TerminalApp::implementation +{ + TasksPaneContent::TasksPaneContent() + { + InitializeComponent(); + + auto res = Windows::UI::Xaml::Application::Current().Resources(); + auto bg = res.Lookup(winrt::box_value(L"UnfocusedBorderBrush")); + Background(bg.try_as()); + } + + void TasksPaneContent::_updateFilteredCommands() + { + const auto& queryString = _filterBox().Text(); + + // DON'T replace the itemSource here. If you do, it'll un-expand all the + // nested items the user has expanded. Instead, just update the filter. + // That'll also trigger a PropertyChanged for the Visibility property. + for (const auto& t : _allTasks) + { + t.UpdateFilter(queryString); + } + } + + void TasksPaneContent::UpdateSettings(const CascadiaSettings& settings) + { + _settings = settings; + + // You'd think that `FilterToSendInput(queryString)` would work. It + // doesn't! That uses the queryString as the current command the user + // has typed, then relies on the sxnui to _also_ filter with that + // string. + + const auto tasks = _settings.GlobalSettings().ActionMap().FilterToSendInput(L""); // IVector + _allTasks = winrt::single_threaded_observable_vector(); + for (const auto& t : tasks) + { + const auto& filtered{ winrt::make(t) }; + _allTasks.Append(filtered); + } + _treeView().ItemsSource(_allTasks); + + _updateFilteredCommands(); + } + + void TasksPaneContent::_filterTextChanged(const IInspectable& /*sender*/, + const Windows::UI::Xaml::RoutedEventArgs& /*args*/) + { + _updateFilteredCommands(); + } + + winrt::Windows::UI::Xaml::FrameworkElement TasksPaneContent::GetRoot() + { + return *this; + } + winrt::Windows::Foundation::Size TasksPaneContent::MinimumSize() + { + return { 1, 1 }; + } + void TasksPaneContent::Focus(winrt::Windows::UI::Xaml::FocusState reason) + { + reason; + // _box.Focus(reason); + } + void TasksPaneContent::Close() + { + CloseRequested.raise(*this, nullptr); + } + + NewTerminalArgs TasksPaneContent::GetNewTerminalArgs(const bool /* asContent */) const + { + return nullptr; + } + + winrt::hstring TasksPaneContent::Icon() const + { + static constexpr std::wstring_view glyph{ L"\xe70b" }; // QuickNote + return winrt::hstring{ glyph }; + } + + winrt::Windows::UI::Xaml::Media::Brush TasksPaneContent::BackgroundBrush() + { + return Background(); + } + + void TasksPaneContent::SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control) + { + _control = control; + } + + void TasksPaneContent::_runCommandButtonClicked(const Windows::Foundation::IInspectable& sender, + const Windows::UI::Xaml::RoutedEventArgs&) + { + if (const auto& taskVM{ sender.try_as().DataContext().try_as() }) + { + if (const auto& strongControl{ _control.get() }) + { + // By using the last active control as the sender here, the + // actiopn dispatch will send this to the active control, + // thinking that it is the control that requested this event. + DispatchCommandRequested.raise(strongControl, taskVM->Command()); + } + } + } + +} diff --git a/src/cascadia/TerminalApp/TasksPaneContent.h b/src/cascadia/TerminalApp/TasksPaneContent.h new file mode 100644 index 00000000000..935b18a70b3 --- /dev/null +++ b/src/cascadia/TerminalApp/TasksPaneContent.h @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "TasksPaneContent.g.h" +#include "FilteredTask.g.h" +#include "FilteredCommand.h" +#include "ActionPaletteItem.h" + +namespace winrt::TerminalApp::implementation +{ + struct TasksPaneContent : TasksPaneContentT + { + TasksPaneContent(); + + winrt::Windows::UI::Xaml::FrameworkElement GetRoot(); + + void UpdateSettings(const winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings& settings); + + winrt::Windows::Foundation::Size MinimumSize(); + void Focus(winrt::Windows::UI::Xaml::FocusState reason = winrt::Windows::UI::Xaml::FocusState::Programmatic); + void Close(); + winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs GetNewTerminalArgs(const bool asContent) const; + + // TODO! lots of strings here and in XAML that need RS_-ifying + winrt::hstring Title() { return L"Tasks"; } + uint64_t TaskbarState() { return 0; } + uint64_t TaskbarProgress() { return 0; } + bool ReadOnly() { return false; } + winrt::hstring Icon() const; + Windows::Foundation::IReference TabColor() const noexcept { return nullptr; } + winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush(); + + void SetLastActiveControl(const Microsoft::Terminal::Control::TermControl& control); + + til::typed_event<> CloseRequested; + til::typed_event BellRequested; + til::typed_event<> TitleChanged; + til::typed_event<> TabColorChanged; + til::typed_event<> TaskbarProgressChanged; + til::typed_event<> ConnectionStateChanged; + til::typed_event<> ReadOnlyChanged; + til::typed_event<> FocusRequested; + + til::typed_event DispatchCommandRequested; + + private: + friend struct TasksPaneContentT; // for Xaml to bind events + + winrt::weak_ref _control{ nullptr }; + winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; + + winrt::Windows::Foundation::Collections::IObservableVector _allTasks{ nullptr }; + + void _runCommandButtonClicked(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs&); + void _filterTextChanged(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + + void _updateFilteredCommands(); + }; + + struct FilteredTask : FilteredTaskT + { + FilteredTask() = default; + + FilteredTask(const winrt::Microsoft::Terminal::Settings::Model::Command& command) + { + _constructFilteredCommand(winrt::make(command)); + _command = command; + + // The Children() method must always return a non-null vector + _children = winrt::single_threaded_observable_vector(); + if (_command.HasNestedCommands()) + { + for (const auto& [_, child] : _command.NestedCommands()) + { + auto vm{ winrt::make(child) }; + _children.Append(vm); + } + } + } + + void UpdateFilter(const winrt::hstring& filter) override + { + TerminalApp::implementation::FilteredCommand::UpdateFilter(filter); + for (const auto& c : _children) + { + c.UpdateFilter(filter); + } + + PropertyChanged.raise(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Visibility" }); + } + + winrt::hstring Input() + { + if (const auto& actionItem{ _Item.try_as() }) + { + if (const auto& command{ actionItem.Command() }) + { + if (const auto& sendInput{ command.ActionAndArgs().Args().try_as() }) + { + return sendInput.Input(); + } + } + } + return L""; + }; + + winrt::Windows::Foundation::Collections::IObservableVector Children() { return _children; } + winrt::Microsoft::Terminal::Settings::Model::Command Command() { return _command; } + + // Used to control if this item is visible in the TreeView. Turns out, + // TreeView is in fact sane enough to remove items entirely if they're + // Collapsed. + winrt::Windows::UI::Xaml::Visibility Visibility() + { + // Is there no filter, or do we match it? + if (_Filter.empty() || _Weight > 0) + { + return winrt::Windows::UI::Xaml::Visibility::Visible; + } + // If we don't match, maybe one of our children does + auto totalWeight = _Weight; + for (const auto& c : _children) + { + totalWeight += c.Weight(); + } + + return totalWeight > 0 ? winrt::Windows::UI::Xaml::Visibility::Visible : winrt::Windows::UI::Xaml::Visibility::Collapsed; + }; + + private: + winrt::Microsoft::Terminal::Settings::Model::Command _command{ nullptr }; + winrt::Windows::Foundation::Collections::IObservableVector _children{ nullptr }; + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + BASIC_FACTORY(TasksPaneContent); +} diff --git a/src/cascadia/TerminalApp/TasksPaneContent.xaml b/src/cascadia/TerminalApp/TasksPaneContent.xaml new file mode 100644 index 00000000000..c13830b9416 --- /dev/null +++ b/src/cascadia/TerminalApp/TasksPaneContent.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index d0629da8036..f6a669624a6 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -71,6 +71,9 @@ Designer + + Designer + @@ -161,6 +164,9 @@ TerminalPaneContent.idl + + TasksPaneContent.xaml + TerminalPaneContent.idl @@ -274,6 +280,9 @@ TerminalPaneContent.idl + + TasksPaneContent.xaml + TerminalPaneContent.idl @@ -352,7 +361,10 @@ - + + TaskPaneContent.xaml + Code + diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 3a82a05aebf..70af18b7434 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -454,10 +454,10 @@ namespace winrt::TerminalApp::implementation // - command - command to dispatch // Return Value: // - - void TerminalPage::_OnDispatchCommandRequested(const IInspectable& /*sender*/, const Microsoft::Terminal::Settings::Model::Command& command) + void TerminalPage::_OnDispatchCommandRequested(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::Command& command) { const auto& actionAndArgs = command.ActionAndArgs(); - _actionDispatch->DoAction(actionAndArgs); + _actionDispatch->DoAction(sender, actionAndArgs); } // Method Description: diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.idl b/src/cascadia/TerminalApp/TerminalPaneContent.idl index 0e4738fff42..98347e60e14 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.idl +++ b/src/cascadia/TerminalApp/TerminalPaneContent.idl @@ -3,6 +3,7 @@ import "IPaneContent.idl"; import "TerminalSettingsCache.idl"; +import "FilteredCommand.idl"; namespace TerminalApp { @@ -16,4 +17,20 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler RestartTerminalRequested; } + + [default_interface] runtimeclass FilteredTask : TerminalApp.FilteredCommand + { + String Input{ get; }; + Windows.Foundation.Collections.IObservableVector Children { get; }; + Windows.UI.Xaml.Visibility Visibility { get; }; + } + + [default_interface] runtimeclass TasksPaneContent : Windows.UI.Xaml.Controls.UserControl, IPaneContent + { + TasksPaneContent(); + void SetLastActiveControl(Microsoft.Terminal.Control.TermControl control); + + event Windows.Foundation.TypedEventHandler DispatchCommandRequested; + + } } diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index d3e52d4037d..f4c45243519 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1249,6 +1249,18 @@ namespace winrt::TerminalApp::implementation // Raise our own ActivePaneChanged event. ActivePaneChanged.raise(); + + const auto content{ pane->GetContent() }; + if (const auto termContent{ content.try_as() }) + { + const auto& termControl{ termContent.GetTermControl() }; + _rootPane->WalkTree([termControl](const auto& p) { + if (const auto& taskPane{ p->GetContent().try_as() }) + { + taskPane.SetLastActiveControl(termControl); + } + }); + } } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 7bba13bc4cc..6dd2a8c1109 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -98,6 +98,7 @@ static constexpr std::string_view RestartConnectionKey{ "restartConnection" }; static constexpr std::string_view ToggleBroadcastInputKey{ "toggleBroadcastInput" }; static constexpr std::string_view OpenScratchpadKey{ "experimental.openScratchpad" }; static constexpr std::string_view OpenAboutKey{ "openAbout" }; +static constexpr std::string_view OpenTasksPaneKey{ "experimental.openTasks" }; static constexpr std::string_view ActionKey{ "action" }; @@ -434,6 +435,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ToggleBroadcastInput, RS_(L"ToggleBroadcastInputCommandKey") }, { ShortcutAction::OpenScratchpad, RS_(L"OpenScratchpadKey") }, { ShortcutAction::OpenAbout, RS_(L"OpenAboutCommandKey") }, + { ShortcutAction::OpenTasksPane, RS_(L"OpenTasksPaneCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 220cd825565..6833b522a43 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -964,17 +964,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation cmdImpl.copy_from(winrt::get_self(command)); const auto inArgs{ command.ActionAndArgs().Args().try_as() }; - + const auto inputString{ (std::wstring_view)(inArgs ? inArgs.Input() : L"") }; auto args = winrt::make_self( winrt::hstring{ fmt::format(FMT_COMPILE(L"{:\x7f^{}}{}"), L"", numBackspaces, - (std::wstring_view)(inArgs ? inArgs.Input() : L"")) }); + inputString) }); Model::ActionAndArgs actionAndArgs{ ShortcutAction::SendInput, *args }; auto copy = cmdImpl->Copy(); copy->ActionAndArgs(actionAndArgs); + if (!copy->HasName()) + { + // Here, we want to manually generate a send input name, but + // without visualizing space and backspace + // + // TODO! Do we want to include `Send Input: ` in the generated + // string? I think it looks better without it tbh + copy->Name(winrt::hstring{ til::visualize_nonspace_control_codes(std::wstring{ inputString }) }); + } + return *copy; }; diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index 28ba4281e69..39de2323199 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -111,7 +111,8 @@ ON_ALL_ACTIONS(RestartConnection) \ ON_ALL_ACTIONS(ToggleBroadcastInput) \ ON_ALL_ACTIONS(OpenScratchpad) \ - ON_ALL_ACTIONS(OpenAbout) + ON_ALL_ACTIONS(OpenAbout) \ + ON_ALL_ACTIONS(OpenTasksPane) #define ALL_SHORTCUT_ACTIONS_WITH_ARGS \ ON_ALL_ACTIONS_WITH_ARGS(AdjustFontSize) \ diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 6179099bdf5..2f0e5fd5362 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -727,4 +727,8 @@ Open about dialog This will open the "about" dialog, to display version info and other documentation - \ No newline at end of file + + Open tasks pane + This will open a pane with a list of the users's saved commands in it + + diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 468c2120acf..475f4039a84 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -447,6 +447,7 @@ { "command": "quit", "id": "Terminal.Quit" }, { "command": "restoreLastClosed", "id": "Terminal.RestoreLastClosed" }, { "command": "openAbout", "id": "Terminal.OpenAboutDialog" }, + { "command": "experimental.openTasks", "id": "Terminal.OpenTasks" }, // Tab Management // "command": "closeTab" is unbound by default. diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 17c720614b1..fa1575d32ae 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -133,7 +133,7 @@ public: \ _##name = value; \ } \ \ -private: \ +protected: \ type _##name{ __VA_ARGS__ }; // Use this macro to quickly implement both the getter and setter for an @@ -158,7 +158,7 @@ public: } \ }; \ \ -private: \ +protected: \ type _##name{ __VA_ARGS__ }; \ void _set##name(const type& value) \ { \ diff --git a/src/inc/til/string.h b/src/inc/til/string.h index fcb6905e282..33570ffe621 100644 --- a/src/inc/til/string.h +++ b/src/inc/til/string.h @@ -24,6 +24,26 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } return str; } + _TIL_INLINEPREFIX std::wstring visualize_nonspace_control_codes(std::wstring str) noexcept + { + for (auto& ch : str) + { + // NOT backspace! + if (ch < 0x20 && ch != 0x08) + { + ch += 0x2400; + } + // else if (ch == 0x20) + // { + // ch = 0x2423; // replace space with ␣ + // } + else if (ch == 0x7f) + { + ch = 0x2421; // replace del with ␡ + } + } + return str; + } _TIL_INLINEPREFIX std::wstring visualize_control_codes(std::wstring_view str) {