aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKnut Petter Svendsen <knutpett@pvv.org>2013-02-21 05:45:44 +0100
committerDavid Schulz <david.schulz@digia.com>2013-02-21 13:34:25 +0100
commitc937226db1c3c2d98e4a73d7dacde8e35d8cbc1c (patch)
treeab5f30bb19d4e17c1854615c4bba35a07aa1a925
parent8d2f40609263396e7809c608c52fd2c8713db668 (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.cpp123
-rw-r--r--src/plugins/cppeditor/cppeditor.cpp262
-rw-r--r--src/plugins/cppeditor/cppeditor.h1
-rw-r--r--src/plugins/cppeditor/cppplugin.h6
-rw-r--r--src/plugins/cpptools/completionsettingspage.ui2
-rw-r--r--src/plugins/cpptools/doxygengenerator.cpp22
-rw-r--r--src/plugins/cpptools/doxygengenerator.h6
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 &currentBlock = 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 &currentBlock = 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);