diff --git a/AK/StringView.cpp b/AK/StringView.cpp index 83892bc50b3..7d23e1aa3a6 100644 --- a/AK/StringView.cpp +++ b/AK/StringView.cpp @@ -68,6 +68,48 @@ Vector StringView::split_view(StringView separator, SplitBehavior sp return parts; } +template +static void for_each_line(StringView string, Callback&& callback) +{ + char const* characters = string.characters_without_null_termination(); + + size_t substart = 0; + bool last_ch_was_cr = false; + + for (size_t i = 0; i < string.length(); ++i) { + char ch = characters[i]; + bool split_view = false; + + switch (ch) { + case '\n': + if (last_ch_was_cr) + substart = i + 1; + else + split_view = true; + + last_ch_was_cr = false; + break; + + case '\r': + split_view = true; + last_ch_was_cr = true; + break; + + default: + last_ch_was_cr = false; + break; + } + + if (split_view) { + callback(string.substring_view(substart, i - substart)); + substart = i + 1; + } + } + + if (size_t taillen = string.length() - substart; taillen != 0) + callback(string.substring_view(substart, taillen)); +} + Vector StringView::lines(ConsiderCarriageReturn consider_carriage_return) const { if (is_empty()) @@ -76,36 +118,24 @@ Vector StringView::lines(ConsiderCarriageReturn consider_carriage_re if (consider_carriage_return == ConsiderCarriageReturn::No) return split_view('\n', SplitBehavior::KeepEmpty); - Vector v; - size_t substart = 0; - bool last_ch_was_cr = false; - bool split_view = false; - for (size_t i = 0; i < length(); ++i) { - char ch = characters_without_null_termination()[i]; - if (ch == '\n') { - split_view = true; - if (last_ch_was_cr) { - substart = i + 1; - split_view = false; - } - } - if (ch == '\r') { - split_view = true; - last_ch_was_cr = true; - } else { - last_ch_was_cr = false; - } - if (split_view) { - size_t sublen = i - substart; - v.append(substring_view(substart, sublen)); - substart = i + 1; - } - split_view = false; - } - size_t taillen = length() - substart; - if (taillen != 0) - v.append(substring_view(substart, taillen)); - return v; + Vector lines; + for_each_line(*this, [&](auto line) { lines.append(line); }); + + return lines; +} + +size_t StringView::count_lines(ConsiderCarriageReturn consider_carriage_return) const +{ + if (is_empty()) + return 1; + + if (consider_carriage_return == ConsiderCarriageReturn::No) + return count('\n') + 1; + + size_t lines = 0; + for_each_line(*this, [&](auto) { ++lines; }); + + return lines; } bool StringView::starts_with(char ch) const diff --git a/AK/StringView.h b/AK/StringView.h index 4a28fab8cb9..285b832a2bf 100644 --- a/AK/StringView.h +++ b/AK/StringView.h @@ -240,6 +240,7 @@ public: Yes, }; [[nodiscard]] Vector lines(ConsiderCarriageReturn = ConsiderCarriageReturn::Yes) const; + [[nodiscard]] size_t count_lines(ConsiderCarriageReturn = ConsiderCarriageReturn::Yes) const; // Create a new substring view of this string view, starting either at the beginning of // the given substring view, or after its end, and continuing until the end of this string diff --git a/Tests/AK/TestStringView.cpp b/Tests/AK/TestStringView.cpp index 1487b848301..63523b863b5 100644 --- a/Tests/AK/TestStringView.cpp +++ b/Tests/AK/TestStringView.cpp @@ -105,6 +105,24 @@ TEST_CASE(lines) EXPECT_EQ(test_string_vector.at(2).is_empty(), true); } +TEST_CASE(count_lines) +{ + EXPECT_EQ(""sv.count_lines(), 1u); + EXPECT_EQ("foo"sv.count_lines(), 1u); + + EXPECT_EQ("foo\nbar"sv.count_lines(), 2u); + EXPECT_EQ("foo\rbar"sv.count_lines(), 2u); + EXPECT_EQ("foo\rbar"sv.count_lines(StringView::ConsiderCarriageReturn::No), 1u); + EXPECT_EQ("foo\r\nbar"sv.count_lines(), 2u); + EXPECT_EQ("foo\r\nbar"sv.count_lines(StringView::ConsiderCarriageReturn::No), 2u); + + EXPECT_EQ("foo\nbar\nbax"sv.count_lines(), 3u); + EXPECT_EQ("foo\rbar\rbaz"sv.count_lines(), 3u); + EXPECT_EQ("foo\rbar\rbaz"sv.count_lines(StringView::ConsiderCarriageReturn::No), 1u); + EXPECT_EQ("foo\r\nbar\r\nbaz"sv.count_lines(), 3u); + EXPECT_EQ("foo\r\nbar\r\nbaz"sv.count_lines(StringView::ConsiderCarriageReturn::No), 3u); +} + TEST_CASE(find) { auto test_string_view = "aabbcc_xy_ccbbaa"sv;