diff options
author | Leandro Melo <leandro.melo@nokia.com> | 2011-12-07 15:05:02 +0100 |
---|---|---|
committer | Leandro Melo <leandro.melo@nokia.com> | 2011-12-09 10:25:59 +0100 |
commit | beede7d7cff3e740ec0b0053ae9e382693e7f42c (patch) | |
tree | 74c0ffc3cad7569aa2f7946a2884dfd6d89c7361 /src/plugins/cpptools/doxygengenerator.cpp | |
parent | 24b4c127372c6a9c496c8d0bd812696f6ad0f4d4 (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.cpp | 289 |
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(' ')); +} |