From 19e1be8e0d1946bbff02412ec703be33c7f4fede Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sat, 23 Nov 2019 21:50:58 +1100 Subject: [PATCH] Make wrap_lines a lazy range view Avoid the need to allocate a vector by using the ranges framework. --- src/string_utils.cc | 51 ++++++++++++++++++++++++--------------------- src/string_utils.hh | 35 ++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/string_utils.cc b/src/string_utils.cc index 43e7ef18..3ce98940 100644 --- a/src/string_utils.cc +++ b/src/string_utils.cc @@ -235,58 +235,61 @@ String expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col) return res; } -Vector wrap_lines(StringView text, ColumnCount max_width) +WrapView::Iterator::Iterator(StringView text, ColumnCount max_width) + : m_remaining{text}, m_max_width{max_width} { if (max_width <= 0) throw runtime_error("Invalid max width"); + ++*this; +} +WrapView::Iterator& WrapView::Iterator::operator++() +{ using Utf8It = utf8::iterator; - Utf8It it{text.begin(), text}; - Utf8It end{text.end(), text}; - Utf8It line_begin = it; + Utf8It it{m_remaining.begin(), m_remaining}; Utf8It last_word_end = it; - Vector lines; - while (it != end) + while (it != m_remaining.end()) { const CharCategories cat = categorize(*it, {'_'}); if (cat == CharCategories::EndOfLine) { - lines.emplace_back(line_begin.base(), it.base()); - line_begin = it = it+1; - continue; + m_current = StringView{m_remaining.begin(), it.base()}; + m_remaining = StringView{(it+1).base(), m_remaining.end()}; + return *this; } Utf8It word_end = it+1; - while (word_end != end and categorize(*word_end, {'_'}) == cat) + while (word_end != m_remaining.end() and categorize(*word_end, {'_'}) == cat) ++word_end; - while (word_end > line_begin and - utf8::column_distance(line_begin.base(), word_end.base()) >= max_width) + if (word_end > m_remaining.begin() and + utf8::column_distance(m_remaining.begin(), word_end.base()) >= m_max_width) { - auto line_end = last_word_end <= line_begin ? - Utf8It{utf8::advance(line_begin.base(), text.end(), max_width), text} + auto line_end = last_word_end <= m_remaining.begin() ? + Utf8It{utf8::advance(m_remaining.begin(), m_remaining.end(), m_max_width), m_remaining} : last_word_end; - lines.emplace_back(line_begin.base(), line_end.base()); + m_current = StringView{m_remaining.begin(), line_end.base()}; - while (line_end != end and is_horizontal_blank(*line_end)) + while (line_end != m_remaining.end() and is_horizontal_blank(*line_end)) ++line_end; - if (line_end != end and *line_end == '\n') + if (line_end != m_remaining.end() and *line_end == '\n') ++line_end; - it = line_begin = line_end; + m_remaining = StringView{line_end.base(), m_remaining.end()}; + return *this; } if (cat == CharCategories::Word or cat == CharCategories::Punctuation) last_word_end = word_end; - if (word_end > line_begin) + if (word_end > m_remaining.begin()) it = word_end; } - if (line_begin != end) - lines.emplace_back(line_begin.base(), text.end()); - return lines; + m_current = m_remaining; + m_remaining = StringView{}; + return *this; } template @@ -378,7 +381,7 @@ UnitTest test_string{[]() { kak_assert(String("youpi ") + "matin" == "youpi matin"); - Vector wrapped = wrap_lines("wrap this paragraph\n respecting whitespaces and much_too_long_words", 16); + auto wrapped = "wrap this paragraph\n respecting whitespaces and much_too_long_words" | wrap_at(16) | gather(); kak_assert(wrapped.size() == 6); kak_assert(wrapped[0] == "wrap this"); kak_assert(wrapped[1] == "paragraph"); @@ -387,7 +390,7 @@ UnitTest test_string{[]() kak_assert(wrapped[4] == "much_too_long_wo"); kak_assert(wrapped[5] == "rds"); - Vector wrapped2 = wrap_lines("error: unknown type", 7); + auto wrapped2 = "error: unknown type" | wrap_at(7) | gather(); kak_assert(wrapped2.size() == 3); kak_assert(wrapped2[0] == "error:"); kak_assert(wrapped2[1] == "unknown"); diff --git a/src/string_utils.hh b/src/string_utils.hh index 49f4baeb..6f78b674 100644 --- a/src/string_utils.hh +++ b/src/string_utils.hh @@ -4,6 +4,7 @@ #include "string.hh" #include "enum.hh" #include "vector.hh" +#include "ranges.hh" #include "optional.hh" namespace Kakoune @@ -61,7 +62,39 @@ bool subsequence_match(StringView str, StringView subseq); String expand_tabs(StringView line, ColumnCount tabstop, ColumnCount col = 0); -Vector wrap_lines(StringView text, ColumnCount max_width); +struct WrapView +{ + struct Iterator : std::iterator + { + Iterator(StringView text, ColumnCount max_width); + + Iterator& operator++(); + Iterator operator++(int) { auto copy = *this; ++(*this); return copy; } + + bool operator==(Iterator other) const { return m_remaining == other.m_remaining and m_current == other.m_current; } + bool operator!=(Iterator other) const { return not (*this == other); } + + StringView operator*() { return m_current; } + + private: + StringView m_current; + StringView m_remaining; + ColumnCount m_max_width; + }; + + Iterator begin() const { return {text, max_width}; } + Iterator end() const { return {{}, 1}; } + + StringView text; + ColumnCount max_width; +}; + +inline auto wrap_at(ColumnCount max_width) +{ + return make_view_factory([=](StringView text) { + return WrapView{text, max_width}; + }); +} int str_to_int(StringView str); // throws on error Optional str_to_int_ifp(StringView str);