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. 

<details>
<summary>Settings blob</summary>

```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,
```

</details>
This commit is contained in:
Mike Griese 2022-06-09 16:10:16 -05:00 committed by GitHub
parent 730eb5fafd
commit 799b5d4add
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 754 additions and 21 deletions

View File

@ -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<ScrollToMarkArgs>())
{
_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<AddMarkArgs>())
{
_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)
{

View File

@ -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<til::color>& 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<Control::ScrollMark> ControlCore::ScrollMarks() const
{
auto internalMarks{ _terminal->GetScrollMarks() };
auto v = winrt::single_threaded_observable_vector<Control::ScrollMark>();
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<DispatchTypes::ScrollMark> 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);
}
}
}
}

View File

@ -119,6 +119,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
int BufferHeight() const;
bool BracketedPasteEnabled() const noexcept;
Windows::Foundation::Collections::IVector<Control::ScrollMark> ScrollMarks() const;
void AddMark(const Control::ScrollMark& mark);
void ClearMark();
void ClearAllMarks();
void ScrollToMark(const Control::ScrollToMarkDirection& direction);
#pragma endregion
#pragma region ITerminalInput

View File

@ -22,7 +22,6 @@ namespace Microsoft.Terminal.Control
IsRightButtonDown = 0x4
};
enum ClearBufferType
{
Screen,

View File

@ -56,6 +56,7 @@ namespace Microsoft.Terminal.Control
// Experimental Settings
Boolean ForceFullRepaintRendering { get; };
Boolean SoftwareRendering { get; };
Boolean ShowMarks { get; };
Boolean UseBackgroundImageForWindow { get; };
};
}

View File

@ -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<ScrollMark> ScrollMarks { get; };
};
}

View File

@ -124,9 +124,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
[weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() }; !control->_IsClosing())
{
control->_isInternalScrollBarUpdate = true;
control->_throttledUpdateScrollbar(update);
}
});
auto scrollBar = control->ScrollBar();
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
_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);
@ -137,15 +154,36 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// 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;
_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<til::color>(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);
}
}
});
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
_ApplyUISettings();
}
// Method Description:
@ -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<Control::ScrollMark> TermControl::ScrollMarks() const
{
return _core.ScrollMarks();
}
}

View File

@ -67,6 +67,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
uint64_t OwningHwnd();
void OwningHwnd(uint64_t owner);
Windows::Foundation::Collections::IVector<Control::ScrollMark> 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<Windows::UI::Xaml::DispatcherTimer> _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);
};
}

View File

@ -1243,6 +1243,30 @@
Style="{StaticResource ForkedScrollbarTemplate}"
ValueChanged="_ScrollbarChangeHandler"
ViewportSize="10" />
<Grid x:Name="ScrollMarksGrid"
Grid.Column="1"
Width="{StaticResource ScrollBarSize}"
HorizontalAlignment="Right"
VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Grid.Row="0"
Height="{StaticResource ScrollBarSize}" />
<Canvas x:Name="ScrollBarCanvas"
Grid.Row="1"
Width="{StaticResource ScrollBarSize}"
HorizontalAlignment="Right"
VerticalAlignment="Stretch" />
<Border Grid.Row="2"
Height="{StaticResource ScrollBarSize}" />
</Grid>
</Grid>
<local:TSFInputControl x:Name="TSFInputControl"

View File

@ -45,6 +45,7 @@
#include <winrt/Windows.UI.Xaml.Input.h>
#include <winrt/Windows.UI.Xaml.Interop.h>
#include <winrt/Windows.ui.xaml.markup.h>
#include <winrt/Windows.ui.xaml.shapes.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Windows.Storage.h>

View File

@ -24,6 +24,15 @@ namespace Microsoft.Terminal.Core
UInt8 A;
};
// Yes, this is also just an IReference<Color>. 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.

View File

@ -26,6 +26,9 @@ namespace Microsoft.Terminal.Core
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> TabColor;
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> StartingTabColor;
Boolean AutoMarkPrompts;
};
}

View File

@ -49,7 +49,8 @@ Terminal::Terminal() :
_selection{ std::nullopt },
_taskbarState{ 0 },
_taskbarProgress{ 0 },
_trimBlockSelection{ false }
_trimBlockSelection{ false },
_autoMarkPrompts{ false }
{
auto passAlongInput = [&](std::deque<std::unique_ptr<IInputEvent>>& 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<Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark>& 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<DispatchTypes::ScrollMark> _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);
}
}
}

