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
* 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
#include "gromacs/utility/programcontext.h"
#include "gromacs/utility/stringutil.h"
+#include "rstparser.h"
+
namespace gmx
{
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
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:
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \ingroup module_onlinehelp
+ */
+#include "gmxpre.h"
+
+#include "rstparser.h"
+
+#include <cctype>
+
+#include <algorithm>
+
+#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
--- /dev/null
+/*
+ * 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 <teemu.murtola@gmail.com>
+ * \ingroup module_onlinehelp
+ */
+#ifndef GMX_ONLINEHELP_RSTPARSER_H
+#define GMX_ONLINEHELP_RSTPARSER_H
+
+#include <string>
+
+#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
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[] = {
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",
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="Console"><![CDATA[
+Sample paragraph:
+
+ literal block
+ another indented line
+
+Normal paragraph with wrapping]]></String>
+ <String Name="reStructuredText"><![CDATA[
+Sample paragraph::
+
+ literal block
+ another indented line
+
+Normal paragraph
+with wrapping]]></String>
+</ReferenceData>
<String Name="Console"><![CDATA[
Title
=====
-
Some text without spacing
Subtitle
<String Name="reStructuredText"><![CDATA[
Title
=====
-
Some text without spacing
Subtitle
--lineEnd;
}
- size_t lineLength = lineEnd - lineStart;
+ const size_t lineLength = lineEnd - lineStart;
+ if (lineLength == 0)
+ {
+ return std::string();
+ }
std::string result(indent, ' ');
result.append(input, lineStart, lineLength);
if (bContinuation && settings_.continuationChar_ != '\0')
--- /dev/null
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+ <String Name="WrappedWithNoLimit"><![CDATA[
+ A quick brown fox jumps
+
+ over the lazy dog]]></String>
+ <String Name="WrappedAt14"><![CDATA[
+ A quick brown
+ fox jumps
+
+ over the lazy
+ dog]]></String>
+</ReferenceData>
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";
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;