From 799b5d4add799ac2fbf5c855b774cd7dcb957697 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 9 Jun 2022 16:10:16 -0500 Subject: [PATCH] Experimental: add support for scrollbar marks (#12948) Adds support for marks in the scrollbar. These marks can be added in 3 ways: * Via the iterm2 `OSC 1337 ; SetMark` sequence * Via the `addMark` action * Automatically when the `experimental.autoMarkPrompts` per-profile setting is enabled. #11000 has more tracking for the big-picture for this feature, as well as additional follow-ups. This set of functionality seemed complete enough to send a review for now. That issue describes these how I wish these actions to look in the fullness of time. This is simply the v0.1 from the hackathon last month. #### Actions * `addMark`: add a mark to the buffer. If there's a selection, use place the mark covering at the selection. Otherwise, place the mark on the cursor row. - `color`: a color for the scrollbar mark. This is optional - defaults to the `foreground` color of the current scheme if omitted. * `scrollToMark` - `direction`: `["first", "previous", "next", "last"]` * `clearMark`: Clears marks at the current postition (either the selection if there is one, or the cursor position. * `clearAllMarks`: Don't think this needs explanation. #### Per-profile settings * `experimental.autoMarkPrompts`: `bool`, default `false`. * `experimental.showMarksOnScrollbar`: `bool` ## PR Checklist * [x] Closes #1527 * [x] Closes #6232 ## Detailed Description of the Pull Request / Additional comments This is basically hackathon code. It's experimental! That's okay! We'll figure the rest of the design in post. Theoretically, I should make these actions `experimental.` as well, but it seemed like since the only way to see these guys was via the `experimental.showMarksOnScrollbar` setting, you've already broken yourself into experimental jail, and you know what you're doing. Things that won't work as expected: * resizing, ESPECIALLY reflowing * Clearing the buffer with ED sequences / Clear Buffer I could theoretically add velocity around this in the `TermControl` layer. Always prevent marks from being visible, ignore all the actions. Marks could still be set by VT and automark, but they'd be useless. Next up priorities: * Making this work with the FinalTerm sequences * properly speccing * adding support for `showMarksOnScrollbar: flags(categories)`, so you can only display errors on the scrollbar * adding the `category` flag to the `addMark` action ## Validation Steps Performed I like using it quite a bit. The marks can get noisy if you have them emitted on every prompt and the buffer has 9000 lines. But that's the beautiful thing, the actions work even if the marks aren't visible, so you can still scroll between prompts.
Settings blob ```jsonc // actions { "keys": "ctrl+up", "command": { "action": "scrollToMark", "direction": "previous" }, "name": "Previous mark" }, { "keys": "ctrl+down", "command": { "action": "scrollToMark", "direction": "next" }, "name": "Next mark" }, { "keys": "ctrl+pgup", "command": { "action": "scrollToMark", "direction": "first" }, "name": "First mark" }, { "keys": "ctrl+pgdn", "command": { "action": "scrollToMark", "direction": "last" }, "name": "Last mark" }, { "command": { "action": "addMark" } }, { "command": { "action": "addMark", "color": "#ff00ff" } }, { "command": { "action": "addMark", "color": "#0000ff" } }, { "command": { "action": "clearAllMarks" } }, // profiles.defaults "experimental.autoMarkPrompts": true, "experimental.showMarksOnScrollbar": true, ```
--- .../TerminalApp/AppActionHandlers.cpp | 49 ++++++ src/cascadia/TerminalControl/ControlCore.cpp | 162 ++++++++++++++++++ src/cascadia/TerminalControl/ControlCore.h | 7 + src/cascadia/TerminalControl/ControlCore.idl | 1 - .../TerminalControl/IControlSettings.idl | 1 + src/cascadia/TerminalControl/ICoreState.idl | 32 ++++ src/cascadia/TerminalControl/TermControl.cpp | 86 ++++++++-- src/cascadia/TerminalControl/TermControl.h | 9 + src/cascadia/TerminalControl/TermControl.xaml | 24 +++ src/cascadia/TerminalControl/pch.h | 1 + src/cascadia/TerminalCore/ICoreAppearance.idl | 9 + src/cascadia/TerminalCore/ICoreSettings.idl | 3 + src/cascadia/TerminalCore/Terminal.cpp | 139 ++++++++++++++- src/cascadia/TerminalCore/Terminal.hpp | 15 ++ src/cascadia/TerminalCore/TerminalApi.cpp | 6 + .../TerminalSettingsModel/ActionAndArgs.cpp | 8 + .../TerminalSettingsModel/ActionArgs.cpp | 34 ++++ .../TerminalSettingsModel/ActionArgs.h | 14 ++ .../TerminalSettingsModel/ActionArgs.idl | 11 ++ .../AllShortcutActions.h | 6 + .../TerminalSettingsModel/MTSMSettings.h | 4 +- .../TerminalSettingsModel/Profile.idl | 2 + .../Resources/en-US/Resources.resw | 27 ++- .../TerminalSettings.cpp | 2 + .../TerminalSettingsModel/TerminalSettings.h | 3 + .../TerminalSettingsSerializationHelpers.h | 11 ++ src/cascadia/inc/ControlProperties.h | 6 +- .../ut_app/TerminalApp.Unit.Tests.manifest | 1 + src/features.xml | 12 ++ src/host/outputStream.cpp | 5 + src/host/outputStream.hpp | 2 + src/terminal/adapter/DispatchTypes.hpp | 18 ++ src/terminal/adapter/ITermDispatch.hpp | 2 + src/terminal/adapter/ITerminalApi.hpp | 2 + src/terminal/adapter/adaptDispatch.cpp | 44 +++++ src/terminal/adapter/adaptDispatch.hpp | 2 + src/terminal/adapter/termDispatch.hpp | 2 + .../adapter/ut_adapter/adapterTest.cpp | 5 + .../parser/OutputStateMachineEngine.cpp | 5 + .../parser/OutputStateMachineEngine.hpp | 3 +- 40 files changed, 754 insertions(+), 21 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 6db40713c9..5b035f38eb 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -278,6 +278,55 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleScrollToMark(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _ApplyToActiveControls([&realArgs](auto& control) { + control.ScrollToMark(realArgs.Direction()); + }); + } + args.Handled(true); + } + void TerminalPage::_HandleAddMark(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + _ApplyToActiveControls([realArgs](auto& control) { + Control::ScrollMark mark; + if (realArgs.Color()) + { + mark.Color.Color = realArgs.Color().Value(); + mark.Color.HasValue = true; + } + else + { + mark.Color.HasValue = false; + } + control.AddMark(mark); + }); + } + args.Handled(true); + } + void TerminalPage::_HandleClearMark(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + _ApplyToActiveControls([](auto& control) { + control.ClearMark(); + }); + args.Handled(true); + } + void TerminalPage::_HandleClearAllMarks(const IInspectable& /*sender*/, + const ActionEventArgs& args) + { + _ApplyToActiveControls([](auto& control) { + control.ClearAllMarks(); + }); + args.Handled(true); + } + void TerminalPage::_HandleFindMatch(const IInspectable& /*sender*/, const ActionEventArgs& args) { diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 9d98115c91..ec470a31dd 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -37,6 +37,28 @@ constexpr const auto UpdatePatternLocationsInterval = std::chrono::milliseconds( namespace winrt::Microsoft::Terminal::Control::implementation { + static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const til::color& c) + { + Core::OptionalColor result; + result.Color = c; + result.HasValue = true; + return result; + } + static winrt::Microsoft::Terminal::Core::OptionalColor OptionalFromColor(const std::optional& c) + { + Core::OptionalColor result; + if (c.has_value()) + { + result.Color = *c; + result.HasValue = true; + } + else + { + result.HasValue = false; + } + return result; + } + // Helper static function to ensure that all ambiguous-width glyphs are reported as narrow. // See microsoft/terminal#2066 for more info. static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) @@ -1196,6 +1218,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation const int viewHeight, const int bufferSize) { + if (!_initializedTerminal) + { + return; + } // Clear the regex pattern tree so the renderer does not try to render them while scrolling // We're **NOT** taking the lock here unlike _scrollbarChangeHandler because // we are already under lock (since this usually happens as a result of writing). @@ -1854,4 +1880,140 @@ namespace winrt::Microsoft::Terminal::Control::implementation // transparency, or our acrylic, or our image. return Opacity() < 1.0f || UseAcrylic() || !_settings->BackgroundImage().empty() || _settings->UseBackgroundImageForWindow(); } + + Windows::Foundation::Collections::IVector ControlCore::ScrollMarks() const + { + auto internalMarks{ _terminal->GetScrollMarks() }; + auto v = winrt::single_threaded_observable_vector(); + for (const auto& mark : internalMarks) + { + Control::ScrollMark m{}; + + // sneaky: always evaluate the color of the mark to a real value + // before shoving it into the optional. If the mark doesn't have a + // specific color set, we'll use the value from the color table + // that's appropriate for this category of mark. If we do have a + // color set, then great we'll use that. The TermControl can then + // always use the value in the Mark regardless if it was actually + // set or not. + m.Color = OptionalFromColor(_terminal->GetColorForMark(mark)); + m.Start = mark.start.to_core_point(); + m.End = mark.end.to_core_point(); + + v.Append(m); + } + + return v; + } + + void ControlCore::AddMark(const Control::ScrollMark& mark) + { + ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark m{}; + + if (mark.Color.HasValue) + { + m.color = til::color{ mark.Color.Color }; + } + + if (HasSelection()) + { + m.start = til::point{ _terminal->GetSelectionAnchor() }; + m.end = til::point{ _terminal->GetSelectionEnd() }; + } + else + { + m.start = m.end = til::point{ _terminal->GetTextBuffer().GetCursor().GetPosition() }; + } + + // The version of this that only accepts a ScrollMark will automatically + // set the start & end to the cursor position. + _terminal->AddMark(m, m.start, m.end); + } + void ControlCore::ClearMark() { _terminal->ClearMark(); } + void ControlCore::ClearAllMarks() { _terminal->ClearAllMarks(); } + + void ControlCore::ScrollToMark(const Control::ScrollToMarkDirection& direction) + { + const auto currentOffset = ScrollOffset(); + const auto& marks{ _terminal->GetScrollMarks() }; + + std::optional tgt; + + switch (direction) + { + case ScrollToMarkDirection::Last: + { + int highest = currentOffset; + for (const auto& mark : marks) + { + const auto newY = mark.start.y; + if (newY > highest) + { + tgt = mark; + highest = newY; + } + } + break; + } + case ScrollToMarkDirection::First: + { + int lowest = currentOffset; + for (const auto& mark : marks) + { + const auto newY = mark.start.y; + if (newY < lowest) + { + tgt = mark; + lowest = newY; + } + } + break; + } + case ScrollToMarkDirection::Next: + { + int minDistance = INT_MAX; + for (const auto& mark : marks) + { + const auto delta = mark.start.y - currentOffset; + if (delta > 0 && delta < minDistance) + { + tgt = mark; + minDistance = delta; + } + } + break; + } + case ScrollToMarkDirection::Previous: + default: + { + int minDistance = INT_MAX; + for (const auto& mark : marks) + { + const auto delta = currentOffset - mark.start.y; + if (delta > 0 && delta < minDistance) + { + tgt = mark; + minDistance = delta; + } + } + break; + } + } + + if (tgt.has_value()) + { + UserScrollViewport(tgt->start.y); + } + else + { + if (direction == ScrollToMarkDirection::Last || direction == ScrollToMarkDirection::Next) + { + UserScrollViewport(BufferHeight()); + } + else if (direction == ScrollToMarkDirection::First || direction == ScrollToMarkDirection::Previous) + { + UserScrollViewport(0); + } + } + } } diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index b3c8afa8a9..5f20493db1 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -119,6 +119,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation int BufferHeight() const; bool BracketedPasteEnabled() const noexcept; + + Windows::Foundation::Collections::IVector ScrollMarks() const; + void AddMark(const Control::ScrollMark& mark); + void ClearMark(); + void ClearAllMarks(); + void ScrollToMark(const Control::ScrollToMarkDirection& direction); + #pragma endregion #pragma region ITerminalInput diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index ac47a72955..13bd83c855 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -22,7 +22,6 @@ namespace Microsoft.Terminal.Control IsRightButtonDown = 0x4 }; - enum ClearBufferType { Screen, diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2fb8c59851..66ea5a3362 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -56,6 +56,7 @@ namespace Microsoft.Terminal.Control // Experimental Settings Boolean ForceFullRepaintRendering { get; }; Boolean SoftwareRendering { get; }; + Boolean ShowMarks { get; }; Boolean UseBackgroundImageForWindow { get; }; }; } diff --git a/src/cascadia/TerminalControl/ICoreState.idl b/src/cascadia/TerminalControl/ICoreState.idl index fd11bda8f1..4d4d2e2e4e 100644 --- a/src/cascadia/TerminalControl/ICoreState.idl +++ b/src/cascadia/TerminalControl/ICoreState.idl @@ -3,6 +3,32 @@ namespace Microsoft.Terminal.Control { + enum MarkCategory + { + Prompt = 0, + Error = 1, + Warning = 2, + Info = 3 + }; + + struct ScrollMark + { + // There are other members of DispatchTypes::ScrollMark, but these are + // all we need to expose up and set downwards currently. Additional + // members can be bubbled as necessary. + Microsoft.Terminal.Core.Point Start; + Microsoft.Terminal.Core.Point End; // exclusive + Microsoft.Terminal.Core.OptionalColor Color; + }; + + enum ScrollToMarkDirection + { + Previous, + Next, + First, + Last + }; + // These are properties of the TerminalCore that should be queryable by the // rest of the app. interface ICoreState @@ -27,5 +53,11 @@ namespace Microsoft.Terminal.Control UInt64 OwningHwnd; + void AddMark(ScrollMark mark); + void ClearMark(); + void ClearAllMarks(); + void ScrollToMark(ScrollToMarkDirection direction); + IVector ScrollMarks { get; }; + }; } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 5fe54d6acc..53a8f9f778 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -124,20 +124,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation [weakThis = get_weak()](const auto& update) { if (auto control{ weakThis.get() }; !control->_IsClosing()) { - control->_isInternalScrollBarUpdate = true; - - auto scrollBar = control->ScrollBar(); - if (update.newValue) - { - scrollBar.Value(*update.newValue); - } - scrollBar.Maximum(update.newMaximum); - scrollBar.Minimum(update.newMinimum); - scrollBar.ViewportSize(update.newViewportSize); - // scroll one full screen worth at a time when the scroll bar is clicked - scrollBar.LargeChange(std::max(update.newViewportSize - 1, 0.)); - - control->_isInternalScrollBarUpdate = false; + control->_throttledUpdateScrollbar(update); } }); @@ -148,6 +135,57 @@ namespace winrt::Microsoft::Terminal::Control::implementation _ApplyUISettings(); } + void TermControl::_throttledUpdateScrollbar(const ScrollBarUpdate& update) + { + // Assumptions: + // * we're already not closing + // * caller already checked weak ptr to make sure we're still alive + + _isInternalScrollBarUpdate = true; + + auto scrollBar = ScrollBar(); + if (update.newValue) + { + scrollBar.Value(*update.newValue); + } + scrollBar.Maximum(update.newMaximum); + scrollBar.Minimum(update.newMinimum); + scrollBar.ViewportSize(update.newViewportSize); + // scroll one full screen worth at a time when the scroll bar is clicked + scrollBar.LargeChange(std::max(update.newViewportSize - 1, 0.)); + + _isInternalScrollBarUpdate = false; + + if (_showMarksInScrollbar) + { + // Update scrollbar marks + ScrollBarCanvas().Children().Clear(); + const auto marks{ _core.ScrollMarks() }; + const auto fullHeight{ ScrollBarCanvas().ActualHeight() }; + const auto totalBufferRows{ update.newMaximum + update.newViewportSize }; + + for (const auto m : marks) + { + Windows::UI::Xaml::Shapes::Rectangle r; + Media::SolidColorBrush brush{}; + // Sneaky: technically, a mark doesn't need to have a color set, + // it might want to just use the color from the palette for that + // kind of mark. Fortunately, ControlCore is kind enough to + // pre-evaluate that for us, and shove the real value into the + // Color member, regardless if the mark has a literal value set. + brush.Color(static_cast(m.Color.Color)); + r.Fill(brush); + r.Width(16.0f / 3.0f); // pip width - 1/3rd of the scrollbar width. + r.Height(2); + const auto markRow = m.Start.Y; + const auto fractionalHeight = markRow / totalBufferRows; + const auto relativePos = fractionalHeight * fullHeight; + ScrollBarCanvas().Children().Append(r); + Windows::UI::Xaml::Controls::Canvas::SetTop(r, relativePos); + } + } + } + // Method Description: // - Loads the search box from the xaml UI and focuses it. void TermControl::CreateSearchBoxControl() @@ -404,6 +442,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation newMargin.Right, newMargin.Bottom }); } + + _showMarksInScrollbar = settings.ShowMarks(); + // Clear out all the current marks + ScrollBarCanvas().Children().Clear(); + // When we hot reload the settings, the core will send us a scrollbar + // update. If we enabled scrollbar marks, then great, when we handle + // that message, we'll redraw them. } // Method Description: @@ -2863,4 +2908,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation return _core.OwningHwnd(); } + void TermControl::AddMark(const Control::ScrollMark& mark) + { + _core.AddMark(mark); + } + void TermControl::ClearMark() { _core.ClearMark(); } + void TermControl::ClearAllMarks() { _core.ClearAllMarks(); } + void TermControl::ScrollToMark(const Control::ScrollToMarkDirection& direction) { _core.ScrollToMark(direction); } + + Windows::Foundation::Collections::IVector TermControl::ScrollMarks() const + { + return _core.ScrollMarks(); + } + } diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 8816472e01..de091b6748 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -67,6 +67,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation uint64_t OwningHwnd(); void OwningHwnd(uint64_t owner); + + Windows::Foundation::Collections::IVector ScrollMarks() const; + void AddMark(const Control::ScrollMark& mark); + void ClearMark(); + void ClearAllMarks(); + void ScrollToMark(const Control::ScrollToMarkDirection& direction); + #pragma endregion void ScrollViewport(int viewTop); @@ -195,6 +202,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation std::optional _blinkTimer; winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; + bool _showMarksInScrollbar{ false }; inline bool _IsClosing() const noexcept { @@ -287,6 +295,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); void _coreFoundMatch(const IInspectable& sender, const Control::FoundResultsArgs& args); + void _throttledUpdateScrollbar(const ScrollBarUpdate& update); }; } diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index 94afdb9c43..dcb24c353b 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -1243,6 +1243,30 @@ Style="{StaticResource ForkedScrollbarTemplate}" ValueChanged="_ScrollbarChangeHandler" ViewportSize="10" /> + + + + + + + + + + + + + + #include #include +#include #include #include diff --git a/src/cascadia/TerminalCore/ICoreAppearance.idl b/src/cascadia/TerminalCore/ICoreAppearance.idl index 131410be78..5e5ed3ff72 100644 --- a/src/cascadia/TerminalCore/ICoreAppearance.idl +++ b/src/cascadia/TerminalCore/ICoreAppearance.idl @@ -24,6 +24,15 @@ namespace Microsoft.Terminal.Core UInt8 A; }; + // Yes, this is also just an IReference. However, IReference has some + // weird ownership semantics that just make it a pain for something as + // simple as "maybe this color doesn't have a value set". + struct OptionalColor + { + Boolean HasValue; + Microsoft.Terminal.Core.Color Color; + }; + // TerminalCore declares its own Color struct to avoid depending on // Windows.UI. Windows.Foundation.Point also exists, but it's composed of // floating-point coordinates, when we almost always need integer coordinates. diff --git a/src/cascadia/TerminalCore/ICoreSettings.idl b/src/cascadia/TerminalCore/ICoreSettings.idl index 5b05a72c05..1f3c78aa09 100644 --- a/src/cascadia/TerminalCore/ICoreSettings.idl +++ b/src/cascadia/TerminalCore/ICoreSettings.idl @@ -26,6 +26,9 @@ namespace Microsoft.Terminal.Core Windows.Foundation.IReference TabColor; Windows.Foundation.IReference StartingTabColor; + + Boolean AutoMarkPrompts; + }; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index bc4b01e608..cead8dd5d8 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -49,7 +49,8 @@ Terminal::Terminal() : _selection{ std::nullopt }, _taskbarState{ 0 }, _taskbarProgress{ 0 }, - _trimBlockSelection{ false } + _trimBlockSelection{ false }, + _autoMarkPrompts{ false } { auto passAlongInput = [&](std::deque>& inEventsToWrite) { if (!_pfnWriteInput) @@ -121,6 +122,7 @@ void Terminal::UpdateSettings(ICoreSettings settings) _suppressApplicationTitle = settings.SuppressApplicationTitle(); _startingTitle = settings.StartingTitle(); _trimBlockSelection = settings.TrimBlockSelection(); + _autoMarkPrompts = settings.AutoMarkPrompts(); _terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput()); @@ -211,6 +213,11 @@ void Terminal::UpdateAppearance(const ICoreAppearance& appearance) } _defaultCursorShape = cursorShape; + + // Tell the control that the scrollbar has somehow changed. Used as a + // workaround to force the control to redraw any scrollbar marks whose color + // may have changed. + _NotifyScrollEvent(); } void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) @@ -750,6 +757,22 @@ bool Terminal::SendCharEvent(const wchar_t ch, const WORD scanCode, const Contro vkey = _VirtualKeyFromCharacter(ch); } + // GH#1527: When the user has auto mark prompts enabled, we're going to try + // and heuristically detect if this was the line the prompt was on. + // * If the key was an Enter keypress (Terminal.app also marks ^C keypresses + // as prompts. That's omitted for now.) + // * AND we're not in the alt buffer + // + // Then treat this line like it's a prompt mark. + if (_autoMarkPrompts && vkey == VK_RETURN && !_inAltBuffer()) + { + DispatchTypes::ScrollMark mark; + mark.category = DispatchTypes::MarkCategory::Prompt; + // Don't set the color - we'll automatically use the DEFAULT_FOREGROUND + // color for any MarkCategory::Prompt marks without one set. + AddMark(mark); + } + // Unfortunately, the UI doesn't give us both a character down and a // character up event, only a character received event. So fake sending both // to the terminal input translator. Unless it's in win32-input-mode, it'll @@ -1198,6 +1221,17 @@ void Terminal::_AdjustCursorPosition(const til::point proposedPosition) if (rowsPushedOffTopOfBuffer != 0) { + if (_scrollMarks.size() > 0) + { + for (auto& mark : _scrollMarks) + { + mark.start.y -= rowsPushedOffTopOfBuffer; + } + _scrollMarks.erase(std::remove_if(_scrollMarks.begin(), + _scrollMarks.end(), + [](const VirtualTerminal::DispatchTypes::ScrollMark& m) { return m.start.y < 0; }), + _scrollMarks.end()); + } // We have to report the delta here because we might have circled the text buffer. // That didn't change the viewport and therefore the TriggerScroll(void) // method can't detect the delta on its own. @@ -1450,6 +1484,11 @@ void Terminal::ApplyScheme(const Scheme& colorScheme) _renderSettings.SetColorTableEntry(TextColor::CURSOR_COLOR, til::color{ colorScheme.CursorColor }); _renderSettings.MakeAdjustedColorArray(); + + // Tell the control that the scrollbar has somehow changed. Used as a + // workaround to force the control to redraw any scrollbar marks whose color + // may have changed. + _NotifyScrollEvent(); } bool Terminal::_inAltBuffer() const noexcept @@ -1476,3 +1515,101 @@ void Terminal::_updateUrlDetection() ClearPatternTree(); } } + +void Terminal::AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark, + const til::point& start, + const til::point& end) +{ + if (_inAltBuffer()) + { + return; + } + + DispatchTypes::ScrollMark m = mark; + m.start = start; + m.end = end; + + _scrollMarks.push_back(m); + + // Tell the control that the scrollbar has somehow changed. Used as a + // workaround to force the control to redraw any scrollbar marks + _NotifyScrollEvent(); +} + +void Terminal::ClearMark() +{ + // Look for one where the cursor is, or where the selection is if we have + // one. Any mark that intersects the cursor/selection, on either side + // (inclusive), will get cleared. + const til::point cursor{ _activeBuffer().GetCursor().GetPosition() }; + til::point start{ cursor }; + til::point end{ cursor }; + + if (IsSelectionActive()) + { + start = til::point{ GetSelectionAnchor() }; + end = til::point{ GetSelectionEnd() }; + } + + _scrollMarks.erase(std::remove_if(_scrollMarks.begin(), + _scrollMarks.end(), + [&start, &end](const auto& m) { + return (m.start >= start && m.start <= end) || + (m.end >= start && m.end <= end); + }), + _scrollMarks.end()); + + // Tell the control that the scrollbar has somehow changed. Used as a + // workaround to force the control to redraw any scrollbar marks + _NotifyScrollEvent(); +} +void Terminal::ClearAllMarks() +{ + _scrollMarks.clear(); + // Tell the control that the scrollbar has somehow changed. Used as a + // workaround to force the control to redraw any scrollbar marks + _NotifyScrollEvent(); +} + +const std::vector& Terminal::GetScrollMarks() const +{ + // TODO: GH#11000 - when the marks are stored per-buffer, get rid of this. + // We want to return _no_ marks when we're in the alt buffer, to effectively + // hide them. We need to return a reference, so we can't just ctor an empty + // list here just for when we're in the alt buffer. + static std::vector _altBufferMarks{}; + return _inAltBuffer() ? _altBufferMarks : _scrollMarks; +} + +til::color Terminal::GetColorForMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) const +{ + if (mark.color.has_value()) + { + return *mark.color; + } + + switch (mark.category) + { + case Microsoft::Console::VirtualTerminal::DispatchTypes::MarkCategory::Prompt: + { + return _renderSettings.GetColorAlias(ColorAlias::DefaultForeground); + } + case Microsoft::Console::VirtualTerminal::DispatchTypes::MarkCategory::Error: + { + return _renderSettings.GetColorTableEntry(TextColor::BRIGHT_RED); + } + case Microsoft::Console::VirtualTerminal::DispatchTypes::MarkCategory::Warning: + { + return _renderSettings.GetColorTableEntry(TextColor::BRIGHT_YELLOW); + } + case Microsoft::Console::VirtualTerminal::DispatchTypes::MarkCategory::Success: + { + return _renderSettings.GetColorTableEntry(TextColor::BRIGHT_GREEN); + } + default: + case Microsoft::Console::VirtualTerminal::DispatchTypes::MarkCategory::Info: + { + return _renderSettings.GetColorAlias(ColorAlias::DefaultForeground); + } + } +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 509f2c326c..ba4c43d34c 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -102,6 +102,11 @@ public: RenderSettings& GetRenderSettings() noexcept { return _renderSettings; }; const RenderSettings& GetRenderSettings() const noexcept { return _renderSettings; }; + const std::vector& GetScrollMarks() const; + void AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark, + const til::point& start, + const til::point& end); + #pragma region ITerminalApi // These methods are defined in TerminalApi.cpp void PrintString(const std::wstring_view string) override; @@ -129,11 +134,18 @@ public: void ShowWindow(bool showOrHide) override; void UseAlternateScreenBuffer() override; void UseMainScreenBuffer() override; + + void AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) override; + bool IsConsolePty() const override; bool IsVtInputEnabled() const override; void NotifyAccessibilityChange(const til::rect& changedRect) override; #pragma endregion + void ClearMark(); + void ClearAllMarks(); + til::color GetColorForMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) const; + #pragma region ITerminalInput // These methods are defined in Terminal.cpp bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; @@ -290,6 +302,7 @@ private: bool _suppressApplicationTitle; bool _bracketedPasteMode; bool _trimBlockSelection; + bool _autoMarkPrompts; size_t _taskbarState; size_t _taskbarProgress; @@ -356,6 +369,8 @@ private: }; std::optional _lastKeyEventCodes; + std::vector _scrollMarks; + static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept; static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept; static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept; diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 06df2d809c..01894b0522 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -298,6 +298,12 @@ void Terminal::UseMainScreenBuffer() CATCH_LOG(); } +void Terminal::AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) +{ + const til::point cursorPos{ _activeBuffer().GetCursor().GetPosition() }; + AddMark(mark, cursorPos, cursorPos); +} + // Method Description: // - Reacts to a client asking us to show or hide the window. // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 148d77fb37..1b49cc3dd4 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -39,6 +39,10 @@ static constexpr std::string_view ScrollUpKey{ "scrollUp" }; static constexpr std::string_view ScrollUpPageKey{ "scrollUpPage" }; static constexpr std::string_view ScrollToTopKey{ "scrollToTop" }; static constexpr std::string_view ScrollToBottomKey{ "scrollToBottom" }; +static constexpr std::string_view ScrollToMarkKey{ "scrollToMark" }; +static constexpr std::string_view AddMarkKey{ "addMark" }; +static constexpr std::string_view ClearMarkKey{ "clearMark" }; +static constexpr std::string_view ClearAllMarksKey{ "clearAllMarks" }; static constexpr std::string_view SendInputKey{ "sendInput" }; static constexpr std::string_view SetColorSchemeKey{ "setColorScheme" }; static constexpr std::string_view SetTabColorKey{ "setTabColor" }; @@ -354,6 +358,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") }, { ShortcutAction::ScrollToTop, RS_(L"ScrollToTopCommandKey") }, { ShortcutAction::ScrollToBottom, RS_(L"ScrollToBottomCommandKey") }, + { ShortcutAction::ScrollToMark, RS_(L"ScrollToPreviousMarkCommandKey") }, + { ShortcutAction::AddMark, RS_(L"AddMarkCommandKey") }, + { ShortcutAction::ClearMark, RS_(L"ClearMarkCommandKey") }, + { ShortcutAction::ClearAllMarks, RS_(L"ClearAllMarksCommandKey") }, { ShortcutAction::SendInput, L"" }, { ShortcutAction::SetColorScheme, L"" }, { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") }, diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index 42898ee166..3b3c56543a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -29,6 +29,8 @@ #include "CloseTabsAfterArgs.g.cpp" #include "CloseTabArgs.g.cpp" #include "MoveTabArgs.g.cpp" +#include "ScrollToMarkArgs.g.cpp" +#include "AddMarkArgs.g.cpp" #include "FindMatchArgs.g.cpp" #include "ToggleCommandPaletteArgs.g.cpp" #include "NewWindowArgs.g.cpp" @@ -611,6 +613,38 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return RS_(L"ScrollDownCommandKey"); } + winrt::hstring ScrollToMarkArgs::GenerateName() const + { + switch (Direction()) + { + case Microsoft::Terminal::Control::ScrollToMarkDirection::Last: + return winrt::hstring{ RS_(L"ScrollToLastMarkCommandKey") }; + case Microsoft::Terminal::Control::ScrollToMarkDirection::First: + return winrt::hstring{ RS_(L"ScrollToFirstMarkCommandKey") }; + case Microsoft::Terminal::Control::ScrollToMarkDirection::Next: + return winrt::hstring{ RS_(L"ScrollToNextMarkCommandKey") }; + case Microsoft::Terminal::Control::ScrollToMarkDirection::Previous: + default: + return winrt::hstring{ RS_(L"ScrollToPreviousMarkCommandKey") }; + } + return winrt::hstring{ RS_(L"ScrollToPreviousMarkCommandKey") }; + } + + winrt::hstring AddMarkArgs::GenerateName() const + { + if (Color()) + { + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"AddMarkWithColorCommandKey")), + til::color{ Color().Value() }.ToHexString(true)) + }; + } + else + { + return RS_(L"AddMarkCommandKey"); + } + } + winrt::hstring MoveTabArgs::GenerateName() const { winrt::hstring directionString; diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 05bd831a3a..03f6925256 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -30,6 +30,8 @@ #include "CloseTabArgs.g.h" #include "ScrollUpArgs.g.h" #include "ScrollDownArgs.g.h" +#include "ScrollToMarkArgs.g.h" +#include "AddMarkArgs.g.h" #include "MoveTabArgs.g.h" #include "ToggleCommandPaletteArgs.g.h" #include "FindMatchArgs.g.h" @@ -180,6 +182,14 @@ private: #define SCROLL_DOWN_ARGS(X) \ X(Windows::Foundation::IReference, RowsToScroll, "rowsToScroll", false, nullptr) +//////////////////////////////////////////////////////////////////////////////// +#define SCROLL_TO_MARK_ARGS(X) \ + X(Microsoft::Terminal::Control::ScrollToMarkDirection, Direction, "direction", false, Microsoft::Terminal::Control::ScrollToMarkDirection::Previous) + +//////////////////////////////////////////////////////////////////////////////// +#define ADD_MARK_ARGS(X) \ + X(Windows::Foundation::IReference, Color, "color", false, nullptr) + //////////////////////////////////////////////////////////////////////////////// #define TOGGLE_COMMAND_PALETTE_ARGS(X) \ X(CommandPaletteLaunchMode, LaunchMode, "launchMode", false, CommandPaletteLaunchMode::Action) @@ -612,6 +622,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARGS_STRUCT(ScrollDownArgs, SCROLL_DOWN_ARGS); + ACTION_ARGS_STRUCT(ScrollToMarkArgs, SCROLL_TO_MARK_ARGS); + + ACTION_ARGS_STRUCT(AddMarkArgs, ADD_MARK_ARGS); + ACTION_ARGS_STRUCT(ToggleCommandPaletteArgs, TOGGLE_COMMAND_PALETTE_ARGS); ACTION_ARGS_STRUCT(FindMatchArgs, FIND_MATCH_ARGS); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index ddc05c28dc..77c817315b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -290,6 +290,17 @@ namespace Microsoft.Terminal.Settings.Model Windows.Foundation.IReference RowsToScroll { get; }; }; + + [default_interface] runtimeclass ScrollToMarkArgs : IActionArgs + { + Microsoft.Terminal.Control.ScrollToMarkDirection Direction { get; }; + }; + + [default_interface] runtimeclass AddMarkArgs : IActionArgs + { + Windows.Foundation.IReference Color { get; }; + }; + [default_interface] runtimeclass ToggleCommandPaletteArgs : IActionArgs { CommandPaletteLaunchMode LaunchMode { get; }; diff --git a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h index d6a23dc203..c9bd6bd207 100644 --- a/src/cascadia/TerminalSettingsModel/AllShortcutActions.h +++ b/src/cascadia/TerminalSettingsModel/AllShortcutActions.h @@ -47,6 +47,10 @@ ON_ALL_ACTIONS(ScrollDownPage) \ ON_ALL_ACTIONS(ScrollToTop) \ ON_ALL_ACTIONS(ScrollToBottom) \ + ON_ALL_ACTIONS(ScrollToMark) \ + ON_ALL_ACTIONS(AddMark) \ + ON_ALL_ACTIONS(ClearMark) \ + ON_ALL_ACTIONS(ClearAllMarks) \ ON_ALL_ACTIONS(ResizePane) \ ON_ALL_ACTIONS(MoveFocus) \ ON_ALL_ACTIONS(MovePane) \ @@ -119,6 +123,8 @@ ON_ALL_ACTIONS_WITH_ARGS(ResizePane) \ ON_ALL_ACTIONS_WITH_ARGS(ScrollDown) \ ON_ALL_ACTIONS_WITH_ARGS(ScrollUp) \ + ON_ALL_ACTIONS_WITH_ARGS(ScrollToMark) \ + ON_ALL_ACTIONS_WITH_ARGS(AddMark) \ ON_ALL_ACTIONS_WITH_ARGS(SendInput) \ ON_ALL_ACTIONS_WITH_ARGS(SetColorScheme) \ ON_ALL_ACTIONS_WITH_ARGS(SetTabColor) \ diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 5c90f6e5fb..e425bdfd87 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -78,7 +78,9 @@ Author(s): X(bool, UseAtlasEngine, "experimental.useAtlasEngine", false) \ X(Windows::Foundation::Collections::IVector, BellSound, "bellSound", nullptr) \ X(bool, Elevate, "elevate", false) \ - X(bool, VtPassthrough, "experimental.connection.passthroughMode", false) + X(bool, VtPassthrough, "experimental.connection.passthroughMode", false) \ + X(bool, AutoMarkPrompts, "experimental.autoMarkPrompts", false) \ + X(bool, ShowMarks, "experimental.showMarksOnScrollbar", false) // Intentionally omitted Profile settings: // * Name diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index f8f3dc8d1c..76357b6589 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -84,5 +84,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector, BellSound); INHERITABLE_PROFILE_SETTING(Boolean, Elevate); + INHERITABLE_PROFILE_SETTING(Boolean, AutoMarkPrompts); + INHERITABLE_PROFILE_SETTING(Boolean, ShowMarks); } } diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index ec3868faa5..9b7e6fbe66 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -368,6 +368,31 @@ Scroll to the bottom of history + + Scroll to the the previous mark + + + Scroll to the next mark + + + Scroll to the first mark + + + Scroll to the last mark + + + Add a scroll mark + + + Add a scroll mark, color:{0} + {Locked="color"}. "color" is a literal parameter name that shouldn't be translated. {0} will be replaced with a color in hexadecimal format, like `#123456` + + + Clear mark + + + Clear all marks + Send Input: "{0}" {0} will be replaced with a string of input as defined by the user @@ -536,4 +561,4 @@ Toggle block selection - \ No newline at end of file + diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index e8f0aa3d2c..0ba5785730 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -281,6 +281,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } _Elevate = profile.Elevate(); + _AutoMarkPrompts = Feature_ScrollbarMarks::IsEnabled() && profile.AutoMarkPrompts(); + _ShowMarks = Feature_ScrollbarMarks::IsEnabled() && profile.ShowMarks(); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 3b5ca2d085..9786af8aa0 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -158,6 +158,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, Elevate, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, AutoMarkPrompts, false); + INHERITABLE_SETTING(Model::TerminalSettings, bool, ShowMarks, false); + private: std::optional> _ColorTable; gsl::span _getColorTableImpl(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index 65df15cdbd..9dd273ee3c 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -546,3 +546,14 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage) pair_type{ "setAsDefault", ValueType::SetAsDefault }, }; }; + +// Possible ScrollToMarkDirection values +JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection) +{ + JSON_MAPPINGS(4) = { + pair_type{ "previous", ValueType::Previous }, + pair_type{ "next", ValueType::Next }, + pair_type{ "first", ValueType::First }, + pair_type{ "last", ValueType::Last }, + }; +}; diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index f9c880e83d..6d3b42a4db 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -46,7 +46,8 @@ X(bool, ForceVTInput, false) \ X(winrt::hstring, StartingTitle) \ X(bool, DetectURLs, true) \ - X(bool, VtPassthrough, false) + X(bool, VtPassthrough, false) \ + X(bool, AutoMarkPrompts) // --------------------------- Control Settings --------------------------- // All of these settings are defined in IControlSettings. @@ -68,5 +69,6 @@ X(winrt::Microsoft::Terminal::Control::TextAntialiasingMode, AntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode::Grayscale) \ X(bool, ForceFullRepaintRendering, false) \ X(bool, SoftwareRendering, false) \ + X(bool, UseAtlasEngine, false) \ X(bool, UseBackgroundImageForWindow, false) \ - X(bool, UseAtlasEngine, false) + X(bool, ShowMarks, false) diff --git a/src/cascadia/ut_app/TerminalApp.Unit.Tests.manifest b/src/cascadia/ut_app/TerminalApp.Unit.Tests.manifest index 8e1afe241f..8a61b53a59 100644 --- a/src/cascadia/ut_app/TerminalApp.Unit.Tests.manifest +++ b/src/cascadia/ut_app/TerminalApp.Unit.Tests.manifest @@ -27,6 +27,7 @@ PerMonitorV2 true + SegmentHeap diff --git a/src/features.xml b/src/features.xml index 985297e10f..cbe55f3a5a 100644 --- a/src/features.xml +++ b/src/features.xml @@ -116,4 +116,16 @@ Preview + + + Feature_ScrollbarMarks + Enables the experimental scrollbar marks feature. + AlwaysDisabled + + + Dev + Preview + + + diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index e91241336d..900869af2a 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -481,3 +481,8 @@ void ConhostInternalGetSet::NotifyAccessibilityChange(const til::rect& changedRe changedRect.bottom - 1); } } + +void ConhostInternalGetSet::AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& /*mark*/) +{ + // Not implemented for conhost. +} diff --git a/src/host/outputStream.hpp b/src/host/outputStream.hpp index 9a3bc2a0bd..2acd85ae29 100644 --- a/src/host/outputStream.hpp +++ b/src/host/outputStream.hpp @@ -74,6 +74,8 @@ public: void NotifyAccessibilityChange(const til::rect& changedRect) override; + void AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) override; + private: Microsoft::Console::IIoProvider& _io; }; diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index 2ae3b67a6a..afa1de0f79 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -496,4 +496,22 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes constexpr VTInt s_sDECCOLMSetColumns = 132; constexpr VTInt s_sDECCOLMResetColumns = 80; + enum class MarkCategory : size_t + { + Prompt = 0, + Error = 1, + Warning = 2, + Success = 3, + Info = 4 + }; + + struct ScrollMark + { + std::optional color; + til::point start; + til::point end; // exclusive + MarkCategory category{ MarkCategory::Info }; + // Other things we may want to think about in the future are listed in + // GH#11000 + }; } diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index bf6cdbbb93..17c7374c30 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -135,6 +135,8 @@ public: virtual bool DoConEmuAction(const std::wstring_view string) = 0; + virtual bool DoITerm2Action(const std::wstring_view string) = 0; + virtual StringHandler DownloadDRCS(const VTInt fontNumber, const VTParameter startChar, const DispatchTypes::DrcsEraseControl eraseControl, diff --git a/src/terminal/adapter/ITerminalApi.hpp b/src/terminal/adapter/ITerminalApi.hpp index 277a07fafd..3291013356 100644 --- a/src/terminal/adapter/ITerminalApi.hpp +++ b/src/terminal/adapter/ITerminalApi.hpp @@ -76,5 +76,7 @@ namespace Microsoft::Console::VirtualTerminal virtual bool IsConsolePty() const = 0; virtual void NotifyAccessibilityChange(const til::rect& changedRect) = 0; + + virtual void AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& mark) = 0; }; } diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 0531b4aef6..a94b4f0c6a 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -2465,6 +2465,50 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string) return false; } +// Method Description: +// - Performs a iTerm2 action +// - Ascribes to the ITermDispatch interface +// - Currently, the actions we support are: +// * `OSC1337;SetMark`: mark a line as a prompt line +// - Not actually used in conhost +// Arguments: +// - string: contains the parameters that define which action we do +// Return Value: +// - false in conhost, true for the SetMark action, otherwise false. +bool AdaptDispatch::DoITerm2Action(const std::wstring_view string) +{ + // This is not implemented in conhost. + if (_api.IsConsolePty()) + { + // Flush the frame manually, to make sure marks end up on the right line, like the alt buffer sequence. + _renderer.TriggerFlush(false); + return false; + } + + if constexpr (!Feature_ScrollbarMarks::IsEnabled()) + { + return false; + } + + const auto parts = Utils::SplitString(string, L';'); + + if (parts.size() < 1) + { + return false; + } + + const auto action = til::at(parts, 0); + + if (action == L"SetMark") + { + DispatchTypes::ScrollMark mark; + mark.category = DispatchTypes::MarkCategory::Prompt; + _api.AddMark(mark); + return true; + } + return false; +} + // Method Description: // - DECDLD - Downloads one or more characters of a dynamically redefinable // character set (DRCS) with a specified pixel pattern. The pixel array is diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 41e91885e2..ca6a221fc7 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -130,6 +130,8 @@ namespace Microsoft::Console::VirtualTerminal bool DoConEmuAction(const std::wstring_view string) override; + bool DoITerm2Action(const std::wstring_view string) override; + StringHandler DownloadDRCS(const VTInt fontNumber, const VTParameter startChar, const DispatchTypes::DrcsEraseControl eraseControl, diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 6da351861f..49c690aabb 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -128,6 +128,8 @@ public: bool DoConEmuAction(const std::wstring_view /*string*/) override { return false; } + bool DoITerm2Action(const std::wstring_view /*string*/) override { return false; } + StringHandler DownloadDRCS(const VTInt /*fontNumber*/, const VTParameter /*startChar*/, const DispatchTypes::DrcsEraseControl /*eraseControl*/, diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp index 8475f18154..64454ca036 100644 --- a/src/terminal/adapter/ut_adapter/adapterTest.cpp +++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp @@ -237,6 +237,11 @@ public: Log::Comment(L"NotifyAccessibilityChange MOCK called..."); } + void AddMark(const Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark& /*mark*/) override + { + Log::Comment(L"AddMark MOCK called..."); + } + void PrepData() { PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter. diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 5e0a9e14f3..08972476e7 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -837,6 +837,11 @@ bool OutputStateMachineEngine::ActionOscDispatch(const wchar_t /*wch*/, success = _dispatch->DoConEmuAction(string); break; } + case OscActionCodes::ITerm2Action: + { + success = _dispatch->DoITerm2Action(string); + break; + } default: // If no functions to call, overall dispatch was a failure. success = false; diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index 21055d09bc..f37843e249 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -187,7 +187,8 @@ namespace Microsoft::Console::VirtualTerminal SetClipboard = 52, ResetForegroundColor = 110, // Not implemented ResetBackgroundColor = 111, // Not implemented - ResetCursorColor = 112 + ResetCursorColor = 112, + ITerm2Action = 1337, }; bool _GetOscTitle(const std::wstring_view string,