Skip to content

Commit

Permalink
Add text based cursor movement helpers (#15779)
Browse files Browse the repository at this point in the history
`COOKED_READ_DATA` is a little special and requires cursor navigation
based on the raw (buffered) text contents instead of what's in the
text buffer. This requires the introduction of new helper functions
to implement such cursor navigation. They're made part of `TextBuffer`
as these helpers will get support graphemes in the future.
It also helps keeping it close to `TextBuffer` as the cursor
navigation should optimally behave identical between the two.

Part of #8000.
  • Loading branch information
lhecker authored Aug 1, 2023
1 parent 57b9549 commit a4340af
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 22 deletions.
17 changes: 13 additions & 4 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,20 @@ void TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
}
}

void TextBuffer::ConsumeGrapheme(std::wstring_view& chars) noexcept
// Given the character offset `position` in the `chars` string, this function returns the starting position of the next grapheme.
// For instance, given a `chars` of L"x\uD83D\uDE42y" and a `position` of 1 it'll return 3.
// GraphemePrev would do the exact inverse of this operation.
// In the future, these functions are expected to also deliver information about how many columns a grapheme occupies.
// (I know that mere UTF-16 code point iteration doesn't handle graphemes, but that's what we're working towards.)
size_t TextBuffer::GraphemeNext(const std::wstring_view& chars, size_t position) noexcept
{
// This function is supposed to mirror the behavior of ROW::Write, when it reads characters off of `chars`.
// (I know that a UTF-16 code point is not a grapheme, but that's what we're working towards.)
chars = til::utf16_pop(chars);
return til::utf16_iterate_next(chars, position);
}

// It's the counterpart to GraphemeNext. See GraphemeNext.
size_t TextBuffer::GraphemePrev(const std::wstring_view& chars, size_t position) noexcept
{
return til::utf16_iterate_prev(chars, position);
}

// This function is intended for writing regular "lines" of text as it'll set the wrap flag on the given row.
Expand Down
4 changes: 3 additions & 1 deletion src/buffer/out/textBuffer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ class TextBuffer final
TextBufferTextIterator GetTextLineDataAt(const til::point at) const;
TextBufferTextIterator GetTextDataAt(const til::point at, const Microsoft::Console::Types::Viewport limit) const;

static size_t GraphemeNext(const std::wstring_view& chars, size_t position) noexcept;
static size_t GraphemePrev(const std::wstring_view& chars, size_t position) noexcept;

// Text insertion functions
static void ConsumeGrapheme(std::wstring_view& chars) noexcept;
void Write(til::CoordType row, const TextAttribute& attributes, RowWriteState& state);
void FillRect(const til::rect& rect, const std::wstring_view& fill, const TextAttribute& attributes);

Expand Down
34 changes: 18 additions & 16 deletions src/inc/til/unicode.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,30 @@ namespace til
return { ptr, len };
}

// Removes the first code point off of `wstr` and returns the rest.
constexpr std::wstring_view utf16_pop(std::wstring_view wstr) noexcept
// Returns the index of the next codepoint in the given wstr (i.e. after the codepoint that idx points at).
constexpr size_t utf16_iterate_next(const std::wstring_view& wstr, size_t idx) noexcept
{
auto it = wstr.begin();
const auto end = wstr.end();

if (it != end)
if (idx < wstr.size() && is_leading_surrogate(til::at(wstr, idx++)))
{
const auto wch = *it;
++it;

if (is_surrogate(wch))
if (idx < wstr.size() && is_trailing_surrogate(til::at(wstr, idx)))
{
const auto wch2 = it != end ? *it : wchar_t{};
if (is_leading_surrogate(wch) && is_trailing_surrogate(wch2))
{
++it;
}
++idx;
}
}
return idx;
}

return { it, end };
// Returns the index of the preceding codepoint in the given wstr (i.e. in front of the codepoint that idx points at).
constexpr size_t utf16_iterate_prev(const std::wstring_view& wstr, size_t idx) noexcept
{
if (idx > 0 && is_trailing_surrogate(til::at(wstr, --idx)))
{
if (idx > 0 && is_leading_surrogate(til::at(wstr, idx - 1)))
{
--idx;
}
}
return idx;
}

// Splits a UTF16 string into codepoints, yielding `wstring_view`s of UTF16 text. Use it as:
Expand Down
2 changes: 1 addition & 1 deletion src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
// we tried writing a wide glyph into the last column which can't work.
if (textPositionBefore == textPositionAfter && (state.columnBegin == 0 || !wrapAtEOL))
{
textBuffer.ConsumeGrapheme(state.text);
state.text = state.text.substr(textBuffer.GraphemeNext(state.text, 0));
}

if (wrapAtEOL)
Expand Down

0 comments on commit a4340af

Please sign in to comment.