aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cpptools/doxygengenerator.cpp
diff options
context:
space:
mode:
authorLeandro Melo <leandro.melo@nokia.com>2011-12-07 15:05:02 +0100
committerLeandro Melo <leandro.melo@nokia.com>2011-12-09 10:25:59 +0100
commitbeede7d7cff3e740ec0b0053ae9e382693e7f42c (patch)
tree74c0ffc3cad7569aa2f7946a2884dfd6d89c7361 /src/plugins/cpptools/doxygengenerator.cpp
parent24b4c127372c6a9c496c8d0bd812696f6ad0f4d4 (diff)
C++: Automatic Doxygen comment blocks generation
This improves our completion support for documentation comments. It's now possible to have a Doxygen block generated when hitting enter after a /** or /*! comment start. A couple other related options are also available. Task-number: QTCREATORBUG-2752 Task-number: QTCREATORBUG-3165 Change-Id: I1c81c0b4b370eb1d409ef72a9c7f22c357f202f4 Reviewed-by: Leena Miettinen <riitta-leena.miettinen@nokia.com> Reviewed-by: Christian Kamm <christian.d.kamm@nokia.com>
Diffstat (limited to 'src/plugins/cpptools/doxygengenerator.cpp')
-rw-r--r--src/plugins/cpptools/doxygengenerator.cpp289
1 files changed, 289 insertions, 0 deletions
diff --git a/src/plugins/cpptools/doxygengenerator.cpp b/src/plugins/cpptools/doxygengenerator.cpp
new file mode 100644
index 0000000000..8e3df8d6b8
--- /dev/null
+++ b/src/plugins/cpptools/doxygengenerator.cpp
@@ -0,0 +1,289 @@
+#include "doxygengenerator.h"
+
+#include <cplusplus/SimpleLexer.h>
+#include <cplusplus/BackwardsScanner.h>
+#include <cplusplus/Token.h>
+#include <cplusplus/TranslationUnit.h>
+#include <cplusplus/AST.h>
+#include <cplusplus/Symbols.h>
+#include <cplusplus/CppDocument.h>
+#include <cplusplus/Scope.h>
+#include <cplusplus/LookupContext.h>
+
+#include <QtCore/QStringBuilder>
+#include <QtGui/QTextDocument>
+#include <QDebug>
+
+using namespace CppTools;
+using namespace CPlusPlus;
+
+DoxygenGenerator::DoxygenGenerator()
+ : m_addLeadingAsterisks(true)
+ , m_generateBrief(true)
+ , m_startComment(true)
+ , m_style(QtStyle)
+ , m_commentOffset(0)
+{}
+
+void DoxygenGenerator::setStyle(DocumentationStyle style)
+{
+ m_style = style;
+}
+
+void DoxygenGenerator::setStartComment(bool start)
+{
+ m_startComment = start;
+}
+
+void DoxygenGenerator::setGenerateBrief(bool get)
+{
+ m_generateBrief = get;
+}
+
+void DoxygenGenerator::setAddLeadingAsterisks(bool add)
+{
+ m_addLeadingAsterisks = add;
+}
+
+QString DoxygenGenerator::generate(QTextCursor cursor)
+{
+ const QChar &c = cursor.document()->characterAt(cursor.position());
+ if (!c.isLetter() && c != QLatin1Char('_'))
+ return QString();
+
+ // Try to find what would be the declaration we are interested in.
+ SimpleLexer lexer;
+ QTextBlock block = cursor.block();
+ while (block.isValid()) {
+ const QString &text = block.text();
+ const QList<Token> &tks = lexer(text);
+ foreach (const Token &tk, tks) {
+ if (tk.is(T_SEMICOLON) || tk.is(T_LBRACE)) {
+ // No need to continue beyond this, we might already have something meaningful.
+ cursor.setPosition(block.position() + tk.end(), QTextCursor::KeepAnchor);
+ break;
+ }
+ }
+
+ if (cursor.hasSelection())
+ break;
+
+ block = block.next();
+ }
+
+ if (!cursor.hasSelection())
+ return QString();
+
+ QString declCandidate = cursor.selectedText();
+ declCandidate.replace(QChar::ParagraphSeparator, QLatin1Char('\n'));
+
+ // Let's append a closing brace in the case we got content like 'class MyType {'
+ if (declCandidate.endsWith(QLatin1Char('{')))
+ declCandidate.append(QLatin1Char('}'));
+
+ Document::Ptr doc = Document::create(QLatin1String("<doxygen>"));
+ doc->setSource(declCandidate.toUtf8());
+ doc->parse(Document::ParseDeclaration);
+ doc->check(Document::FastCheck);
+
+ if (!doc->translationUnit()
+ || !doc->translationUnit()->ast()
+ || !doc->translationUnit()->ast()->asDeclaration()) {
+ return QString();
+ }
+
+ return generate(cursor, doc->translationUnit()->ast()->asDeclaration());
+}
+
+QString DoxygenGenerator::generate(QTextCursor cursor, CPlusPlus::DeclarationAST *decl)
+{
+ SpecifierAST *spec = 0;
+ DeclaratorAST *decltr = 0;
+ if (SimpleDeclarationAST *simpleDecl = decl->asSimpleDeclaration()) {
+ if (simpleDecl->declarator_list
+ && simpleDecl->declarator_list->value) {
+ decltr = simpleDecl->declarator_list->value;
+ } else if (simpleDecl->decl_specifier_list
+ && simpleDecl->decl_specifier_list->value) {
+ spec = simpleDecl->decl_specifier_list->value;
+ }
+ } else if (FunctionDefinitionAST * defDecl = decl->asFunctionDefinition()) {
+ decltr = defDecl->declarator;
+ }
+
+ assignCommentOffset(cursor);
+
+ QString comment;
+ if (m_startComment)
+ writeStart(&comment);
+ writeNewLine(&comment);
+ writeContinuation(&comment);
+
+ if (decltr
+ && decltr->core_declarator
+ && decltr->core_declarator->asDeclaratorId()
+ && decltr->core_declarator->asDeclaratorId()->name) {
+ CoreDeclaratorAST *coreDecl = decltr->core_declarator;
+ if (m_generateBrief)
+ writeBrief(&comment, m_printer.prettyName(coreDecl->asDeclaratorId()->name->name));
+ else
+ writeNewLine(&comment);
+
+ if (decltr->postfix_declarator_list
+ && decltr->postfix_declarator_list->value
+ && decltr->postfix_declarator_list->value->asFunctionDeclarator()) {
+ FunctionDeclaratorAST *funcDecltr =
+ decltr->postfix_declarator_list->value->asFunctionDeclarator();
+ if (funcDecltr->parameter_declaration_clause
+ && funcDecltr->parameter_declaration_clause->parameter_declaration_list) {
+ for (ParameterDeclarationListAST *it =
+ funcDecltr->parameter_declaration_clause->parameter_declaration_list;
+ it;
+ it = it->next) {
+ ParameterDeclarationAST *paramDecl = it->value;
+ if (paramDecl->declarator
+ && paramDecl->declarator->core_declarator
+ && paramDecl->declarator->core_declarator->asDeclaratorId()
+ && paramDecl->declarator->core_declarator->asDeclaratorId()->name) {
+ DeclaratorIdAST *paramId =
+ paramDecl->declarator->core_declarator->asDeclaratorId();
+ writeContinuation(&comment);
+ writeCommand(&comment,
+ ParamCommand,
+ m_printer.prettyName(paramId->name->name));
+ }
+ }
+ }
+ if (funcDecltr->symbol
+ && funcDecltr->symbol->returnType().type()
+ && !funcDecltr->symbol->returnType()->isVoidType()
+ && !funcDecltr->symbol->returnType()->isUndefinedType()) {
+ writeContinuation(&comment);
+ writeCommand(&comment, ReturnCommand);
+ }
+ }
+ } else if (spec && m_generateBrief) {
+ bool briefWritten = false;
+ if (ClassSpecifierAST *classSpec = spec->asClassSpecifier()) {
+ if (classSpec->name) {
+ QString aggregate;
+ if (classSpec->symbol->isClass())
+ aggregate = QLatin1String("class");
+ else if (classSpec->symbol->isStruct())
+ aggregate = QLatin1String("struct");
+ else
+ aggregate = QLatin1String("union");
+ writeBrief(&comment,
+ m_printer.prettyName(classSpec->name->name),
+ QLatin1String("The"),
+ aggregate);
+ briefWritten = true;
+ }
+ } else if (EnumSpecifierAST *enumSpec = spec->asEnumSpecifier()) {
+ if (enumSpec->name) {
+ writeBrief(&comment,
+ m_printer.prettyName(enumSpec->name->name),
+ QLatin1String("The"),
+ QLatin1String("enum"));
+ briefWritten = true;
+ }
+ }
+ if (!briefWritten)
+ writeNewLine(&comment);
+ } else {
+ writeNewLine(&comment);
+ }
+
+ writeEnd(&comment);
+
+ return comment;
+}
+
+QChar DoxygenGenerator::startMark() const
+{
+ if (m_style == QtStyle)
+ return QLatin1Char('!');
+ return QLatin1Char('*');
+}
+
+QChar DoxygenGenerator::styleMark() const
+{
+ if (m_style == QtStyle)
+ return QLatin1Char('\\');
+ return QLatin1Char('@');
+}
+
+QString DoxygenGenerator::commandSpelling(Command command)
+{
+ if (command == ParamCommand)
+ return QLatin1String("param ");
+ if (command == ReturnCommand)
+ return QLatin1String("return ");
+
+ Q_ASSERT(command == BriefCommand);
+ return QLatin1String("brief ");
+}
+
+void DoxygenGenerator::writeStart(QString *comment) const
+{
+ comment->append(offsetString() % QLatin1String("/*") % startMark());
+}
+
+void DoxygenGenerator::writeEnd(QString *comment) const
+{
+ comment->append(offsetString() % QLatin1String(" */"));
+}
+
+void DoxygenGenerator::writeContinuation(QString *comment) const
+{
+ if (m_addLeadingAsterisks)
+ comment->append(offsetString() % QLatin1String(" *"));
+ else
+ comment->append(offsetString() % QLatin1String(" "));
+}
+
+void DoxygenGenerator::writeNewLine(QString *comment) const
+{
+ comment->append(QLatin1Char('\n'));
+}
+
+void DoxygenGenerator::writeCommand(QString *comment,
+ Command command,
+ const QString &commandContent) const
+{
+ comment->append(QLatin1Char(' ')
+ % styleMark()
+ % commandSpelling(command)
+ % commandContent
+ % QLatin1Char('\n'));
+}
+
+void DoxygenGenerator::writeBrief(QString *comment,
+ const QString &brief,
+ const QString &prefix,
+ const QString &suffix)
+{
+ QString content = prefix % QLatin1Char(' ') % brief % QLatin1Char(' ') % suffix;
+ writeCommand(comment, BriefCommand, content.trimmed());
+}
+
+void DoxygenGenerator::assignCommentOffset(QTextCursor cursor)
+{
+ if (cursor.hasSelection()) {
+ if (cursor.anchor() < cursor.position())
+ cursor.setPosition(cursor.anchor());
+ }
+
+ m_commentOffset = cursor.positionInBlock();
+}
+
+QString DoxygenGenerator::offsetString() const
+{
+ // Note: Currently we don't indent comments, but simply preserve them in the original
+ // relative positions. What we do here is just to make sure that such positions are correct,
+ // although they might still be wrong from an indentation point of view (for instance,
+ // using spaces instead of tabs). Therefore, the content generated should still have
+ // the indentation strings fixed.
+
+ return QString(m_commentOffset, QLatin1Char(' '));
+}