Repository: microsoft/terminal (language: cpp)

Issue:
"Color selection" off-by-one regression: it colors 1 cell past the end
### Windows Terminal version

1.23.10732.0

### Windows build number

10.0.26388.1001

### Other Software

_No response_

### Steps to reproduce

I use the "Color Selection" feature a lot, and often the "all matches" variant, which lets you select some text, and then change the fg/bg color for all occurrences of the selected text.

Recently it started coloring one cell past the end of the selection. In the screenshot below, I selected "abcd", but the colorization applies to "abcde" (as well as "abcdeX", demonstrating that the search is not similarly afected):

![Image](https://github.com/user-attachments/assets/6258a18f-d941-443d-9f3e-c7fd6750dbe3)

### Expected Behavior

Just the "abcd" should have been colorized.

### Actual Behavior

Colorization included one character past the end.


Discussion / hints:
As we all know, the two hardest things in computer science are naming things, cache invalidation, and off-by-one errors. :D
Alas, another victim of #18106 

Fix patch:
diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp
index df30a3ffb23..7cd232ad539 100644
--- a/src/buffer/out/textBuffer.cpp
+++ b/src/buffer/out/textBuffer.cpp
@@ -2061,14 +2061,6 @@ void TextBuffer::_ExpandTextRow(til::inclusive_rect& textRow) const
     }
 }
 
-size_t TextBuffer::SpanLength(const til::point coordStart, const til::point coordEnd) const
-{
-    const auto bufferSize = GetSize();
-    // The coords are inclusive, so to get the (inclusive) length we add 1.
-    const auto length = bufferSize.CompareInBounds(coordEnd, coordStart) + 1;
-    return gsl::narrow<size_t>(length);
-}
-
 // Routine Description:
 // - Retrieves the plain text data between the specified coordinates.
 // Arguments:
diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp
index 775417caab0..fca973f50de 100644
--- a/src/buffer/out/textBuffer.hpp
+++ b/src/buffer/out/textBuffer.hpp
@@ -199,8 +199,6 @@ class TextBuffer final
     std::wstring GetCustomIdFromId(uint16_t id) const;
     void CopyHyperlinkMaps(const TextBuffer& OtherBuffer);
 
-    size_t SpanLength(const til::point coordStart, const til::point coordEnd) const;
-
     std::wstring GetPlainText(til::point start, til::point end) const;
 
     struct CopyRequest
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index 0a94352e724..4bd41d58d01 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -2868,6 +2868,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
                 // coloring other matches, then we need to make sure those get redrawn,
                 // too.
                 _renderer->TriggerRedrawAll();
+                _updateSelectionUI();
             }
         }
     }
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 9e6350df044..91def441079 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -1571,10 +1571,10 @@ void Terminal::SerializeMainBuffer(const wchar_t* destination) const
 
 void Terminal::ColorSelection(const TextAttribute& attr, winrt::Microsoft::Terminal::Core::MatchMode matchMode)
 {
-    const auto colorSelection = [this](const til::point coordStart, const til::point coordEnd, const TextAttribute& attr) {
+    const auto colorSelection = [this](const til::point coordStartInclusive, const til::point coordEndExclusive, const TextAttribute& attr) {
         auto& textBuffer = _activeBuffer();
-        const auto spanLength = textBuffer.SpanLength(coordStart, coordEnd);
-        textBuffer.Write(OutputCellIterator(attr, spanLength), coordStart);
+        const auto spanLength = textBuffer.GetSize().CompareInBounds(coordEndExclusive, coordStartInclusive, true);
+        textBuffer.Write(OutputCellIterator(attr, spanLength), coordStartInclusive);
     };
 
     for (const auto [start, end] : _GetSelectionSpans())


Test patch: