From: Teemu Murtola Date: Tue, 2 Jun 2015 10:26:30 +0000 (+0300) Subject: Refactor rst parsing for console output X-Git-Url: http://biod.pnpi.spb.ru/gitweb/?a=commitdiff_plain;h=1e7d2af2ea3b3fc3dfeae17baaec30b5bca4e1ab;p=alexxy%2Fgromacs.git Refactor rst parsing for console output Move code from helpwritercontext.cpp into a separate class in rstparser.*, and structure it more cleanly. The logic is mostly the same as earlier, but the new structure also makes it easier to fix some issues. In particular, since the code now first identifies what the next paragraph is, and uses a separate loop to format it, it is much easier to adapt the formatting to the contents of the paragraph. Add comments for the complicated paragraph parsing logic. To support this change, make TextLineWrapper not produce whitespace-only lines if requested to indent an empty line. Add tests for fixed/enhanced functionality. Change-Id: I690dcf1578780d5fb0016d41da1e67dca96bd08c --- diff --git a/docs/doxygen/user/onlinehelp.md b/docs/doxygen/user/onlinehelp.md index 3c70c6dea0..4c3e0ce4bd 100644 --- a/docs/doxygen/user/onlinehelp.md +++ b/docs/doxygen/user/onlinehelp.md @@ -57,26 +57,24 @@ output: reStructuredText). This means that block quotes are also rendered reasonably, since they are just indented paragraphs. - Literal blocks, i.e., indented paragraphs where the preceding paragraph ends - with `::`. Line breaks within such paragraphs are preserved (but - varying indentation is not currently implemented). The rules for handling - the `::` are the same as in reStructuredText. + with `::`. Line breaks within such paragraphs are preserved. The rules for + handling the `::` are the same as in reStructuredText. + Multiple paragraphs within a literal block are not currently supported. - Titles, i.e., a line underlined by a punctuation character. Title formatting is currently preserved as-is, so it must be manually ensured that the used punctuation character properly fits into the context (i.e., other titles in the same generated reStructuredText document). Titles with both under- and overline are not currently supported. - Bullet lists. Only lists that start with `*` are currently recognized. - Indentation for the second and subsequent lines is either 2 (if the bullet - text is on a single line in input), or determined from the second line in - the input. + Indentation for the second and subsequent lines is determined from + the first non-space character after the bullet and/or from the second line + in the input (if these are not the same, the minimum is used). Note that in reStructuredText, the `*` must not be indented with respect to the preceding paragraph; otherwise, the bullet list is rendered within a - block quote. + block quote. Also, an empty line needs to precede a bullet list. - Enumerated lists. Only lists that start with digits are supported (e.g., `1.`). Multi-digit numbers can be used. - Indentation for the second and subsequent lines is determined either from - the first non-space character after the period (if there is a single line in - the input), or from the second line in the input. + Indentation is determined as for bullet lists. Lists are not renumbered automatically. \Gromacs-specific markup diff --git a/src/gromacs/onlinehelp-doc.h b/src/gromacs/onlinehelp-doc.h index 8a7e1f7223..fd1e7a0c9f 100644 --- a/src/gromacs/onlinehelp-doc.h +++ b/src/gromacs/onlinehelp-doc.h @@ -44,6 +44,8 @@ * output formats from the same input strings and API calls. Whenever * possible, the output format should be abstracted using this interface, * but in some cases code still writes out raw reStructuredText. + * - rstparser.h provides the functionality to parse reStructuredText such that + * it can be rewrapped for console output. * - helpformat.h provides some general text-processing classes, currently * focused on producing aligned tables for console output. * - helptopicinterface.h, helptopic.h, and helpmanager.h provide classes for diff --git a/src/gromacs/onlinehelp/helpwritercontext.cpp b/src/gromacs/onlinehelp/helpwritercontext.cpp index 680828df70..0c3c37abf3 100644 --- a/src/gromacs/onlinehelp/helpwritercontext.cpp +++ b/src/gromacs/onlinehelp/helpwritercontext.cpp @@ -57,6 +57,8 @@ #include "gromacs/utility/programcontext.h" #include "gromacs/utility/stringutil.h" +#include "rstparser.h" + namespace gmx { @@ -338,98 +340,6 @@ std::string removeExtraNewlinesRst(const std::string &text) return result; } -/*! \brief - * Returns `true` if a list item starts in \p text at \p index. - * - * Does not throw. - */ -bool startsListItem(const std::string &text, size_t index) -{ - if (text.length() <= index + 1) - { - return false; - } - if (text[index] == '*' && std::isspace(text[index+1])) - { - return true; - } - if (std::isdigit(text[index])) - { - while (index < text.length() && std::isdigit(text[index])) - { - ++index; - } - if (text.length() > index + 1 && text[index] == '.' - && std::isspace(text[index+1])) - { - return true; - } - } - return false; -} - -/*! \brief - * Returns `true` if a table starts in \p text at \p index. - * - * The function only inspects the first line for something that looks like a - * reStructuredText table, and accepts also some malformed tables. - * Any issues should be apparent when Sphinx parses the reStructuredText - * export, so full validation is not done here. - * - * Does not throw. - */ -bool startsTable(const std::string &text, size_t index) -{ - if (text[index] == '=') - { - while (index < text.length() && text[index] != '\n') - { - if (text[index] != '=' && !std::isspace(text[index])) - { - return false; - } - ++index; - } - return true; - } - else if (text[index] == '+') - { - while (index < text.length() && text[index] != '\n') - { - if (text[index] != '-' && text[index] != '+') - { - return false; - } - ++index; - } - return true; - } - return false; -} - -/*! \brief - * Returns `true` if a line in \p text starting at \p index is a title underline. - * - * Does not throw. - */ -bool isTitleUnderline(const std::string &text, size_t index) -{ - const char firstChar = text[index]; - if (std::ispunct(firstChar)) - { - while (index < text.length() && text[index] != '\n') - { - if (text[index] != firstChar) - { - return false; - } - ++index; - } - return true; - } - return false; -} - //! \} } // namespace @@ -636,140 +546,18 @@ void HelpWriterContext::Impl::processMarkup(const std::string &text, const int baseIndent = wrapper->settings().indent(); result = repall(result, sandrTty); result = replaceLinks(result); - std::string paragraph; + std::string paragraph; paragraph.reserve(result.length()); - size_t i = 0; - int nextBreakSize = 0; - bool bLiteral = false; - while (i < result.length()) + RstParagraphIterator iter(result); + while (iter.nextParagraph()) { - while (i < result.length() && result[i] == '\n') - { - ++i; - } - if (i == result.length()) - { - break; - } - const int breakSize = nextBreakSize; - int currentLine = 0; - bool bLineStart = true; - int currentIndent = 0; - int firstIndent = 0; - int indent = 0; - paragraph.clear(); - for (;; ++i) - { - if (result[i] == '\n' || i == result.length()) - { - if (currentLine == 0) - { - firstIndent = currentIndent; - } - else if (currentLine == 1) - { - indent = currentIndent; - } - ++currentLine; - bLineStart = true; - currentIndent = 0; - if (i + 1 >= result.length() || result[i + 1] == '\n') - { - nextBreakSize = 2; - break; - } - if (!bLiteral) - { - if (!std::isspace(result[i - 1])) - { - paragraph.push_back(' '); - } - continue; - } - } - else if (bLineStart) - { - if (std::isspace(result[i])) - { - ++currentIndent; - continue; - } - else if (startsListItem(result, i)) - { - if (currentLine > 0) - { - while (i > 0 && result[i - 1] != '\n') - { - --i; - } - paragraph = stripString(paragraph); - nextBreakSize = 1; - break; - } - int prefixLength = 0; - while (!std::isspace(result[i + prefixLength])) - { - ++prefixLength; - } - while (i + prefixLength < result.length() - && std::isspace(result[i + prefixLength])) - { - ++prefixLength; - } - indent = currentIndent + prefixLength; - } - else if (currentLine == 0 && startsTable(result, i)) - { - bLiteral = true; - } - else if (currentLine == 1 && isTitleUnderline(result, i)) - { - // TODO: Nicer formatting that shares - // implementation with writeTitle() and honors the - // nesting depths etc. - if (i > 0) - { - paragraph[paragraph.length() - 1] = '\n'; - } - } - bLineStart = false; - } - paragraph.push_back(result[i]); - } - if (endsWith(paragraph, "::")) - { - bLiteral = true; - if (paragraph.length() == 2) - { - if (breakSize == 0) - { - nextBreakSize = 0; - } - continue; - } - if (paragraph[paragraph.length() - 3] == ' ') - { - paragraph.resize(paragraph.length() - 3); - } - else - { - paragraph.resize(paragraph.length() - 1); - } - } - else - { - bLiteral = false; - } - if (breakSize > 0) - { - wrapper->wrap(std::string(breakSize, '\n')); - } - wrapper->settings().setFirstLineIndent(baseFirstLineIndent + firstIndent); - wrapper->settings().setIndent(baseIndent + indent); + iter.getParagraphText(¶graph); + wrapper->settings().setFirstLineIndent(baseFirstLineIndent + iter.firstLineIndent()); + wrapper->settings().setIndent(baseIndent + iter.indent()); wrapper->wrap(paragraph); - wrapper->settings().setFirstLineIndent(baseFirstLineIndent); - wrapper->settings().setIndent(baseIndent); } + wrapper->settings().setFirstLineIndent(baseFirstLineIndent); + wrapper->settings().setIndent(baseIndent); break; } case eHelpOutputFormat_Rst: diff --git a/src/gromacs/onlinehelp/rstparser.cpp b/src/gromacs/onlinehelp/rstparser.cpp new file mode 100644 index 0000000000..dc5edacbed --- /dev/null +++ b/src/gromacs/onlinehelp/rstparser.cpp @@ -0,0 +1,340 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal \file + * \brief + * Implements classes from rstparser.h. + * + * \author Teemu Murtola + * \ingroup module_onlinehelp + */ +#include "gmxpre.h" + +#include "rstparser.h" + +#include + +#include + +#include "gromacs/utility/stringutil.h" + +namespace gmx +{ + +namespace +{ + +/*! \brief + * Counts the number of leading spaces in a text range. + * + * Does not throw. + */ +int countLeadingSpace(const std::string &text, size_t start, size_t end) +{ + for (size_t i = start; i < end; ++i) + { + if (!std::isspace(text[i])) + { + return i - start; + } + } + return end - start; +} + +/*! \brief + * Returns `true` if a list item starts in \p text at \p index. + * + * Does not throw. + */ +bool startsListItem(const std::string &text, size_t index) +{ + if (text.length() <= index + 1) + { + return false; + } + if (text[index] == '*' && std::isspace(text[index+1])) + { + return true; + } + if (std::isdigit(text[index])) + { + while (index < text.length() && std::isdigit(text[index])) + { + ++index; + } + if (text.length() > index + 1 && text[index] == '.' + && std::isspace(text[index+1])) + { + return true; + } + } + return false; +} + +/*! \brief + * Returns `true` if a table starts in \p text at \p index. + * + * The function only inspects the first line for something that looks like a + * reStructuredText table, and accepts also some malformed tables. + * Any issues should be apparent when Sphinx parses the reStructuredText + * export, so full validation is not done here. + * + * Does not throw. + */ +bool startsTable(const std::string &text, size_t index) +{ + if (text[index] == '=') + { + while (index < text.length() && text[index] != '\n') + { + if (text[index] != '=' && !std::isspace(text[index])) + { + return false; + } + ++index; + } + return true; + } + else if (text[index] == '+') + { + while (index < text.length() && text[index] != '\n') + { + if (text[index] != '-' && text[index] != '+') + { + return false; + } + ++index; + } + return true; + } + return false; +} + +/*! \brief + * Returns `true` if a line in \p text starting at \p index is a title underline. + * + * Does not throw. + */ +bool isTitleUnderline(const std::string &text, size_t index) +{ + const char firstChar = text[index]; + if (std::ispunct(firstChar)) + { + while (index < text.length() && text[index] != '\n') + { + if (text[index] != firstChar) + { + return false; + } + ++index; + } + return true; + } + return false; +} + +} // namespace + +/******************************************************************** + * RstParagraphIterator + */ + +RstParagraphIterator::RstParagraphIterator(const std::string &text) + : text_(text), begin_(0), end_(0), type_(eParagraphType_Normal), + breakSize_(0), firstLineIndent_(0), indent_(0), + nextBegin_(0), nextBreakSize_(0), literalIndent_(-1) +{ +} + +bool RstParagraphIterator::nextParagraph() +{ + begin_ = nextBegin_; + type_ = eParagraphType_Normal; + breakSize_ = nextBreakSize_; + // Skip leading newlines (includes those separating paragraphs). + while (begin_ < text_.length() && text_[begin_] == '\n') + { + ++begin_; + } + if (begin_ == text_.length()) + { + end_ = begin_; + breakSize_ = 0; + nextBegin_ = begin_; + return false; + } + if (literalIndent_ >= 0) + { + type_ = eParagraphType_Literal; + } + // Loop over lines in input until the end of the current paragraph. + size_t i = begin_; + int lineCount = 0; + while (true) + { + const bool bFirstLine = (lineCount == 0); + const size_t lineStart = i; + const size_t lineEnd = std::min(text_.find('\n', i), text_.length()); + const int lineIndent = countLeadingSpace(text_, lineStart, lineEnd); + const size_t textStart = lineStart + lineIndent; + const bool bListItem = startsListItem(text_, textStart); + // Return each list item as a separate paragraph to make the behavior + // the same always; the item text could even contain multiple + // paragraphs, that would anyways produce breaks. + if (bListItem && !bFirstLine) + { + // Since there was no empty line in input, do not produce one in + // the output, either. + nextBreakSize_ = 1; + // end_ is not updated to break the paragraph before the current line. + break; + } + // Now we will actually use this line as part of this paragraph. + end_ = lineEnd; + ++lineCount; + // Update indentation. + if (bFirstLine) + { + firstLineIndent_ = indent_ = lineIndent; + if (bListItem) + { + // Find the indentation of the actual text after the + // bullet/number. + int prefixLength = 0; + while (!std::isspace(text_[textStart + prefixLength])) + { + ++prefixLength; + } + while (textStart + prefixLength < text_.length() + && std::isspace(text_[textStart + prefixLength])) + { + ++prefixLength; + } + indent_ += prefixLength; + } + } + else + { + indent_ = std::min(indent_, lineIndent); + } + // We need to check for the title underline before checking for the + // paragraph break so that the title is correctly recognized. + if (lineCount == 2 && isTitleUnderline(text_, lineStart)) + { + type_ = eParagraphType_Title; + } + // Check for end-of-input or an empty line, i.e., a normal paragraph + // break. + if (lineEnd + 1 >= text_.length() || text_[lineEnd + 1] == '\n') + { + nextBreakSize_ = 2; + break; + } + // Always return the title as a separate paragraph, as it requires + // different processing. + // TODO: This should allow nicer formatting that shares + // implementation with writeTitle() and honors the nesting depths etc., + // but that is not implemented. + if (type_ == eParagraphType_Title) + { + // If we are here, there was no actual paragraph break, so do not + // produce one in the output either. + nextBreakSize_ = 1; + break; + } + // Next loop starts at the character after the newline. + i = lineEnd + 1; + } + nextBegin_ = end_; + // Check if the next paragraph should be treated as a literal paragraph, + // and deal with transformations for the :: marker. + if (end_ - begin_ >= 2 && text_.compare(end_ - 2, 2, "::") == 0) + { + literalIndent_ = indent_; + // Return the actual literal block if the paragraph was just an "::". + if (end_ - begin_ == 2) + { + // Avoid leading whitespace at the beginning; breakSize_ == 0 + // only for the first paragraph. + if (breakSize_ == 0) + { + nextBreakSize_ = 0; + } + return nextParagraph(); + } + // Remove one of the colons, or both if preceded by whitespace. + const bool bRemoveDoubleColon = (text_[end_ - 3] == ' '); + end_ -= (bRemoveDoubleColon ? 3 : 1); + } + else + { + literalIndent_ = -1; + } + // Treat a table like a literal block (preserve newlines). + if (startsTable(text_, begin_ + firstLineIndent_)) + { + type_ = eParagraphType_Literal; + } + return true; +} + +void RstParagraphIterator::getParagraphText(std::string *result) const +{ + result->clear(); + result->reserve(end_ - begin_); + result->append(breakSize_, '\n'); + const bool bPreserveNewlines = (type_ != eParagraphType_Normal); + size_t i = begin_; + while (i < end_) + { + const bool bFirstLine = (i == begin_); + const size_t lineStart = i + (bFirstLine ? firstLineIndent_ : indent_); + const size_t lineEnd = std::min(text_.find('\n', i), end_); + if (!bFirstLine) + { + if (bPreserveNewlines) + { + result->push_back('\n'); + } + else if (!std::isspace((*result)[result->length() - 1])) + { + result->push_back(' '); + } + } + result->append(text_, lineStart, lineEnd - lineStart); + i = lineEnd + 1; + } +} + +} // namespace gmx diff --git a/src/gromacs/onlinehelp/rstparser.h b/src/gromacs/onlinehelp/rstparser.h new file mode 100644 index 0000000000..c250209166 --- /dev/null +++ b/src/gromacs/onlinehelp/rstparser.h @@ -0,0 +1,147 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2015, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal \file + * \brief + * Declares classes for (partial) parsing of reStructuredText. + * + * \author Teemu Murtola + * \ingroup module_onlinehelp + */ +#ifndef GMX_ONLINEHELP_RSTPARSER_H +#define GMX_ONLINEHELP_RSTPARSER_H + +#include + +#include "gromacs/utility/classhelpers.h" + +namespace gmx +{ + +class TextLineWrapperSettings; + +/*! \internal + * \brief + * Iterator over reStructuredText paragraphs. + * + * After initialization, nextParagraph() needs to be called to access the first + * paragraph. Subsequence paragraphs can be accessed by repeated calls to + * nextParagraph(). After the last paragraph, nextParagraph() returns `false`. + * + * After each call to nextParagraph(), other methods can be called to query + * details of the current paragraph. + * + * \ingroup module_onlinehelp + */ +class RstParagraphIterator +{ + public: + /*! \brief + * Initializes an iterator for given input text. + * + * Does not throw. + */ + explicit RstParagraphIterator(const std::string &text); + + /*! \brief + * Advances the iterator to the next paragraph. + * + * \returns `false` if there were no more paragraphs. + * + * Does not throw (except std::bad_alloc if std::string::compare() + * throws). + */ + bool nextParagraph(); + + //! Returns the indentation for first line of this paragraph. + int firstLineIndent() const { return firstLineIndent_; } + //! Returns the indentation for subsequent lines of this paragraph. + int indent() const { return indent_; } + /*! \brief + * Returns the text + * + * \param[out] result Variable to receive the paragraph text. + * \throws std::bad_alloc if out of memory. + * + * Indentation and internal line breaks have been stripped from the + * paragraph text (except for literal blocks etc.). For literal + * blocks, the common indentation has been stripped and is returned in + * indent() instead. + * + * Leading newlines are returned to indicate necessary separation from + * the preceding paragraph. + */ + void getParagraphText(std::string *result) const; + + private: + enum ParagraphType + { + eParagraphType_Normal, + eParagraphType_Literal, + eParagraphType_Title + }; + + //! The text to iterate over. + const std::string &text_; + + //! Start of the current paragraph. + size_t begin_; + //! End of the current paragraph (C++-style iterator). + size_t end_; + //! Type of the current paragraph. + ParagraphType type_; + //! Number of newlines to print before the current paragraph. + int breakSize_; + //! Indentation of the first line of this paragraph. + int firstLineIndent_; + //! (Minimum) indentation of other lines in this paragraph. + int indent_; + + //! Start of the next paragrah. + size_t nextBegin_; + //! Number of newlines to print after the current paragraph. + int nextBreakSize_; + /*! \brief + * Indentation of the preceding paragraph that contained `::`. + * + * If the next paragraph is not a literal block, the value is `-1`. + */ + int literalIndent_; + + GMX_DISALLOW_COPY_AND_ASSIGN(RstParagraphIterator); +}; + +} // namespace gmx + +#endif diff --git a/src/gromacs/onlinehelp/tests/helpwritercontext.cpp b/src/gromacs/onlinehelp/tests/helpwritercontext.cpp index 1b2c4acf68..d41ca87f6b 100644 --- a/src/gromacs/onlinehelp/tests/helpwritercontext.cpp +++ b/src/gromacs/onlinehelp/tests/helpwritercontext.cpp @@ -168,6 +168,20 @@ TEST_F(HelpWriterContextTest, FormatsLiteralTextAtBeginning) testFormatting(text); } +TEST_F(HelpWriterContextTest, FormatsLiteralTextWithIndentation) +{ + const char *const text[] = { + "Sample paragraph::", + "", + " literal block", + " another indented line", + "", + "Normal paragraph", + "with wrapping" + }; + testFormatting(text); +} + TEST_F(HelpWriterContextTest, FormatsBulletList) { const char *const text[] = { @@ -243,12 +257,9 @@ TEST_F(HelpWriterContextTest, FormatsGridTable) TEST_F(HelpWriterContextTest, FormatsTitles) { - // Console formatting does not currently work without the paragraph breaks - // after the title. const char *const text[] = { "Title", "=====", - "", "Some text without spacing", "", "Subtitle", diff --git a/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsLiteralTextWithIndentation.xml b/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsLiteralTextWithIndentation.xml new file mode 100644 index 0000000000..6fbc448970 --- /dev/null +++ b/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsLiteralTextWithIndentation.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsTitles.xml b/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsTitles.xml index b5eadeb566..3a03ef4cac 100644 --- a/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsTitles.xml +++ b/src/gromacs/onlinehelp/tests/refdata/HelpWriterContextTest_FormatsTitles.xml @@ -4,7 +4,6 @@ + + + + + diff --git a/src/gromacs/utility/tests/stringutil.cpp b/src/gromacs/utility/tests/stringutil.cpp index 5f9fb1e31a..ba1683d06d 100644 --- a/src/gromacs/utility/tests/stringutil.cpp +++ b/src/gromacs/utility/tests/stringutil.cpp @@ -238,6 +238,8 @@ TEST(ReplaceAllTest, HandlesPossibleRecursiveMatches) const char g_wrapText[] = "A quick brown fox jumps over the lazy dog"; //! Test string for wrapping with embedded line breaks. const char g_wrapText2[] = "A quick brown fox jumps\nover the lazy dog"; +//! Test string for wrapping with embedded line breaks and an empty line. +const char g_wrapText3[] = "A quick brown fox jumps\n\nover the lazy dog"; //! Test string for wrapping with a long word. const char g_wrapTextLongWord[] = "A quick brown fox jumps awordthatoverflowsaline over the lazy dog"; @@ -331,6 +333,16 @@ TEST_F(TextLineWrapperTest, HandlesIndent) checkText(wrapper.wrapToString(g_wrapText2), "WrappedAt14"); } +TEST_F(TextLineWrapperTest, HandlesIndentWithEmptyLines) +{ + gmx::TextLineWrapper wrapper; + wrapper.settings().setIndent(2); + + checkText(wrapper.wrapToString(g_wrapText3), "WrappedWithNoLimit"); + wrapper.settings().setLineLength(16); + checkText(wrapper.wrapToString(g_wrapText3), "WrappedAt14"); +} + TEST_F(TextLineWrapperTest, HandlesHangingIndent) { gmx::TextLineWrapper wrapper;