View File

@ -102,6 +102,11 @@ public:
RenderSettings& GetRenderSettings() noexcept { return _renderSettings; };
const RenderSettings& GetRenderSettings() const noexcept { return _renderSettings; };
const std::vector<Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark>& 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<KeyEventCodes> _lastKeyEventCodes;
std::vector<Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark> _scrollMarks;
static WORD _ScanCodeFromVirtualKey(const WORD vkey) noexcept;
static WORD _VirtualKeyFromScanCode(const WORD scanCode) noexcept;
static WORD _VirtualKeyFromCharacter(const wchar_t ch) noexcept;

View File

@ -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:

View File

@ -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") },

View File

@ -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;

View File

@ -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<uint32_t>, 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<Microsoft::Terminal::Core::Color>, 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);

View File

@ -290,6 +290,17 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.IReference<UInt32> RowsToScroll { get; };
};
[default_interface] runtimeclass ScrollToMarkArgs : IActionArgs
{
Microsoft.Terminal.Control.ScrollToMarkDirection Direction { get; };
};
[default_interface] runtimeclass AddMarkArgs : IActionArgs
{
Windows.Foundation.IReference<Microsoft.Terminal.Core.Color> Color { get; };
};
[default_interface] runtimeclass ToggleCommandPaletteArgs : IActionArgs
{
CommandPaletteLaunchMode LaunchMode { get; };

View File

@ -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) \

View File

@ -78,7 +78,9 @@ Author(s):
X(bool, UseAtlasEngine, "experimental.useAtlasEngine", false) \
X(Windows::Foundation::Collections::IVector<winrt::hstring>, 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

View File

@ -84,5 +84,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Windows.Foundation.Collections.IVector<String>, BellSound);
INHERITABLE_PROFILE_SETTING(Boolean, Elevate);
INHERITABLE_PROFILE_SETTING(Boolean, AutoMarkPrompts);
INHERITABLE_PROFILE_SETTING(Boolean, ShowMarks);
}
}

View File

@ -368,6 +368,31 @@
<data name="ScrollToBottomCommandKey" xml:space="preserve">
<value>Scroll to the bottom of history</value>
</data>
<data name="ScrollToPreviousMarkCommandKey" xml:space="preserve">
<value>Scroll to the the previous mark</value>
</data>
<data name="ScrollToNextMarkCommandKey" xml:space="preserve">
<value>Scroll to the next mark</value>
</data>
<data name="ScrollToFirstMarkCommandKey" xml:space="preserve">
<value>Scroll to the first mark</value>
</data>
<data name="ScrollToLastMarkCommandKey" xml:space="preserve">
<value>Scroll to the last mark</value>
</data>
<data name="AddMarkCommandKey" xml:space="preserve">
<value>Add a scroll mark</value>
</data>
<data name="AddMarkWithColorCommandKey" xml:space="preserve">
<value>Add a scroll mark, color:{0}</value>
<comment>{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`</comment>
</data>
<data name="ClearMarkCommandKey" xml:space="preserve">
<value>Clear mark</value>
</data>
<data name="ClearAllMarksCommandKey" xml:space="preserve">
<value>Clear all marks</value>
</data>
<data name="SendInputCommandKey" xml:space="preserve">
<value>Send Input: "{0}"</value>
<comment>{0} will be replaced with a string of input as defined by the user</comment>

View File

@ -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:

View File

@ -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<std::array<Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE>> _ColorTable;
gsl::span<Microsoft::Terminal::Core::Color> _getColorTableImpl();

View File

@ -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 },
};
};

View File

@ -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)

View File

@ -27,6 +27,7 @@
<windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
</windowsSettings>
</application>
</assembly>

View File

@ -116,4 +116,16 @@
<brandingToken>Preview</brandingToken>
</alwaysDisabledBrandingTokens>
</feature>
<feature>
<name>Feature_ScrollbarMarks</name>
<description>Enables the experimental scrollbar marks feature.</description>
<stage>AlwaysDisabled</stage>
<!-- Did it this way instead of "release tokens" to ensure it won't go into Windows Inbox either... -->
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
<brandingToken>Preview</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>

View File

@ -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.
}

View File

@ -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;
};

View File

@ -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<til::color> 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
};
}

View File

@ -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,

View File

@ -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;
};
}

View File

@ -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

View File

@ -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,

View File

@ -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*/,

View File

@ -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.

View File

@ -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;

View File

@ -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,