Skip to content

Commit

Permalink
Let marks be cleared by clear (and friends) (#15686)
Browse files Browse the repository at this point in the history
Move scroll marks to `TextBuffer`, so they can be cleared by
EraseInDisplay and EraseScrollback.

Also removes the namespacing on them.

## References and Relevant Issues
* see also #11000 and #15057
* Resize/Reflow _doesn't_ work yet and I'm not attempting this here. 

## Validation Steps Performed

* `cls` works
* `Clear-Host` works
* `clear` works
* the "Clear buffer" action works
* They work when there's marks above the current viewport, and clear the
scrollback
* they work if you clear multiple "pages" of output, then scroll back to
where marks previously were
* resizing doesn't totally destroy the marks

Closes #15426
  • Loading branch information
zadjii-msft authored Jul 18, 2023
1 parent 482c724 commit b4042ea
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 127 deletions.
114 changes: 114 additions & 0 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2660,6 +2660,9 @@ try
// Set size back to real size as it will be taking over the rendering duties.
newCursor.SetSize(ulSize);

newBuffer._marks = oldBuffer._marks;
newBuffer._trimMarksOutsideBuffer();

return S_OK;
}
CATCH_RETURN()
Expand Down Expand Up @@ -2869,3 +2872,114 @@ PointTree TextBuffer::GetPatterns(const til::CoordType firstRow, const til::Coor
PointTree result(std::move(intervals));
return result;
}

const std::vector<ScrollMark>& TextBuffer::GetMarks() const noexcept
{
return _marks;
}

// Remove all marks between `start` & `end`, inclusive.
void TextBuffer::ClearMarksInRange(
const til::point start,
const til::point end)
{
auto inRange = [&start, &end](const ScrollMark& m) {
return (m.start >= start && m.start <= end) ||
(m.end >= start && m.end <= end);
};

_marks.erase(std::remove_if(_marks.begin(),
_marks.end(),
inRange),
_marks.end());
}
void TextBuffer::ClearAllMarks() noexcept
{
_marks.clear();
}

// Adjust all the marks in the y-direction by `delta`. Positive values move the
// marks down (the positive y direction). Negative values move up. This will
// trim marks that are no longer have a start in the bounds of the buffer
void TextBuffer::ScrollMarks(const int delta)
{
for (auto& mark : _marks)
{
mark.start.y += delta;

// If the mark had sub-regions, then move those pointers too
if (mark.commandEnd.has_value())
{
(*mark.commandEnd).y += delta;
}
if (mark.outputEnd.has_value())
{
(*mark.outputEnd).y += delta;
}
}
_trimMarksOutsideBuffer();
}

// Method Description:
// - Add a mark to our list of marks, and treat it as the active "prompt". For
// the sake of shell integration, we need to know which mark represents the
// current prompt/command/output. Internally, we'll always treat the _last_
// mark in the list as the current prompt.
// Arguments:
// - m: the mark to add.
void TextBuffer::StartPromptMark(const ScrollMark& m)
{
_marks.push_back(m);
}
// Method Description:
// - Add a mark to our list of marks. Don't treat this as the active prompt.
// This should be used for marks created by the UI or from other user input.
// By inserting at the start of the list, we can separate out marks that were
// generated by client programs vs ones created by the user.
// Arguments:
// - m: the mark to add.
void TextBuffer::AddMark(const ScrollMark& m)
{
_marks.insert(_marks.begin(), m);
}

void TextBuffer::_trimMarksOutsideBuffer()
{
const auto height = GetSize().Height();
_marks.erase(std::remove_if(_marks.begin(),
_marks.end(),
[height](const auto& m) {
return (m.start.y < 0) ||
(m.start.y >= height);
}),
_marks.end());
}

void TextBuffer::SetCurrentPromptEnd(const til::point pos) noexcept
{
if (_marks.empty())
{
return;
}
auto& curr{ _marks.back() };
curr.end = pos;
}
void TextBuffer::SetCurrentCommandEnd(const til::point pos) noexcept
{
if (_marks.empty())
{
return;
}
auto& curr{ _marks.back() };
curr.commandEnd = pos;
}
void TextBuffer::SetCurrentOutputEnd(const til::point pos, ::MarkCategory category) noexcept
{
if (_marks.empty())
{
return;
}
auto& curr{ _marks.back() };
curr.outputEnd = pos;
curr.category = category;
}
48 changes: 48 additions & 0 deletions src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,41 @@ namespace Microsoft::Console::Render
class Renderer;
}

enum class MarkCategory
{
Prompt = 0,
Error = 1,
Warning = 2,
Success = 3,
Info = 4
};
struct ScrollMark
{
std::optional<til::color> color;
til::point start;
til::point end; // exclusive
std::optional<til::point> commandEnd;
std::optional<til::point> outputEnd;

MarkCategory category{ MarkCategory::Info };
// Other things we may want to think about in the future are listed in
// GH#11000

bool HasCommand() const noexcept
{
return commandEnd.has_value() && *commandEnd != end;
}
bool HasOutput() const noexcept
{
return outputEnd.has_value() && *outputEnd != *commandEnd;
}
std::pair<til::point, til::point> GetExtent() const
{
til::point realEnd{ til::coalesce_value(outputEnd, commandEnd, end) };
return std::make_pair(til::point{ start }, realEnd);
}
};

