diff options
author | Knut Petter Svendsen <knutpett@pvv.org> | 2013-02-21 05:45:44 +0100 |
---|---|---|
committer | David Schulz <david.schulz@digia.com> | 2013-02-21 13:34:25 +0100 |
commit | c937226db1c3c2d98e4a73d7dacde8e35d8cbc1c (patch) | |
tree | ab5f30bb19d4e17c1854615c4bba35a07aa1a925 | |
parent | 8d2f40609263396e7809c608c52fd2c8713db668 (diff) |
C++: Improved automatic Doxygen comment blocks with CppStyle
Added support for CppStyle for Doxygen block generation when
hitting enter after a /// or //! comment. Previously only
QtStyle and JavaStyle was supported.
Change-Id: Ib010e55ba602127a6842ba02034fbe85994ee2bd
Reviewed-by: David Schulz <david.schulz@digia.com>
-rw-r--r-- | src/plugins/cppeditor/cppdoxygen_test.cpp | 123 | ||||
-rw-r--r-- | src/plugins/cppeditor/cppeditor.cpp | 262 | ||||
-rw-r--r-- | src/plugins/cppeditor/cppeditor.h | 1 | ||||
-rw-r--r-- | src/plugins/cppeditor/cppplugin.h | 6 | ||||
-rw-r--r-- | src/plugins/cpptools/completionsettingspage.ui | 2 | ||||
-rw-r--r-- | src/plugins/cpptools/doxygengenerator.cpp | 22 | ||||
-rw-r--r-- | src/plugins/cpptools/doxygengenerator.h | 6 |
7 files changed, 359 insertions, 63 deletions
diff --git a/src/plugins/cppeditor/cppdoxygen_test.cpp b/src/plugins/cppeditor/cppdoxygen_test.cpp index d72c9fde98..a46109cfb3 100644 --- a/src/plugins/cppeditor/cppdoxygen_test.cpp +++ b/src/plugins/cppeditor/cppdoxygen_test.cpp @@ -40,7 +40,9 @@ #include <QKeyEvent> #include <QString> #include <QTextDocument> +#ifdef WITH_TESTS #include <QtTest> +#endif /*! @@ -221,3 +223,124 @@ void CppPlugin::test_doxygen_comments_java_style_continuation() TestCase data(given); data.run(expected); } + +void CppPlugin::test_doxygen_comments_cpp_styleA() +{ + const QByteArray given = + "bool preventFolding;\n" + "///|\n" + "int a;\n" + ; + + const QByteArray expected = + "bool preventFolding;\n" + "///\n" + "/// \\brief a\n" + "///\n" + "int a;\n" + ; + TestCase data(given); + data.run(expected); +} + +void CppPlugin::test_doxygen_comments_cpp_styleB() +{ + const QByteArray given = + "bool preventFolding;\n" + "//!|\n" + "int a;\n" + ; + + const QByteArray expected = + "bool preventFolding;\n" + "//!\n" + "//! \\brief a\n" + "//!\n" + "int a;\n" + ; + TestCase data(given); + data.run(expected); +} + +void CppPlugin::test_doxygen_comments_cpp_styleA_continuation() +{ + const QByteArray given = + "bool preventFolding;\n" + "///\n" + "/// \\brief a|\n" + "///\n" + "int a;\n" + ; + const QByteArray expected = + "bool preventFolding;\n" + "///\n" + "/// \\brief a\n" + "///\n" + "///\n" + "int a;\n" + ; + + TestCase data(given); + data.run(expected); +} + +/// test cpp style doxygen comment when inside a indented scope +void CppPlugin::test_doxygen_comments_cpp_styleA_indented() +{ + const QByteArray given = + " bool preventFolding;\n" + " ///|\n" + " int a;\n" + ; + + const QByteArray expected = + " bool preventFolding;\n" + " ///\n" + " /// \\brief a\n" + " ///\n" + " int a;\n" + ; + TestCase data(given); + data.run(expected); +} + +/// test cpp style doxygen comment continuation when inside a indented scope +void CppPlugin::test_doxygen_comments_cpp_styleA_indented_continuation() +{ + const QByteArray given = + " bool preventFolding;\n" + " ///\n" + " /// \\brief a|\n" + " ///\n" + " int a;\n" + ; + const QByteArray expected = + " bool preventFolding;\n" + " ///\n" + " /// \\brief a\n" + " ///\n" + " ///\n" + " int a;\n" + ; + + TestCase data(given); + data.run(expected); +} + +void CppPlugin::test_doxygen_comments_cpp_styleA_corner_case() +{ + const QByteArray given = + "bool preventFolding;\n" + "///\n" + "void d(); ///|\n" + ; + const QByteArray expected = + "bool preventFolding;\n" + "///\n" + "void d(); ///\n" + "\n" + ; + + TestCase data(given); + data.run(expected); +} diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index cc8fd2a47b..b609a92d11 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -420,9 +420,175 @@ struct CanonicalSymbol }; - int numberOfClosedEditors = 0; +/// Check if previous line is a CppStyle Doxygen Comment +bool isPreviousLineCppStyleComment(const QTextCursor &cursor) +{ + const QTextBlock ¤tBlock = cursor.block(); + if (!currentBlock.isValid()) + return false; + + const QTextBlock &actual = currentBlock.previous(); + if (!actual.isValid()) + return false; + + const QString text = actual.text().trimmed(); + if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!"))) + return true; + + return false; +} + +/// Check if next line is a CppStyle Doxygen Comment +bool isNextLineCppStyleComment(const QTextCursor &cursor) +{ + const QTextBlock ¤tBlock = cursor.block(); + if (!currentBlock.isValid()) + return false; + + const QTextBlock &actual = currentBlock.next(); + if (!actual.isValid()) + return false; + + const QString text = actual.text().trimmed(); + if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!"))) + return true; + + return false; +} + +/// Check if line is a CppStyle Doxygen comment and the cursor is after the comment +bool isCursorAfterCppComment(const QTextCursor &cursor, const QTextDocument *doc) +{ + QTextCursor cursorFirstNonBlank(cursor); + cursorFirstNonBlank.movePosition(QTextCursor::StartOfLine); + while (doc->characterAt(cursorFirstNonBlank.position()).isSpace() + && cursorFirstNonBlank.movePosition(QTextCursor::NextCharacter)) { + } + + const QTextBlock& block = cursorFirstNonBlank.block(); + const QString text = block.text().trimmed(); + if (text.startsWith(QLatin1String("///")) || text.startsWith(QLatin1String("//!"))) + return (cursor.position() >= cursorFirstNonBlank.position() + 3); + + return false; +} + +bool isCppStyleContinuation(const QTextCursor& cursor) +{ + return (isPreviousLineCppStyleComment(cursor) || isNextLineCppStyleComment(cursor)); +} + +DoxygenGenerator::DocumentationStyle doxygenStyle(const QTextCursor &cursor, + const QTextDocument *doc) +{ + const int pos = cursor.position(); + + QString comment = doc->characterAt(pos - 3) + + doc->characterAt(pos - 2) + + doc->characterAt(pos - 1); + + if (comment == QLatin1String("/**")) + return CppTools::DoxygenGenerator::JavaStyle; + else if (comment == QLatin1String("/*!")) + return CppTools::DoxygenGenerator::QtStyle; + else if (comment == QLatin1String("///")) + return CppTools::DoxygenGenerator::CppStyleA; + else + return CppTools::DoxygenGenerator::CppStyleB; +} + +bool handleDoxygenCppStyleContinuation(QTextCursor &cursor, + QKeyEvent *e) +{ + const int blockPos = cursor.positionInBlock(); + const QString &text = cursor.block().text(); + int offset = 0; + for (; offset < blockPos; ++offset) { + if (!text.at(offset).isSpace()) + break; + } + + // If the line does not start with the comment we don't + // consider it as a continuation. Handles situations like: + // void d(); ///<enter> + if (!(text.trimmed().startsWith(QLatin1String("///")) + || text.startsWith(QLatin1String("//!")))) + return false; + + QString newLine(QLatin1Char('\n')); + newLine.append(QString(offset, QLatin1Char(' '))); // indent correctly + + const QString commentMarker = text.mid(offset, 3); + newLine.append(commentMarker); + + cursor.insertText(newLine); + e->accept(); + return true; +} + +bool handleDoxygenContinuation(QTextCursor &cursor, + QKeyEvent *e, + const QTextDocument *doc, + const bool enableDoxygen, + const bool leadingAsterisks) +{ + // It might be a continuation if: + // a) current line starts with /// or //! and cursor is positioned after the comment + // b) current line is in the middle of a multi-line Qt or Java style comment + + if (enableDoxygen && !cursor.atEnd() && isCursorAfterCppComment(cursor, doc)) + return handleDoxygenCppStyleContinuation(cursor, e); + + if (!leadingAsterisks) + return false; + + // We continue the comment if the cursor is after a comment's line asterisk and if + // there's no asterisk immediately after the cursor (that would already be considered + // a leading asterisk). + int offset = 0; + const int blockPos = cursor.positionInBlock(); + const QString &text = cursor.block().text(); + for (; offset < blockPos; ++offset) { + if (!text.at(offset).isSpace()) + break; + } + + if (offset < blockPos + && (text.at(offset) == QLatin1Char('*') + || (offset < blockPos - 1 + && text.at(offset) == QLatin1Char('/') + && text.at(offset + 1) == QLatin1Char('*')))) { + int followinPos = blockPos; + for (; followinPos < text.length(); ++followinPos) { + if (!text.at(followinPos).isSpace()) + break; + } + if (followinPos == text.length() + || text.at(followinPos) != QLatin1Char('*')) { + QString newLine(QLatin1Char('\n')); + QTextCursor c(cursor); + c.movePosition(QTextCursor::StartOfBlock); + c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, offset); + newLine.append(c.selectedText()); + if (text.at(offset) == QLatin1Char('/')) { + newLine.append(QLatin1String(" *")); + } else { + int start = offset; + while (offset < blockPos && text.at(offset) == QLatin1Char('*')) + ++offset; + newLine.append(QString(offset - start, QLatin1Char('*'))); + } + cursor.insertText(newLine); + e->accept(); + return true; + } + } + + return false; +} + } // end of anonymous namespace CPPEditor::CPPEditor(CPPEditorWidget *editor) @@ -2397,27 +2563,34 @@ bool CPPEditorWidget::handleDocumentationComment(QKeyEvent *e) return false; // We are interested on two particular cases: - // 1) The cursor is right after a /** or /*! and the user pressed enter. If Doxygen - // is enabled we need to generate an entire comment block. + // 1) The cursor is right after a /**, /*!, /// or ///! and the user pressed enter. + // If Doxygen is enabled we need to generate an entire comment block. // 2) The cursor is already in the middle of a multi-line comment and the user pressed // enter. If leading asterisk(s) is set we need to write a comment continuation // with those. if (m_commentsSettings.m_enableDoxygen && cursor.positionInBlock() >= 3) { + const int pos = cursor.position(); - if (characterAt(pos - 3) == QLatin1Char('/') - && characterAt(pos - 2) == QLatin1Char('*') - && (characterAt(pos - 1) == QLatin1Char('*') - || characterAt(pos - 1) == QLatin1Char('!'))) { + + if (isStartOfDoxygenComment(cursor)) { + CppTools::DoxygenGenerator::DocumentationStyle style + = doxygenStyle(cursor, document()); + + // Check if we're already in a CppStyle Doxygen comment => continuation + // Needs special handling since CppStyle does not have start and end markers + if ((style == CppTools::DoxygenGenerator::CppStyleA + || style == CppTools::DoxygenGenerator::CppStyleB) + && isCppStyleContinuation(cursor)) { + return handleDoxygenCppStyleContinuation(cursor, e); + } + CppTools::DoxygenGenerator doxygen; + doxygen.setStyle(style); doxygen.setAddLeadingAsterisks(m_commentsSettings.m_leadingAsterisks); doxygen.setGenerateBrief(m_commentsSettings.m_generateBrief); doxygen.setStartComment(false); - if (characterAt(pos - 1) == QLatin1Char('!')) - doxygen.setStyle(CppTools::DoxygenGenerator::QtStyle); - else - doxygen.setStyle(CppTools::DoxygenGenerator::JavaStyle); // Move until we reach any possibly meaningful content. while (document()->characterAt(cursor.position()).isSpace() @@ -2437,56 +2610,33 @@ bool CPPEditorWidget::handleDocumentationComment(QKeyEvent *e) return true; } } - cursor.setPosition(pos); + } - } + } // right after first doxygen comment - if (!m_commentsSettings.m_leadingAsterisks) - return false; + return handleDoxygenContinuation(cursor, + e, + document(), + m_commentsSettings.m_enableDoxygen, + m_commentsSettings.m_leadingAsterisks); + } - // We continue the comment if the cursor is after a comment's line asterisk and if - // there's no asterisk immediately after the cursor (that would already be considered - // a leading asterisk). - int offset = 0; - const int blockPos = cursor.positionInBlock(); - const QString &text = cursor.block().text(); - for (; offset < blockPos; ++offset) { - if (!text.at(offset).isSpace()) - break; - } + return false; +} - if (offset < blockPos - && (text.at(offset) == QLatin1Char('*') - || (offset < blockPos - 1 - && text.at(offset) == QLatin1Char('/') - && text.at(offset + 1) == QLatin1Char('*')))) { - int followinPos = blockPos; - for (; followinPos < text.length(); ++followinPos) { - if (!text.at(followinPos).isSpace()) - break; - } - if (followinPos == text.length() - || text.at(followinPos) != QLatin1Char('*')) { - QString newLine(QLatin1Char('\n')); - QTextCursor c(cursor); - c.movePosition(QTextCursor::StartOfBlock); - c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, offset); - newLine.append(c.selectedText()); - if (text.at(offset) == QLatin1Char('/')) { - newLine.append(QLatin1String(" *")); - } else { - int start = offset; - while (offset < blockPos && text.at(offset) == QLatin1Char('*')) - ++offset; - newLine.append(QString(offset - start, QLatin1Char('*'))); - } - cursor.insertText(newLine); - e->accept(); - return true; - } - } - } +bool CPPEditorWidget::isStartOfDoxygenComment(const QTextCursor &cursor) const +{ + const int pos = cursor.position(); + QString comment = characterAt(pos - 3) + + characterAt(pos - 2) + + characterAt(pos - 1); + if ((comment == QLatin1String("/**")) + || (comment == QLatin1String("/*!")) + || (comment == QLatin1String("///")) + || (comment == QLatin1String("//!"))) { + return true; + } return false; } diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 3c3064f052..78143b43b0 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -285,6 +285,7 @@ private: QModelIndex indexForPosition(int line, int column, const QModelIndex &rootIndex = QModelIndex()) const; bool handleDocumentationComment(QKeyEvent *e); + bool isStartOfDoxygenComment(const QTextCursor &cursor) const; CPlusPlus::CppModelManagerInterface *m_modelManager; diff --git a/src/plugins/cppeditor/cppplugin.h b/src/plugins/cppeditor/cppplugin.h index 2d9ba179d9..7c7a7a7558 100644 --- a/src/plugins/cppeditor/cppplugin.h +++ b/src/plugins/cppeditor/cppplugin.h @@ -94,6 +94,12 @@ private slots: void test_doxygen_comments_qt_style_continuation(); void test_doxygen_comments_java_style(); void test_doxygen_comments_java_style_continuation(); + void test_doxygen_comments_cpp_styleA(); + void test_doxygen_comments_cpp_styleB(); + void test_doxygen_comments_cpp_styleA_indented(); + void test_doxygen_comments_cpp_styleA_continuation(); + void test_doxygen_comments_cpp_styleA_indented_continuation(); + void test_doxygen_comments_cpp_styleA_corner_case(); void test_quickfix_GenerateGetterSetter_basicGetterWithPrefix(); void test_quickfix_GenerateGetterSetter_basicGetterWithoutPrefix(); diff --git a/src/plugins/cpptools/completionsettingspage.ui b/src/plugins/cpptools/completionsettingspage.ui index 89e37c31d6..b500f9095e 100644 --- a/src/plugins/cpptools/completionsettingspage.ui +++ b/src/plugins/cpptools/completionsettingspage.ui @@ -245,7 +245,7 @@ <item> <widget class="QCheckBox" name="leadingAsterisksCheckBox"> <property name="toolTip"> - <string>Add leading asterisks when continuing comments on new lines</string> + <string>Add leading asterisks when continuing Qt (/*!) and Java (/**) style comments on new lines</string> </property> <property name="text"> <string>Add leading asterisks</string> diff --git a/src/plugins/cpptools/doxygengenerator.cpp b/src/plugins/cpptools/doxygengenerator.cpp index c5305b209e..c0526ea732 100644 --- a/src/plugins/cpptools/doxygengenerator.cpp +++ b/src/plugins/cpptools/doxygengenerator.cpp @@ -238,7 +238,7 @@ QChar DoxygenGenerator::startMark() const QChar DoxygenGenerator::styleMark() const { - if (m_style == QtStyle) + if (m_style == QtStyle || m_style == CppStyleA || m_style == CppStyleB) return QLatin1Char('\\'); return QLatin1Char('@'); } @@ -256,17 +256,31 @@ QString DoxygenGenerator::commandSpelling(Command command) void DoxygenGenerator::writeStart(QString *comment) const { - comment->append(offsetString() % QLatin1String("/*") % startMark()); + if (m_style == CppStyleA) + comment->append(QLatin1String("///")); + if (m_style == CppStyleB) + comment->append(QLatin1String("//!")); + else + comment->append(offsetString() % QLatin1String("/*") % startMark()); } void DoxygenGenerator::writeEnd(QString *comment) const { - comment->append(offsetString() % QLatin1String(" */")); + if (m_style == CppStyleA) + comment->append(QLatin1String("///")); + else if (m_style == CppStyleB) + comment->append(QLatin1String("//!")); + else + comment->append(offsetString() % QLatin1String(" */")); } void DoxygenGenerator::writeContinuation(QString *comment) const { - if (m_addLeadingAsterisks) + if (m_style == CppStyleA) + comment->append(offsetString() % QLatin1String("///")); + else if (m_style == CppStyleB) + comment->append(offsetString() % QLatin1String("//!")); + else if (m_addLeadingAsterisks) comment->append(offsetString() % QLatin1String(" *")); else comment->append(offsetString() % QLatin1String(" ")); diff --git a/src/plugins/cpptools/doxygengenerator.h b/src/plugins/cpptools/doxygengenerator.h index c397934464..313593425e 100644 --- a/src/plugins/cpptools/doxygengenerator.h +++ b/src/plugins/cpptools/doxygengenerator.h @@ -49,8 +49,10 @@ public: DoxygenGenerator(); enum DocumentationStyle { - JavaStyle, - QtStyle + JavaStyle, ///< JavaStyle comment: /** + QtStyle, ///< QtStyle comment: /*! + CppStyleA, ///< CppStyle comment variant A: /// + CppStyleB ///< CppStyle comment variant B: //! }; void setStyle(DocumentationStyle style); |