class TextBuffer final
{
public:
Expand Down Expand Up @@ -228,6 +263,16 @@ class TextBuffer final
void CopyPatterns(const TextBuffer& OtherBuffer);
interval_tree::IntervalTree<til::point, size_t> GetPatterns(const til::CoordType firstRow, const til::CoordType lastRow) const;

const std::vector<ScrollMark>& GetMarks() const noexcept;
void ClearMarksInRange(const til::point start, const til::point end);
void ClearAllMarks() noexcept;
void ScrollMarks(const int delta);
void StartPromptMark(const ScrollMark& m);
void AddMark(const ScrollMark& m);
void SetCurrentPromptEnd(const til::point pos) noexcept;
void SetCurrentCommandEnd(const til::point pos) noexcept;
void SetCurrentOutputEnd(const til::point pos, ::MarkCategory category) noexcept;

private:
void _reserve(til::size screenBufferSize, const TextAttribute& defaultAttributes);
void _commit(const std::byte* row);
Expand All @@ -251,6 +296,7 @@ class TextBuffer final
til::point _GetWordEndForAccessibility(const til::point target, const std::wstring_view wordDelimiters, const til::point limit) const;
til::point _GetWordEndForSelection(const til::point target, const std::wstring_view wordDelimiters) const;
void _PruneHyperlinks();
void _trimMarksOutsideBuffer();

static void _AppendRTFText(std::ostringstream& contentBuilder, const std::wstring_view& text);

Expand Down Expand Up @@ -327,6 +373,8 @@ class TextBuffer final

bool _isActiveBuffer = false;

std::vector<ScrollMark> _marks;

#ifdef UNIT_TESTING
friend class TextBufferTests;
friend class UiaTextRangeTests;
Expand Down
26 changes: 13 additions & 13 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2111,7 +2111,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void ControlCore::AddMark(const Control::ScrollMark& mark)
{
::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark m{};
::ScrollMark m{};

if (mark.Color.HasValue)
{
Expand Down Expand Up @@ -2140,7 +2140,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto currentOffset = ScrollOffset();
const auto& marks{ _terminal->GetScrollMarks() };

std::optional<DispatchTypes::ScrollMark> tgt;
std::optional<::ScrollMark> tgt;

switch (direction)
{
Expand Down Expand Up @@ -2243,7 +2243,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
_terminal->GetTextBuffer().GetCursor().GetPosition();

std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
std::optional<::ScrollMark> nearest{ std::nullopt };
const auto& marks{ _terminal->GetScrollMarks() };

// Early return so we don't have to check for the validity of `nearest` below after the loop exits.
Expand Down Expand Up @@ -2283,7 +2283,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const til::point start = HasSelection() ? (goUp ? _terminal->GetSelectionAnchor() : _terminal->GetSelectionEnd()) :
_terminal->GetTextBuffer().GetCursor().GetPosition();

std::optional<DispatchTypes::ScrollMark> nearest{ std::nullopt };
std::optional<::ScrollMark> nearest{ std::nullopt };
const auto& marks{ _terminal->GetScrollMarks() };

static constexpr til::point worst{ til::CoordTypeMax, til::CoordTypeMax };
Expand Down Expand Up @@ -2357,8 +2357,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void ControlCore::_contextMenuSelectMark(
const til::point& pos,
bool (*filter)(const DispatchTypes::ScrollMark&),
til::point_span (*getSpan)(const DispatchTypes::ScrollMark&))
bool (*filter)(const ::ScrollMark&),
til::point_span (*getSpan)(const ::ScrollMark&))
{
// Do nothing if the caller didn't give us a way to get the span to select for this mark.
if (!getSpan)
Expand Down Expand Up @@ -2391,20 +2391,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_contextMenuSelectMark(
_contextMenuBufferPosition,
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasCommand(); },
[](const DispatchTypes::ScrollMark& m) { return til::point_span{ m.end, *m.commandEnd }; });
[](const ::ScrollMark& m) -> bool { return !m.HasCommand(); },
[](const ::ScrollMark& m) { return til::point_span{ m.end, *m.commandEnd }; });
}
void ControlCore::ContextMenuSelectOutput()
{
_contextMenuSelectMark(
_contextMenuBufferPosition,
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasOutput(); },
[](const DispatchTypes::ScrollMark& m) { return til::point_span{ *m.commandEnd, *m.outputEnd }; });
[](const ::ScrollMark& m) -> bool { return !m.HasOutput(); },
[](const ::ScrollMark& m) { return til::point_span{ *m.commandEnd, *m.outputEnd }; });
}

bool ControlCore::_clickedOnMark(
const til::point& pos,
bool (*filter)(const DispatchTypes::ScrollMark&))
bool (*filter)(const ::ScrollMark&))
{
// Don't show this if the click was on the selection
if (_terminal->IsSelectionActive() &&
Expand Down Expand Up @@ -2442,7 +2442,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// Relies on the anchor set in AnchorContextMenu
return _clickedOnMark(_contextMenuBufferPosition,
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasCommand(); });
[](const ::ScrollMark& m) -> bool { return !m.HasCommand(); });
}

// Method Description:
Expand All @@ -2451,6 +2451,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// Relies on the anchor set in AnchorContextMenu
return _clickedOnMark(_contextMenuBufferPosition,
[](const DispatchTypes::ScrollMark& m) -> bool { return !m.HasOutput(); });
[](const ::ScrollMark& m) -> bool { return !m.HasOutput(); });
}
}
6 changes: 3 additions & 3 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void _contextMenuSelectMark(
const til::point& pos,
bool (*filter)(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&),
til::point_span (*getSpan)(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&));
bool (*filter)(const ::ScrollMark&),
til::point_span (*getSpan)(const ::ScrollMark&));

bool _clickedOnMark(const til::point& pos, bool (*filter)(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::ScrollMark&));
bool _clickedOnMark(const til::point& pos, bool (*filter)(const ::ScrollMark&));

inline bool _IsClosing() const noexcept
{
Expand Down
Loading

0 comments on commit b4042ea

Please sign in to comment.