diff --git a/src/plugins/cppeditor/cppdeclfromdef.cpp b/src/plugins/cppeditor/cppdeclfromdef.cpp index 23a11d7a500d090c395670dbfadc34f5d1984c46..d874a474096fea8a2f1a3edb44a4c78cb126371e 100644 --- a/src/plugins/cppeditor/cppdeclfromdef.cpp +++ b/src/plugins/cppeditor/cppdeclfromdef.cpp @@ -45,6 +45,7 @@ #include <QtCore/QCoreApplication> using namespace CPlusPlus; +using namespace CppEditor; using namespace CppEditor::Internal; using namespace CppTools; @@ -146,22 +147,54 @@ QString prettyMinimalType(const FullySpecifiedType &ty, return oo(ty); } -} // anonymous namespace +class Operation: public CppQuickFixOperation +{ +public: + Operation(const CppQuickFixState &state, int priority, + const QString &targetFileName, const QString &targetSymbolName, + const QString &decl) + : CppQuickFixOperation(state, priority) + , m_targetFileName(targetFileName) + , m_targetSymbolName(targetSymbolName) + , m_decl(decl) + { + setDescription(QCoreApplication::tr("Create Declaration from Definition", + "CppEditor::DeclFromDef")); + } -DeclFromDef::DeclFromDef(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor) -{} + void createChanges() + { + CppRefactoringChanges *changes = refactoringChanges(); -QString DeclFromDef::description() const -{ - return QCoreApplication::tr("Create Declaration from Definition", "DeclFromDef"); -} + Document::Ptr targetDoc = changes->document(m_targetFileName); + InsertionPointFinder findInsertionPoint(targetDoc, m_targetSymbolName); + int line = 0, column = 0; + findInsertionPoint(&line, &column); + + int targetPosition1 = changes->positionInFile(m_targetFileName, line, column); + int targetPosition2 = changes->positionInFile(m_targetFileName, line + 1, 0) - 1; + + Utils::ChangeSet target; + target.insert(targetPosition1, m_decl); + changes->changeFile(m_targetFileName, target); + + changes->reindent(m_targetFileName, + Utils::ChangeSet::Range(targetPosition1, targetPosition2)); + + changes->openEditor(m_targetFileName, line, column); + } + +private: + QString m_targetFileName; + QString m_targetSymbolName; + QString m_decl; +}; + +} // anonymous namespace -int DeclFromDef::match(const QList<CPlusPlus::AST *> &path) +QList<CppQuickFixOperation::Ptr> DeclFromDef::match(const CppQuickFixState &state) { - m_targetFileName.clear(); - m_targetSymbolName.clear(); - m_decl.clear(); + const QList<AST *> &path = state.path(); FunctionDefinitionAST *funDef = 0; int idx = 0; @@ -171,62 +204,40 @@ int DeclFromDef::match(const QList<CPlusPlus::AST *> &path) if (!funDef) funDef = candidate; } else if (node->asClassSpecifier()) { - return -1; + return noResult(); } } if (!funDef || !funDef->symbol) - return -1; + return noResult(); Function *method = funDef->symbol; - LookupContext context(document(), snapshot()); - if (ClassOrNamespace *targetBinding = context.lookupParent(method)) { + if (ClassOrNamespace *targetBinding = state.context().lookupParent(method)) { foreach (Symbol *s, targetBinding->symbols()) { if (Class *clazz = s->asClass()) { - m_targetFileName = QLatin1String(clazz->fileName()); - m_targetSymbolName = QLatin1String(clazz->identifier()->chars()); - - m_decl = generateDeclaration(method, targetBinding); - - return idx; + return singleResult(new Operation(state, idx, + QLatin1String(clazz->fileName()), + QLatin1String(clazz->identifier()->chars()), + generateDeclaration(state, + method, + targetBinding))); } // ### TODO: support insertion into namespaces } } - return -1; -} - -void DeclFromDef::createChanges() -{ - CppRefactoringChanges *changes = refactoringChanges(); - - Document::Ptr targetDoc = changes->document(m_targetFileName); - InsertionPointFinder findInsertionPoint(targetDoc, m_targetSymbolName); - int line = 0, column = 0; - findInsertionPoint(&line, &column); - - int targetPosition1 = changes->positionInFile(m_targetFileName, line, column); - int targetPosition2 = changes->positionInFile(m_targetFileName, line + 1, 0) - 1; - - Utils::ChangeSet target; - target.insert(targetPosition1, m_decl); - changes->changeFile(m_targetFileName, target); - - changes->reindent(m_targetFileName, - Utils::ChangeSet::Range(targetPosition1, targetPosition2)); - - changes->openEditor(m_targetFileName, line, column); + return noResult(); } -QString DeclFromDef::generateDeclaration(Function *method, ClassOrNamespace *targetBinding) +QString DeclFromDef::generateDeclaration(const CppQuickFixState &state, + Function *method, + ClassOrNamespace *targetBinding) { - LookupContext context(document(), snapshot()); Overview oo; QString decl; decl.append(prettyMinimalType(method->returnType(), - context, + state.context(), method->scope(), targetBinding)); @@ -238,7 +249,7 @@ QString DeclFromDef::generateDeclaration(Function *method, ClassOrNamespace *tar decl.append(QLatin1String(", ")); Argument *arg = method->argumentAt(argIdx)->asArgument(); decl.append(prettyMinimalType(arg->type(), - context, + state.context(), method->members(), targetBinding)); decl.append(QLatin1Char(' ')); diff --git a/src/plugins/cppeditor/cppdeclfromdef.h b/src/plugins/cppeditor/cppdeclfromdef.h index 8cbebc9271b3cd77777601e4da3254ac92cb4eff..8b71ef78c046b5c76c2385f3407307f34530d099 100644 --- a/src/plugins/cppeditor/cppdeclfromdef.h +++ b/src/plugins/cppeditor/cppdeclfromdef.h @@ -41,23 +41,15 @@ class ClassOrNamespace; namespace CppEditor { namespace Internal { -class DeclFromDef: public CppQuickFixOperation +class DeclFromDef: public CppQuickFixFactory { public: - DeclFromDef(TextEditor::BaseTextEditor *editor); - - virtual int match(const QList<CPlusPlus::AST *> &path); - virtual QString description() const; - virtual void createChanges(); + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state); protected: - virtual QString generateDeclaration(CPlusPlus::Function *method, - CPlusPlus::ClassOrNamespace *targetBinding); - -private: - QString m_targetFileName; - QString m_targetSymbolName; - QString m_decl; + static QString generateDeclaration(const CppQuickFixState &state, + CPlusPlus::Function *method, + CPlusPlus::ClassOrNamespace *targetBinding); }; diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 274639f4141756b2bc76a6c263f93b6a736f0079..6707b2d3e6a9fbbf580609cc85a82350214f7130 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -30,6 +30,7 @@ SOURCES += cppplugin.cpp \ cppfilewizard.cpp \ cppclasswizard.cpp \ cppquickfix.cpp \ + cppquickfixes.cpp \ cpprefactoringchanges.cpp \ cppchecksymbols.cpp \ cppsemanticinfo.cpp \ diff --git a/src/plugins/cppeditor/cppplugin.cpp b/src/plugins/cppeditor/cppplugin.cpp index 2b9e980f827da7a19fd53b993eab8e154a5bb937..9d269b536efc4199005005d4178fc296c650f03f 100644 --- a/src/plugins/cppeditor/cppplugin.cpp +++ b/src/plugins/cppeditor/cppplugin.cpp @@ -63,6 +63,7 @@ #include <QtGui/QMenu> +using namespace CppEditor; using namespace CppEditor::Internal; enum { QUICKFIX_INTERVAL = 20 }; @@ -208,8 +209,7 @@ bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMess m_quickFixCollector = new CppQuickFixCollector; addAutoReleasedObject(m_quickFixCollector); - - addAutoReleasedObject(new CppQuickFixFactory); + CppQuickFixCollector::registerQuickFixes(this); CppFileWizard::BaseFileWizardParameters wizardParameters(Core::IWizard::FileWizard); diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp index fdc9d676ebcada2a5db19b501c5fd8da1e25aa19..eb452c598b9971f341a670394d41aeb1885f36f0 100644 --- a/src/plugins/cppeditor/cppquickfix.cpp +++ b/src/plugins/cppeditor/cppquickfix.cpp @@ -31,6 +31,10 @@ #include "cppeditor.h" #include "cppdeclfromdef.h" +#include <AST.h> +#include <TranslationUnit.h> +#include <Token.h> + #include <cplusplus/ASTPath.h> #include <cplusplus/CppDocument.h> #include <cplusplus/ResolveExpression.h> @@ -39,1702 +43,118 @@ #include <cplusplus/DependencyTable.h> #include <cplusplus/CppRewriter.h> -#include <TranslationUnit.h> -#include <ASTVisitor.h> -#include <AST.h> -#include <ASTPatternBuilder.h> -#include <ASTMatcher.h> -#include <Token.h> -#include <Type.h> -#include <CoreTypes.h> -#include <Symbol.h> -#include <Symbols.h> -#include <Name.h> -#include <Names.h> -#include <Literals.h> - #include <cppeditor/cppeditor.h> #include <cppeditor/cpprefactoringchanges.h> #include <cpptools/cpptoolsconstants.h> #include <cpptools/cppmodelmanagerinterface.h> #include <extensionsystem/pluginmanager.h> -#include <QtGui/QApplication> #include <QtGui/QTextBlock> +using namespace CppEditor; using namespace CppEditor::Internal; +using namespace TextEditor; using namespace CPlusPlus; using namespace Utils; -namespace { - -class CppQuickFixState: public TextEditor::QuickFixState -{ -public: - QList<CPlusPlus::AST *> path; - Snapshot snapshot; - SemanticInfo info; -}; - -/* - Rewrite - a op b -> !(a invop b) - (a op b) -> !(a invop b) - !(a op b) -> (a invob b) -*/ -class UseInverseOp: public CppQuickFixOperation -{ -public: - UseInverseOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), binary(0), nested(0), negation(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); - } - - virtual int match(const QList<AST *> &path) - { - int index = path.size() - 1; - binary = path.at(index)->asBinaryExpression(); - if (! binary) - return -1; - if (! isCursorOn(binary->binary_op_token)) - return -1; - - CPlusPlus::Kind invertToken; - switch (tokenAt(binary->binary_op_token).kind()) { - case T_LESS_EQUAL: - invertToken = T_GREATER; - break; - case T_LESS: - invertToken = T_GREATER_EQUAL; - break; - case T_GREATER: - invertToken = T_LESS_EQUAL; - break; - case T_GREATER_EQUAL: - invertToken = T_LESS; - break; - case T_EQUAL_EQUAL: - invertToken = T_EXCLAIM_EQUAL; - break; - case T_EXCLAIM_EQUAL: - invertToken = T_EQUAL_EQUAL; - break; - default: - return -1; - } - - CPlusPlus::Token tok; - tok.f.kind = invertToken; - replacement = QLatin1String(tok.spell()); - - // check for enclosing nested expression - if (index - 1 >= 0) - nested = path[index - 1]->asNestedExpression(); - - // check for ! before parentheses - if (nested && index - 2 >= 0) { - negation = path[index - 2]->asUnaryExpression(); - if (negation && ! tokenAt(negation->unary_op_token).is(T_EXCLAIM)) - negation = 0; - } - - return index; - } - - virtual void createChanges() - { - ChangeSet changes; - if (negation) { - // can't remove parentheses since that might break precedence - changes.remove(range(negation->unary_op_token)); - } else if (nested) { - changes.insert(startOf(nested), "!"); - } else { - changes.insert(startOf(binary), "!("); - changes.insert(endOf(binary), ")"); - } - changes.replace(range(binary->binary_op_token), replacement); - refactoringChanges()->changeFile(fileName(), changes); - } - -private: - BinaryExpressionAST *binary; - NestedExpressionAST *nested; - UnaryExpressionAST *negation; - - QString replacement; -}; - -/* - Rewrite - a op b - - As - b flipop a -*/ -class FlipBinaryOp: public CppQuickFixOperation -{ -public: - FlipBinaryOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), binary(0) - {} - - - virtual QString description() const - { - if (replacement.isEmpty()) - return QApplication::translate("CppTools::QuickFix", "Swap Operands"); - else - return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); - } - - virtual int match(const QList<AST *> &path) - { - int index = path.size() - 1; - binary = path.at(index)->asBinaryExpression(); - if (! binary) - return -1; - if (! isCursorOn(binary->binary_op_token)) - return -1; - - CPlusPlus::Kind flipToken; - switch (tokenAt(binary->binary_op_token).kind()) { - case T_LESS_EQUAL: - flipToken = T_GREATER_EQUAL; - break; - case T_LESS: - flipToken = T_GREATER; - break; - case T_GREATER: - flipToken = T_LESS; - break; - case T_GREATER_EQUAL: - flipToken = T_LESS_EQUAL; - break; - case T_EQUAL_EQUAL: - case T_EXCLAIM_EQUAL: - case T_AMPER_AMPER: - case T_PIPE_PIPE: - flipToken = T_EOF_SYMBOL; - break; - default: - return -1; - } - - if (flipToken != T_EOF_SYMBOL) { - CPlusPlus::Token tok; - tok.f.kind = flipToken; - replacement = QLatin1String(tok.spell()); - } - return index; - } - - virtual void createChanges() - { - ChangeSet changes; - - changes.flip(range(binary->left_expression), range(binary->right_expression)); - if (! replacement.isEmpty()) - changes.replace(range(binary->binary_op_token), replacement); - - refactoringChanges()->changeFile(fileName(), changes); - } - -private: - BinaryExpressionAST *binary; - QString replacement; -}; - -/* - Rewrite - !a && !b - - As - !(a || b) -*/ -class RewriteLogicalAndOp: public CppQuickFixOperation -{ -public: - RewriteLogicalAndOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), left(0), right(0), pattern(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Rewrite Condition Using ||"); - } - - virtual int match(const QList<AST *> &path) - { - BinaryExpressionAST *expression = 0; - - int index = path.size() - 1; - for (; index != -1; --index) { - expression = path.at(index)->asBinaryExpression(); - if (expression) - break; - } - - if (! expression) - return -1; - - if (! isCursorOn(expression->binary_op_token)) - return -1; - - left = mk.UnaryExpression(); - right = mk.UnaryExpression(); - pattern = mk.BinaryExpression(left, right); - - if (expression->match(pattern, &matcher) && - tokenAt(pattern->binary_op_token).is(T_AMPER_AMPER) && - tokenAt(left->unary_op_token).is(T_EXCLAIM) && - tokenAt(right->unary_op_token).is(T_EXCLAIM)) { - return index; - } - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - changes.replace(range(pattern->binary_op_token), QLatin1String("||")); - changes.remove(range(left->unary_op_token)); - changes.remove(range(right->unary_op_token)); - const int start = startOf(pattern); - const int end = endOf(pattern); - changes.insert(start, QLatin1String("!(")); - changes.insert(end, QLatin1String(")")); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(pattern)); - } - -private: - ASTMatcher matcher; - ASTPatternBuilder mk; - UnaryExpressionAST *left; - UnaryExpressionAST *right; - BinaryExpressionAST *pattern; -}; - -class SplitSimpleDeclarationOp: public CppQuickFixOperation -{ -public: - SplitSimpleDeclarationOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), declaration(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Split Declaration"); - } - - bool checkDeclaration(SimpleDeclarationAST *declaration) const - { - if (! declaration->semicolon_token) - return false; - - if (! declaration->decl_specifier_list) - return false; - - for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) { - SpecifierAST *specifier = it->value; - - if (specifier->asEnumSpecifier() != 0) - return false; - - else if (specifier->asClassSpecifier() != 0) - return false; - } - - if (! declaration->declarator_list) - return false; - - else if (! declaration->declarator_list->next) - return false; - - return true; - } - - virtual int match(const QList<AST *> &path) - { - CoreDeclaratorAST *core_declarator = 0; - - int index = path.size() - 1; - for (; index != -1; --index) { - AST *node = path.at(index); - - if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) - core_declarator = coreDecl; - - else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { - if (checkDeclaration(simpleDecl)) { - declaration = simpleDecl; - - const int cursorPosition = selectionStart(); - - const int startOfDeclSpecifier = startOf(declaration->decl_specifier_list->firstToken()); - const int endOfDeclSpecifier = endOf(declaration->decl_specifier_list->lastToken() - 1); - - if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) - return index; // the AST node under cursor is a specifier. - - if (core_declarator && isCursorOn(core_declarator)) - return index; // got a core-declarator under the text cursor. - } - - break; - } - } - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - - SpecifierListAST *specifiers = declaration->decl_specifier_list; - int declSpecifiersStart = startOf(specifiers->firstToken()); - int declSpecifiersEnd = endOf(specifiers->lastToken() - 1); - int insertPos = endOf(declaration->semicolon_token); - - DeclaratorAST *prevDeclarator = declaration->declarator_list->value; - - for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) { - DeclaratorAST *declarator = it->value; - - changes.insert(insertPos, QLatin1String("\n")); - changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos); - changes.insert(insertPos, QLatin1String(" ")); - changes.move(range(declarator), insertPos); - changes.insert(insertPos, QLatin1String(";")); - - const int prevDeclEnd = endOf(prevDeclarator); - changes.remove(prevDeclEnd, startOf(declarator)); - - prevDeclarator = declarator; - } - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(declaration)); - } - -private: - SimpleDeclarationAST *declaration; -}; - -/* - Add curly braces to a if statement that doesn't already contain a - compound statement. -*/ -class AddBracesToIfOp: public CppQuickFixOperation -{ -public: - AddBracesToIfOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), _statement(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Add Curly Braces"); - } - - virtual int match(const QList<AST *> &path) - { - // show when we're on the 'if' of an if statement - int index = path.size() - 1; - IfStatementAST *ifStatement = path.at(index)->asIfStatement(); - if (ifStatement && isCursorOn(ifStatement->if_token) && ifStatement->statement - && ! ifStatement->statement->asCompoundStatement()) { - _statement = ifStatement->statement; - return index; - } - - // or if we're on the statement contained in the if - // ### This may not be such a good idea, consider nested ifs... - for (; index != -1; --index) { - IfStatementAST *ifStatement = path.at(index)->asIfStatement(); - if (ifStatement && ifStatement->statement - && isCursorOn(ifStatement->statement) - && ! ifStatement->statement->asCompoundStatement()) { - _statement = ifStatement->statement; - return index; - } - } - - // ### This could very well be extended to the else branch - // and other nodes entirely. - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - - const int start = endOf(_statement->firstToken() - 1); - changes.insert(start, QLatin1String(" {")); - - const int end = endOf(_statement->lastToken() - 1); - changes.insert(end, "\n}"); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(start, end)); - } - -private: - StatementAST *_statement; -}; +CppQuickFixState::CppQuickFixState(TextEditor::BaseTextEditor *editor) + : QuickFixState(editor) +{} -/* - Replace - if (Type name = foo()) {...} - - With - Type name = foo; - if (name) {...} -*/ -class MoveDeclarationOutOfIfOp: public CppQuickFixOperation +const QList<AST *> &CppQuickFixState::path() const { -public: - MoveDeclarationOutOfIfOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), condition(0), pattern(0), core(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition"); - } - - virtual int match(const QList<AST *> &path) - { - condition = mk.Condition(); - pattern = mk.IfStatement(condition); - - int index = path.size() - 1; - for (; index != -1; --index) { - if (IfStatementAST *statement = path.at(index)->asIfStatement()) { - if (statement->match(pattern, &matcher) && condition->declarator) { - DeclaratorAST *declarator = condition->declarator; - core = declarator->core_declarator; - if (! core) - return -1; - - if (isCursorOn(core)) - return index; - } - } - } - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - - changes.copy(range(core), startOf(condition)); - - int insertPos = startOf(pattern); - changes.move(range(condition), insertPos); - changes.insert(insertPos, QLatin1String(";\n")); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(pattern)); - } - -private: - ASTMatcher matcher; - ASTPatternBuilder mk; - CPPEditor *editor; - ConditionAST *condition; - IfStatementAST *pattern; - CoreDeclaratorAST *core; -}; - -/* - Replace - while (Type name = foo()) {...} - - With - Type name; - while ((name = foo()) != 0) {...} -*/ -class MoveDeclarationOutOfWhileOp: public CppQuickFixOperation -{ -public: - MoveDeclarationOutOfWhileOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), condition(0), pattern(0), core(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition"); - } - - virtual int match(const QList<AST *> &path) - { - condition = mk.Condition(); - pattern = mk.WhileStatement(condition); - - int index = path.size() - 1; - for (; index != -1; --index) { - if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) { - if (statement->match(pattern, &matcher) && condition->declarator) { - DeclaratorAST *declarator = condition->declarator; - core = declarator->core_declarator; - - if (! core) - return -1; - - else if (! declarator->equals_token) - return -1; - - else if (! declarator->initializer) - return -1; - - if (isCursorOn(core)) - return index; - } - } - } - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - - changes.insert(startOf(condition), QLatin1String("(")); - changes.insert(endOf(condition), QLatin1String(") != 0")); - - int insertPos = startOf(pattern); - const int conditionStart = startOf(condition); - changes.move(conditionStart, startOf(core), insertPos); - changes.copy(range(core), insertPos); - changes.insert(insertPos, QLatin1String(";\n")); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(pattern)); - } - -private: - ASTMatcher matcher; - ASTPatternBuilder mk; - CPPEditor *editor; - ConditionAST *condition; - WhileStatementAST *pattern; - CoreDeclaratorAST *core; -}; - -/* - Replace - if (something && something_else) { - } - - with - if (something) { - if (something_else) { - } - } - - and - if (something || something_else) - x; - - with - if (something) - x; - else if (something_else) - x; -*/ -class SplitIfStatementOp: public CppQuickFixOperation -{ -public: - SplitIfStatementOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), condition(0), pattern(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Split if Statement"); - } - - virtual int match(const QList<AST *> &path) - { - pattern = 0; - - int index = path.size() - 1; - for (; index != -1; --index) { - AST *node = path.at(index); - if (IfStatementAST *stmt = node->asIfStatement()) { - pattern = stmt; - break; - } - } - - if (! pattern || ! pattern->statement) - return -1; - - unsigned splitKind = 0; - for (++index; index < path.size(); ++index) { - AST *node = path.at(index); - condition = node->asBinaryExpression(); - if (! condition) - return -1; - - Token binaryToken = tokenAt(condition->binary_op_token); - - // only accept a chain of ||s or &&s - no mixing - if (! splitKind) { - splitKind = binaryToken.kind(); - if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE) - return -1; - // we can't reliably split &&s in ifs with an else branch - if (splitKind == T_AMPER_AMPER && pattern->else_statement) - return -1; - } else if (splitKind != binaryToken.kind()) { - return -1; - } - - if (isCursorOn(condition->binary_op_token)) - return index; - } - - return -1; - } - - virtual void createChanges() - { - Token binaryToken = tokenAt(condition->binary_op_token); - - if (binaryToken.is(T_AMPER_AMPER)) - splitAndCondition(); - else - splitOrCondition(); - } - - void splitAndCondition() - { - ChangeSet changes; - - int startPos = startOf(pattern); - changes.insert(startPos, QLatin1String("if (")); - changes.move(range(condition->left_expression), startPos); - changes.insert(startPos, QLatin1String(") {\n")); - - const int lExprEnd = endOf(condition->left_expression); - changes.remove(lExprEnd, startOf(condition->right_expression)); - changes.insert(endOf(pattern), QLatin1String("\n}")); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(pattern)); - } - - void splitOrCondition() - { - ChangeSet changes; - - StatementAST *ifTrueStatement = pattern->statement; - CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement(); - - int insertPos = endOf(ifTrueStatement); - if (compoundStatement) - changes.insert(insertPos, QLatin1String(" ")); - else - changes.insert(insertPos, QLatin1String("\n")); - changes.insert(insertPos, QLatin1String("else if (")); - - const int rExprStart = startOf(condition->right_expression); - changes.move(rExprStart, startOf(pattern->rparen_token), insertPos); - changes.insert(insertPos, QLatin1String(")")); - - const int rParenEnd = endOf(pattern->rparen_token); - changes.copy(rParenEnd, endOf(pattern->statement), insertPos); - - const int lExprEnd = endOf(condition->left_expression); - changes.remove(lExprEnd, startOf(condition->right_expression)); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(pattern)); - } - -private: - BinaryExpressionAST *condition; - IfStatementAST *pattern; -}; - -/* - Replace - "abcd" - With - QLatin1String("abcd") -*/ -class WrapStringLiteral: public CppQuickFixOperation -{ -public: - WrapStringLiteral(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), literal(0), type(TypeNone) - {} - - enum Type { TypeString, TypeObjCString, TypeChar, TypeNone }; - - virtual QString description() const - { - if (type == TypeChar) - return QApplication::translate("CppTools::QuickFix", "Enclose in QLatin1Char(...)"); - return QApplication::translate("CppTools::QuickFix", "Enclose in QLatin1String(...)"); - } - - virtual int match(const QList<AST *> &path) - { - literal = 0; - type = TypeNone; - - if (path.isEmpty()) - return -1; // nothing to do - - literal = path.last()->asStringLiteral(); - - if (! literal) { - literal = path.last()->asNumericLiteral(); - if (!literal || !tokenAt(literal->asNumericLiteral()->literal_token).is(T_CHAR_LITERAL)) - return -1; - else - type = TypeChar; - } else { - type = TypeString; - } - - if (path.size() > 1) { - if (CallAST *call = path.at(path.size() - 2)->asCall()) { - if (call->base_expression) { - if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { - const QByteArray id(tokenAt(functionName->identifier_token).identifier->chars()); - - if (id == "QT_TRANSLATE_NOOP" || id == "tr" || id == "trUtf8" - || (type == TypeString && (id == "QLatin1String" || id == "QLatin1Literal")) - || (type == TypeChar && id == "QLatin1Char")) - return -1; // skip it - } - } - } - } - - if (type == TypeString) { - if (charAt(startOf(literal)) == QLatin1Char('@')) - type = TypeObjCString; - } - return path.size() - 1; // very high priority - } - - virtual void createChanges() - { - ChangeSet changes; - - const int startPos = startOf(literal); - QLatin1String replacement = (type == TypeChar ? QLatin1String("QLatin1Char(") - : QLatin1String("QLatin1String(")); - - if (type == TypeObjCString) - changes.replace(startPos, startPos + 1, replacement); - else - changes.insert(startPos, replacement); - - changes.insert(endOf(literal), ")"); - - refactoringChanges()->changeFile(fileName(), changes); - } - -private: - ExpressionAST *literal; - Type type; -}; - -/* - Replace - "abcd" - With - tr("abcd") or - QCoreApplication::translate("CONTEXT", "abcd") or - QT_TRANSLATE_NOOP("GLOBAL", "abcd") -*/ -class TranslateStringLiteral: public CppQuickFixOperation -{ -public: - TranslateStringLiteral(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), m_literal(0) - { } - - enum TranslationOption { unknown, useTr, useQCoreApplicationTranslate, useMacro }; - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Mark as translateable"); - } - - virtual int match(const QList<AST *> &path) - { - // Initialize - m_literal = 0; - m_option = unknown; - m_context.clear(); - - if (path.isEmpty()) - return -1; - - m_literal = path.last()->asStringLiteral(); - if (!m_literal) - return -1; // No string, nothing to do - - // Do we already have a translation markup? - if (path.size() >= 2) { - if (CallAST *call = path.at(path.size() - 2)->asCall()) { - if (call->base_expression) { - if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { - const QByteArray id(tokenAt(functionName->identifier_token).identifier->chars()); - - if (id == "tr" || id == "trUtf8" - || id == "translate" - || id == "QT_TRANSLATE_NOOP" - || id == "QLatin1String" || id == "QLatin1Literal") - return -1; // skip it - } - } - } - } - - QSharedPointer<Control> control = context().control(); - const Name *trName = control->nameId(control->findOrInsertIdentifier("tr")); - - // Check whether we are in a method: - for (int i = path.size() - 1; i >= 0; --i) - { - if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) { - Function *function = definition->symbol; - ClassOrNamespace *b = context().lookupType(function); - if (b) { - // Do we have a tr method? - foreach(const LookupItem &r, b->find(trName)) { - Symbol *s = r.declaration(); - if (s->type()->isFunctionType()) { - m_option = useTr; - // no context required for tr - return path.size() - 1; - } - } - } - // We need to do a QCA::translate, so we need a context. - // Use fully qualified class name: - Overview oo; - foreach (const Name *n, LookupContext::path(function)) { - if (!m_context.isEmpty()) - m_context.append(QLatin1String("::")); - m_context.append(oo.prettyName(n)); - } - // ... or global if none available! - if (m_context.isEmpty()) - m_context = QLatin1String("GLOBAL"); - m_option = useQCoreApplicationTranslate; - return path.size() - 1; - } - } - - // We need to use Q_TRANSLATE_NOOP - m_context = QLatin1String("GLOBAL"); - m_option = useMacro; - return path.size() - 1; - } - - virtual void createChanges() - { - ChangeSet changes; - - const int startPos = startOf(m_literal); - QString replacement(QLatin1String("tr(")); - if (m_option == useQCoreApplicationTranslate) { - replacement = QLatin1String("QCoreApplication::translate(\"") - + m_context + QLatin1String("\", "); - } else if (m_option == useMacro) { - replacement = QLatin1String("QT_TRANSLATE_NOOP(\"") - + m_context + QLatin1String("\", "); - } - - changes.insert(startPos, replacement); - changes.insert(endOf(m_literal), QLatin1String(")")); - - refactoringChanges()->changeFile(fileName(), changes); - } - -private: - ExpressionAST *m_literal; - TranslationOption m_option; - QString m_context; -}; - -class CStringToNSString: public CppQuickFixOperation -{ -public: - CStringToNSString(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor) - , stringLiteral(0) - , qlatin1Call(0) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Convert to Objective-C String Literal"); - } - - virtual int match(const QList<AST *> &path) - { - stringLiteral = 0; - qlatin1Call = 0; - - if (path.isEmpty()) - return -1; // nothing to do - - stringLiteral = path.last()->asStringLiteral(); - - if (! stringLiteral) - return -1; - - else if (charAt(startOf(stringLiteral)) == QLatin1Char('@')) - return -1; // it's already an objc string literal. - - else if (path.size() > 1) { - if (CallAST *call = path.at(path.size() - 2)->asCall()) { - if (call->base_expression) { - if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { - const QByteArray id(tokenAt(functionName->identifier_token).identifier->chars()); - - if (id == "QLatin1String" || id == "QLatin1Literal") - qlatin1Call = call; - } - } - } - } - - return path.size() - 1; // very high priority - } - - virtual void createChanges() - { - ChangeSet changes; - - if (qlatin1Call) { - changes.replace(startOf(qlatin1Call), startOf(stringLiteral), QLatin1String("@")); - changes.remove(endOf(stringLiteral), endOf(qlatin1Call)); - } else { - changes.insert(startOf(stringLiteral), "@"); - } - - refactoringChanges()->changeFile(fileName(), changes); - } - -private: - StringLiteralAST *stringLiteral; - CallAST *qlatin1Call; -}; - -/* - Base class for converting numeric literals between decimal, octal and hex. - Does the base check for the specific ones and parses the number. - Test cases: - 0xFA0Bu; - 0X856A; - 298.3; - 199; - 074; - 199L; - 074L; - -199; - -017; - 0783; // invalid octal - 0; // border case, allow only hex<->decimal -*/ -class ConvertNumericLiteral: public CppQuickFixOperation -{ -public: - ConvertNumericLiteral(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor) - {} - - virtual int match(const QList<AST *> &path) - { - literal = 0; - - if (path.isEmpty()) - return -1; // nothing to do - - literal = path.last()->asNumericLiteral(); - - if (! literal) - return -1; - - Token token = tokenAt(literal->asNumericLiteral()->literal_token); - if (!token.is(T_NUMERIC_LITERAL)) - return -1; - numeric = token.number; - if (numeric->isDouble() || numeric->isFloat()) - return -1; - - // remove trailing L or U and stuff - const char * const spell = numeric->chars(); - numberLength = numeric->size(); - while (numberLength > 0 && (spell[numberLength-1] < '0' || spell[numberLength-1] > 'F')) - --numberLength; - if (numberLength < 1) - return -1; - - // convert to number - bool valid; - value = QString::fromUtf8(spell).left(numberLength).toULong(&valid, 0); - if (!valid) // e.g. octal with digit > 7 - return -1; - - return path.size() - 1; // very high priority - } - - virtual void createChanges() - { - ChangeSet changes; - int start = startOf(literal); - changes.replace(start, start + numberLength, replacement); - refactoringChanges()->changeFile(fileName(), changes); - } - -protected: - NumericLiteralAST *literal; - const NumericLiteral *numeric; - ulong value; - int numberLength; - QString replacement; -}; - -/* - Convert integer literal to hex representation. - Replace - 32 - 040 - With - 0x20 + return _path; +} -*/ -class ConvertNumericToHex: public ConvertNumericLiteral +Snapshot CppQuickFixState::snapshot() const { -public: - ConvertNumericToHex(TextEditor::BaseTextEditor *editor) - : ConvertNumericLiteral(editor) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Convert to Hexadecimal"); - } - - virtual int match(const QList<AST *> &path) - { - int ret = ConvertNumericLiteral::match(path); - if (ret != -1 && !numeric->isHex()) { - replacement.sprintf("0x%lX", value); - return ret; - } - return -1; - } - -}; + return _snapshot; +} -/* - Convert integer literal to octal representation. - Replace - 32 - 0x20 - With - 040 -*/ -class ConvertNumericToOctal: public ConvertNumericLiteral +Document::Ptr CppQuickFixState::document() const { -public: - ConvertNumericToOctal(TextEditor::BaseTextEditor *editor) - : ConvertNumericLiteral(editor) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Convert to Octal"); - } - - virtual int match(const QList<AST *> &path) - { - int ret = ConvertNumericLiteral::match(path); - if (ret != -1 && value != 0) { - const char * const str = numeric->chars(); - if (numberLength > 1 && str[0] == '0' && str[1] != 'x' && str[1] != 'X') - return -1; - replacement.sprintf("0%lo", value); - return ret; - } - return -1; - } - -}; + return _semanticInfo.doc; +} -/* - Convert integer literal to decimal representation. - Replace - 0x20 - 040 - With - 32 -*/ -class ConvertNumericToDecimal: public ConvertNumericLiteral +SemanticInfo CppQuickFixState::semanticInfo() const { -public: - ConvertNumericToDecimal(TextEditor::BaseTextEditor *editor) - : ConvertNumericLiteral(editor) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Convert to Decimal"); - } - - virtual int match(const QList<AST *> &path) - { - int ret = ConvertNumericLiteral::match(path); - if (ret != -1 && (value != 0 || numeric->isHex())) { - const char * const str = numeric->chars(); - if (numberLength > 1 && str[0] != '0') - return -1; - replacement.sprintf("%lu", value); - return ret; - } - return -1; - } - -}; + return _semanticInfo; +} -/* - Adds missing case statements for "switch (enumVariable)" -*/ -class CompleteSwitchCaseStatement: public CppQuickFixOperation +LookupContext CppQuickFixState::context() const { -public: - CompleteSwitchCaseStatement(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Complete Switch Statement"); - } - - virtual int match(const QList<AST *> &path) - { - if (path.isEmpty()) - return -1; // nothing to do - - // look for switch statement - for (int depth = path.size()-1; depth >= 0; --depth) { - AST *ast = path.at(depth); - SwitchStatementAST *switchStatement = ast->asSwitchStatement(); - if (switchStatement) { - if (!isCursorOn(switchStatement->switch_token) || !switchStatement->statement) - return -1; - compoundStatement = switchStatement->statement->asCompoundStatement(); - if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" - return -1; - // look if the condition's type is an enum - if (Enum *e = conditionEnum(switchStatement)) { - // check the possible enum values - values.clear(); - Overview prettyPrint; - for (unsigned i = 0; i < e->memberCount(); ++i) { - if (Declaration *decl = e->memberAt(i)->asDeclaration()) { - values << prettyPrint(LookupContext::fullyQualifiedName(decl)); - } - } - // Get the used values - Block *block = switchStatement->symbol; - CaseStatementCollector caseValues(document(), snapshot(), - document()->scopeAt(block->line(), block->column())); - QStringList usedValues = caseValues(switchStatement); - // save the values that would be added - foreach (const QString &usedValue, usedValues) - values.removeAll(usedValue); - if (values.isEmpty()) - return -1; - return depth; - } - return -1; - } - } - - return -1; - } - - virtual void createChanges() - { - ChangeSet changes; - int start = endOf(compoundStatement->lbrace_token); - changes.insert(start, QLatin1String("\ncase ") - + values.join(QLatin1String(":\nbreak;\ncase ")) - + QLatin1String(":\nbreak;")); - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), range(compoundStatement)); - } - -protected: - Enum *conditionEnum(SwitchStatementAST *statement) - { - Block *block = statement->symbol; - Scope *scope = document()->scopeAt(block->line(), block->column()); - TypeOfExpression typeOfExpression; - typeOfExpression.init(document(), snapshot()); - const QList<LookupItem> results = typeOfExpression(statement->condition, - document(), - scope); - foreach (LookupItem result, results) { - FullySpecifiedType fst = result.type(); - if (Enum *e = result.declaration()->type()->asEnumType()) - return e; - if (NamedType *namedType = fst->asNamedType()) { - QList<LookupItem> candidates = - typeOfExpression.context().lookup(namedType->name(), scope); - foreach (const LookupItem &r, candidates) { - Symbol *candidate = r.declaration(); - if (Enum *e = candidate->asEnum()) { - return e; - } - } - } - } - return 0; - } - class CaseStatementCollector : public ASTVisitor - { - public: - CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot, - Scope *scope) - : ASTVisitor(document->translationUnit()), - document(document), - scope(scope) - { - typeOfExpression.init(document, snapshot); - } - - QStringList operator ()(AST *ast) - { - values.clear(); - foundCaseStatementLevel = false; - accept(ast); - return values; - } - - bool preVisit(AST *ast) { - if (CaseStatementAST *cs = ast->asCaseStatement()) { - foundCaseStatementLevel = true; - ExpressionAST *expression = cs->expression->asSimpleName(); - if (!expression) - expression = cs->expression->asQualifiedName(); - if (expression) { - LookupItem item = typeOfExpression(expression, - document, - scope).first(); - values << prettyPrint(LookupContext::fullyQualifiedName(item.declaration())); - } - return true; - } else if (foundCaseStatementLevel) { - return false; - } - return true; - } - - Overview prettyPrint; - bool foundCaseStatementLevel; - QStringList values; - TypeOfExpression typeOfExpression; - Document::Ptr document; - Scope *scope; - }; - -protected: - CompoundStatementAST *compoundStatement; - QStringList values; -}; - + return _context; +} -class FixForwardDeclarationOp: public CppQuickFixOperation +Scope *CppQuickFixState::scopeAt(unsigned index) const { -public: - FixForwardDeclarationOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), fwdClass(0) - { - } - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "#include header file"); - } - - bool checkName(NameAST *ast) - { - if (ast && isCursorOn(ast)) { - if (const Name *name = ast->name) { - unsigned line, column; - document()->translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); - - fwdClass = 0; - - foreach (const LookupItem &r, context().lookup(name, document()->scopeAt(line, column))) { - if (! r.declaration()) - continue; - else if (ForwardClassDeclaration *fwd = r.declaration()->asForwardClassDeclaration()) - fwdClass = fwd; - else if (r.declaration()->isClass()) - return false; // nothing to do. - } - - if (fwdClass) - return true; - } - } - - return false; - } - - virtual int match(const QList<AST *> &path) - { - fwdClass = 0; - - for (int index = path.size() - 1; index != -1; --index) { - AST *ast = path.at(index); - if (NamedTypeSpecifierAST *namedTy = ast->asNamedTypeSpecifier()) { - if (checkName(namedTy->name)) - return index; - } else if (ElaboratedTypeSpecifierAST *eTy = ast->asElaboratedTypeSpecifier()) { - if (checkName(eTy->name)) - return index; - } - } - - return -1; - } - - virtual void createChanges() - { - Q_ASSERT(fwdClass != 0); - - if (Class *k = snapshot().findMatchingClassDeclaration(fwdClass)) { - const QString headerFile = QString::fromUtf8(k->fileName(), k->fileNameLength()); - - // collect the fwd headers - Snapshot fwdHeaders; - fwdHeaders.insert(snapshot().document(headerFile)); - foreach (Document::Ptr doc, snapshot()) { - QFileInfo headerFileInfo(doc->fileName()); - if (doc->globalSymbolCount() == 0 && doc->includes().size() == 1) - fwdHeaders.insert(doc); - else if (headerFileInfo.suffix().isEmpty()) - fwdHeaders.insert(doc); - } - - - DependencyTable dep; - dep.build(fwdHeaders); - QStringList candidates = dep.dependencyTable().value(headerFile); - - const QString className = QString::fromUtf8(k->identifier()->chars()); - - QString best; - foreach (const QString &c, candidates) { - QFileInfo headerFileInfo(c); - if (headerFileInfo.fileName() == className) { - best = c; - break; - } else if (headerFileInfo.fileName().at(0).isUpper()) { - best = c; - // and continue - } else if (! best.isEmpty()) { - if (c.count(QLatin1Char('/')) < best.count(QLatin1Char('/'))) - best = c; - } - } - - if (best.isEmpty()) - best = headerFile; - - int pos = startOf(1); - - unsigned currentLine = textCursor().blockNumber() + 1; - unsigned bestLine = 0; - foreach (const Document::Include &incl, document()->includes()) { - if (incl.line() < currentLine) - bestLine = incl.line(); - } - - if (bestLine) - pos = editor()->document()->findBlockByNumber(bestLine).position(); - - Utils::ChangeSet changes; - changes.insert(pos, QString("#include <%1>\n").arg(QFileInfo(best).fileName())); - refactoringChanges()->changeFile(fileName(), changes); - } - } - -private: - Symbol *fwdClass; -}; + unsigned line, column; + document()->translationUnit()->getTokenStartPosition(index, &line, &column); + return document()->scopeAt(line, column); +} -class AddLocalDeclarationOp: public CppQuickFixOperation +bool CppQuickFixState::isCursorOn(unsigned tokenIndex) const { -public: - AddLocalDeclarationOp(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor), binaryAST(0) - { - } - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Add local declaration"); - } - - virtual int match(const QList<AST *> &path) - { - for (int index = path.size() - 1; index != -1; --index) { - if (BinaryExpressionAST *binary = path.at(index)->asBinaryExpression()) { - if (binary->left_expression && binary->right_expression && tokenAt(binary->binary_op_token).is(T_EQUAL)) { - if (isCursorOn(binary->left_expression) && binary->left_expression->asSimpleName() != 0) { - SimpleNameAST *nameAST = binary->left_expression->asSimpleName(); - const QList<LookupItem> results = context().lookup(nameAST->name, scopeAt(nameAST->firstToken())); - Declaration *decl = 0; - foreach (const LookupItem &r, results) { - if (! r.declaration()) - continue; - else if (Declaration *d = r.declaration()->asDeclaration()) { - if (! d->type()->isFunctionType()) { - decl = d; - break; - } - } - } - - if (! decl) { - binaryAST = binary; - typeOfExpression.init(document(), snapshot(), context().bindings()); - return index; - } - } - } - } - } - - return -1; - } - - virtual void createChanges() - { - const QList<LookupItem> result = typeOfExpression(textOf(binaryAST->right_expression), - scopeAt(binaryAST->firstToken()), - TypeOfExpression::Preprocess); - - if (! result.isEmpty()) { - - SubstitutionEnvironment env; - env.setContext(context()); - env.switchScope(result.first().scope()); - UseQualifiedNames q; - env.enter(&q); - - Control *control = context().control().data(); - FullySpecifiedType tn = rewriteType(result.first().type(), &env, control); - - Overview oo; - QString ty = oo(tn); - if (! ty.isEmpty()) { - const QChar ch = ty.at(ty.size() - 1); + QTextCursor tc = textCursor(); + int cursorBegin = tc.selectionStart(); - if (ch.isLetterOrNumber() || ch == QLatin1Char(' ') || ch == QLatin1Char('>')) - ty += QLatin1Char(' '); + int start = startOf(tokenIndex); + int end = endOf(tokenIndex); - Utils::ChangeSet changes; - changes.insert(startOf(binaryAST), ty); - refactoringChanges()->changeFile(fileName(), changes); - } - } - } + if (cursorBegin >= start && cursorBegin <= end) + return true; -private: - TypeOfExpression typeOfExpression; - BinaryExpressionAST *binaryAST; -}; + return false; +} -/* - * Turns "an_example_symbol" into "anExampleSymbol" and - * "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol". - */ -class ToCamelCaseConverter : public CppQuickFixOperation +bool CppQuickFixState::isCursorOn(const AST *ast) const { -public: - ToCamelCaseConverter(TextEditor::BaseTextEditor *editor) - : CppQuickFixOperation(editor) - {} - - virtual QString description() const - { - return QApplication::translate("CppTools::QuickFix", "Convert to Camel Case ..."); - } - - virtual int match(const QList<AST *> &path) - { - if (path.isEmpty()) - return -1; - - AST * const ast = path.last(); - const Name *name = 0; - if (const NameAST * const nameAst = ast->asName()) { - if (nameAst->name && nameAst->name->asNameId()) - name = nameAst->name; - } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) { - name = namespaceAst->symbol->name(); - } - - if (!name) - return -1; - - m_name = QString::fromUtf8(name->identifier()->chars()); - if (m_name.length() < 3) - return -1; - for (int i = 1; i < m_name.length() - 1; ++i) { - if (isConvertibleUnderscore(i)) - return path.size() - 1; - } - - return -1; - } - - virtual void createChanges() - { - for (int i = 1; i < m_name.length(); ++i) { - QCharRef c = m_name[i]; - if (c.isUpper()) { - c = c.toLower(); - } else if (i < m_name.length() - 1 - && isConvertibleUnderscore(i)) { - m_name.remove(i, 1); - m_name[i] = m_name.at(i).toUpper(); - } - } - static_cast<CppEditor::Internal::CPPEditor*>(editor())->renameUsagesNow(m_name); - } - -private: - bool isConvertibleUnderscore(int pos) const - { - return m_name.at(pos) == QLatin1Char('_') && m_name.at(pos+1).isLetter() - && !(pos == 1 && m_name.at(0) == QLatin1Char('m')); - } - - QString m_name; -}; - -} // end of anonymous namespace - + QTextCursor tc = textCursor(); + int cursorBegin = tc.selectionStart(); -CppQuickFixOperation::CppQuickFixOperation(TextEditor::BaseTextEditor *editor) - : TextEditor::QuickFixOperation(editor) - , _refactoringChanges(0) - , _topLevelNode(0) -{ } + int start = startOf(ast); + int end = endOf(ast); -CppQuickFixOperation::~CppQuickFixOperation() -{ - if (_refactoringChanges) - delete _refactoringChanges; -} + if (cursorBegin >= start && cursorBegin <= end) + return true; -int CppQuickFixOperation::match(TextEditor::QuickFixState *state) -{ - CppQuickFixState *s = static_cast<CppQuickFixState *>(state); - _document = s->info.doc; - if (_refactoringChanges) - delete _refactoringChanges; - _refactoringChanges = new CppRefactoringChanges(_document, s->snapshot); - return match(s->path); + return false; } -Utils::ChangeSet::Range CppQuickFixOperation::range(unsigned tokenIndex) const +ChangeSet::Range CppQuickFixState::range(unsigned tokenIndex) const { - const CPlusPlus::Token &token = tokenAt(tokenIndex); + const Token &token = tokenAt(tokenIndex); unsigned line, column; - _document->translationUnit()->getPosition(token.begin(), &line, &column); + document()->translationUnit()->getPosition(token.begin(), &line, &column); const int start = editor()->document()->findBlockByNumber(line - 1).position() + column - 1; - return Utils::ChangeSet::Range(start, start + token.length()); -} - -Utils::ChangeSet::Range CppQuickFixOperation::range(CPlusPlus::AST *ast) const -{ - return Utils::ChangeSet::Range(startOf(ast), endOf(ast)); + return ChangeSet::Range(start, start + token.length()); } -QString CppQuickFixOperation::fileName() const -{ return document()->fileName(); } - -void CppQuickFixOperation::apply() +ChangeSet::Range CppQuickFixState::range(AST *ast) const { - refactoringChanges()->apply(); + return ChangeSet::Range(startOf(ast), endOf(ast)); } -CppEditor::CppRefactoringChanges *CppQuickFixOperation::refactoringChanges() const -{ return _refactoringChanges; } - -Document::Ptr CppQuickFixOperation::document() const -{ return _document; } - -const Snapshot &CppQuickFixOperation::snapshot() const -{ return _refactoringChanges->snapshot(); } - -const CPlusPlus::LookupContext &CppQuickFixOperation::context() const -{ return _refactoringChanges->context(); } - -CPlusPlus::Scope *CppQuickFixOperation::scopeAt(unsigned index) const -{ - unsigned line, column; - document()->translationUnit()->getTokenStartPosition(index, &line, &column); - return document()->scopeAt(line, column); -} - -const CPlusPlus::Token &CppQuickFixOperation::tokenAt(unsigned index) const -{ return _document->translationUnit()->tokenAt(index); } - -int CppQuickFixOperation::startOf(unsigned index) const +int CppQuickFixState::startOf(unsigned index) const { unsigned line, column; - _document->translationUnit()->getPosition(tokenAt(index).begin(), &line, &column); + document()->translationUnit()->getPosition(tokenAt(index).begin(), &line, &column); return editor()->document()->findBlockByNumber(line - 1).position() + column - 1; } -int CppQuickFixOperation::startOf(const CPlusPlus::AST *ast) const +int CppQuickFixState::startOf(const AST *ast) const { return startOf(ast->firstToken()); } -int CppQuickFixOperation::endOf(unsigned index) const +int CppQuickFixState::endOf(unsigned index) const { unsigned line, column; - _document->translationUnit()->getPosition(tokenAt(index).end(), &line, &column); + document()->translationUnit()->getPosition(tokenAt(index).end(), &line, &column); return editor()->document()->findBlockByNumber(line - 1).position() + column - 1; } -int CppQuickFixOperation::endOf(const CPlusPlus::AST *ast) const +int CppQuickFixState::endOf(const AST *ast) const { if (unsigned end = ast->lastToken()) return endOf(end - 1); @@ -1742,46 +162,71 @@ int CppQuickFixOperation::endOf(const CPlusPlus::AST *ast) const return 0; } -void CppQuickFixOperation::startAndEndOf(unsigned index, int *start, int *end) const +void CppQuickFixState::startAndEndOf(unsigned index, int *start, int *end) const { unsigned line, column; - CPlusPlus::Token token(tokenAt(index)); - _document->translationUnit()->getPosition(token.begin(), &line, &column); + Token token(tokenAt(index)); + document()->translationUnit()->getPosition(token.begin(), &line, &column); *start = editor()->document()->findBlockByNumber(line - 1).position() + column - 1; *end = *start + token.length(); } -bool CppQuickFixOperation::isCursorOn(unsigned tokenIndex) const +QString CppQuickFixState::textOf(const AST *ast) const { - QTextCursor tc = textCursor(); - int cursorBegin = tc.selectionStart(); + return textOf(startOf(ast), endOf(ast)); +} - int start = startOf(tokenIndex); - int end = endOf(tokenIndex); +const Token &CppQuickFixState::tokenAt(unsigned index) const +{ + return document()->translationUnit()->tokenAt(index); +} - if (cursorBegin >= start && cursorBegin <= end) - return true; +CppQuickFixOperation::CppQuickFixOperation(const CppQuickFixState &state, int priority) + : QuickFixOperation(priority) + , _state(state) + , _refactoringChanges(new CppRefactoringChanges(state.document(), state.snapshot())) +{} - return false; +CppQuickFixOperation::~CppQuickFixOperation() +{} + +const CppQuickFixState &CppQuickFixOperation::state() const +{ + return _state; } -bool CppQuickFixOperation::isCursorOn(const CPlusPlus::AST *ast) const +QString CppQuickFixOperation::fileName() const +{ return state().document()->fileName(); } + +CppRefactoringChanges *CppQuickFixOperation::refactoringChanges() const +{ return _refactoringChanges.data(); } + +CppQuickFixFactory::CppQuickFixFactory() { - QTextCursor tc = textCursor(); - int cursorBegin = tc.selectionStart(); +} - int start = startOf(ast); - int end = endOf(ast); +CppQuickFixFactory::~CppQuickFixFactory() +{ +} - if (cursorBegin >= start && cursorBegin <= end) - return true; +QList<QuickFixOperation::Ptr> CppQuickFixFactory::matchingOperations(QuickFixState *state) +{ + if (CppQuickFixState *cppState = static_cast<CppQuickFixState *>(state)) + return match(*cppState); + else + return QList<TextEditor::QuickFixOperation::Ptr>(); +} - return false; +QList<CppQuickFixOperation::Ptr> CppQuickFixFactory::singleResult(CppQuickFixOperation *operation) +{ + QList<CppQuickFixOperation::Ptr> result; + result.append(CppQuickFixOperation::Ptr(operation)); + return result; } -QString CppQuickFixOperation::textOf(const AST *ast) const +QList<CppQuickFixOperation::Ptr> CppQuickFixFactory::noResult() { - return textOf(startOf(ast), endOf(ast)); + return QList<CppQuickFixOperation::Ptr>(); } CppQuickFixCollector::CppQuickFixCollector() @@ -1797,12 +242,12 @@ bool CppQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editor) return CppTools::CppModelManagerInterface::instance()->isCppEditor(editor); } -TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor::ITextEditable *editable) +TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor::BaseTextEditor *editor) { - if (CPPEditor *editor = qobject_cast<CPPEditor *>(editable->widget())) { - const SemanticInfo info = editor->semanticInfo(); + if (CPPEditor *cppEditor = qobject_cast<CPPEditor *>(editor)) { + const SemanticInfo info = cppEditor->semanticInfo(); - if (info.revision != editor->editorRevision()) { + if (info.revision != cppEditor->editorRevision()) { // outdated qWarning() << "TODO: outdated semantic info, force a reparse."; return 0; @@ -1811,12 +256,13 @@ TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor if (info.doc) { ASTPath astPath(info.doc); - const QList<AST *> path = astPath(editor->textCursor()); + const QList<AST *> path = astPath(cppEditor->textCursor()); if (! path.isEmpty()) { - CppQuickFixState *state = new CppQuickFixState; - state->path = path; - state->info = info; - state->snapshot = CppTools::CppModelManagerInterface::instance()->snapshot(); + CppQuickFixState *state = new CppQuickFixState(editor); + state->_path = path; + state->_semanticInfo = info; + state->_snapshot = CppTools::CppModelManagerInterface::instance()->snapshot(); + state->_context = LookupContext(info.doc, state->snapshot()); return state; } } @@ -1825,63 +271,11 @@ TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor return 0; } -CppQuickFixFactory::CppQuickFixFactory(QObject *parent) - : TextEditor::IQuickFixFactory(parent) -{ -} - -CppQuickFixFactory::~CppQuickFixFactory() +QList<TextEditor::QuickFixFactory *> CppQuickFixCollector::quickFixFactories() const { -} - -QList<TextEditor::QuickFixOperation::Ptr> CppQuickFixFactory::quickFixOperations(TextEditor::BaseTextEditor *editor) -{ - QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations; - - QSharedPointer<RewriteLogicalAndOp> rewriteLogicalAndOp(new RewriteLogicalAndOp(editor)); - QSharedPointer<SplitIfStatementOp> splitIfStatementOp(new SplitIfStatementOp(editor)); - QSharedPointer<MoveDeclarationOutOfIfOp> moveDeclarationOutOfIfOp(new MoveDeclarationOutOfIfOp(editor)); - QSharedPointer<MoveDeclarationOutOfWhileOp> moveDeclarationOutOfWhileOp(new MoveDeclarationOutOfWhileOp(editor)); - QSharedPointer<SplitSimpleDeclarationOp> splitSimpleDeclarationOp(new SplitSimpleDeclarationOp(editor)); - QSharedPointer<AddBracesToIfOp> addBracesToIfOp(new AddBracesToIfOp(editor)); - QSharedPointer<UseInverseOp> useInverseOp(new UseInverseOp(editor)); - QSharedPointer<FlipBinaryOp> flipBinaryOp(new FlipBinaryOp(editor)); - QSharedPointer<WrapStringLiteral> wrapStringLiteral(new WrapStringLiteral(editor)); - QSharedPointer<CStringToNSString> wrapCString(new CStringToNSString(editor)); - QSharedPointer<TranslateStringLiteral> translateCString(new TranslateStringLiteral(editor)); - QSharedPointer<ConvertNumericToHex> convertNumericToHex(new ConvertNumericToHex(editor)); - QSharedPointer<ConvertNumericToOctal> convertNumericToOctal(new ConvertNumericToOctal(editor)); - QSharedPointer<ConvertNumericToDecimal> convertNumericToDecimal(new ConvertNumericToDecimal(editor)); - QSharedPointer<CompleteSwitchCaseStatement> completeSwitchCaseStatement(new CompleteSwitchCaseStatement(editor)); - QSharedPointer<FixForwardDeclarationOp> fixForwardDeclarationOp(new FixForwardDeclarationOp(editor)); - QSharedPointer<AddLocalDeclarationOp> addLocalDeclarationOp(new AddLocalDeclarationOp(editor)); - QSharedPointer<ToCamelCaseConverter> toCamelCase(new ToCamelCaseConverter(editor)); - QSharedPointer<DeclFromDef> declFromDef(new DeclFromDef(editor)); - - quickFixOperations.append(rewriteLogicalAndOp); - quickFixOperations.append(splitIfStatementOp); - quickFixOperations.append(moveDeclarationOutOfIfOp); - quickFixOperations.append(moveDeclarationOutOfWhileOp); - quickFixOperations.append(splitSimpleDeclarationOp); - quickFixOperations.append(addBracesToIfOp); - quickFixOperations.append(useInverseOp); - quickFixOperations.append(flipBinaryOp); - quickFixOperations.append(wrapStringLiteral); - quickFixOperations.append(translateCString); - quickFixOperations.append(convertNumericToHex); - quickFixOperations.append(convertNumericToOctal); - quickFixOperations.append(convertNumericToDecimal); - quickFixOperations.append(completeSwitchCaseStatement); - quickFixOperations.append(fixForwardDeclarationOp); - quickFixOperations.append(addLocalDeclarationOp); - quickFixOperations.append(toCamelCase); - -#if 0 - quickFixOperations.append(declFromDef); -#endif - - if (editor->mimeType() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE) - quickFixOperations.append(wrapCString); - - return quickFixOperations; + QList<TextEditor::QuickFixFactory *> results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (CppQuickFixFactory *f, pm->getObjects<CppEditor::CppQuickFixFactory>()) + results.append(f); + return results; } diff --git a/src/plugins/cppeditor/cppquickfix.h b/src/plugins/cppeditor/cppquickfix.h index 60c990fda0c5164bce9c4f135492b9236b34f199..4b5bfe50e508871397d22f090e228e6f69833ffa 100644 --- a/src/plugins/cppeditor/cppquickfix.h +++ b/src/plugins/cppeditor/cppquickfix.h @@ -30,100 +30,142 @@ #ifndef CPPQUICKFIX_H #define CPPQUICKFIX_H -#include <texteditor/icompletioncollector.h> -#include <texteditor/quickfix.h> +#include "cpprefactoringchanges.h" +#include "cppsemanticinfo.h" -#include <cplusplus/CppDocument.h> #include <ASTfwd.h> - +#include <cplusplus/CppDocument.h> +#include <texteditor/icompletioncollector.h> +#include <texteditor/quickfix.h> #include <utils/changeset.h> #include <QtCore/QSharedPointer> #include <QtGui/QTextCursor> -#include "cpprefactoringchanges.h" - namespace CppTools { class CppModelManagerInterface; } // end of namespace CppTools +namespace ExtensionSystem { +class IPlugin; +} + namespace CppEditor { + namespace Internal { +class CppQuickFixCollector; +} // end of namespace Internal -class CppQuickFixOperation: public TextEditor::QuickFixOperation +class CPPEDITOR_EXPORT CppQuickFixState: public TextEditor::QuickFixState { - Q_DISABLE_COPY(CppQuickFixOperation) + friend class Internal::CppQuickFixCollector; public: - CppQuickFixOperation(TextEditor::BaseTextEditor *editor); - virtual ~CppQuickFixOperation(); - - virtual int match(const QList<CPlusPlus::AST *> &path) = 0; + CppQuickFixState(TextEditor::BaseTextEditor *editor); + typedef Utils::ChangeSet::Range Range; + const QList<CPlusPlus::AST *> &path() const; + CPlusPlus::Snapshot snapshot() const; CPlusPlus::Document::Ptr document() const; - const CPlusPlus::Snapshot &snapshot() const; - const CPlusPlus::LookupContext &context() const; + CppEditor::Internal::SemanticInfo semanticInfo() const; + CPlusPlus::LookupContext context() const; - virtual int match(TextEditor::QuickFixState *state); + using TextEditor::QuickFixState::range; + using TextEditor::QuickFixState::textOf; + using TextEditor::QuickFixState::charAt; -protected: - using TextEditor::QuickFixOperation::range; - using TextEditor::QuickFixOperation::textOf; - using TextEditor::QuickFixOperation::charAt; - - Utils::ChangeSet::Range range(unsigned tokenIndex) const; - Utils::ChangeSet::Range range(CPlusPlus::AST *ast) const; + CPlusPlus::Scope *scopeAt(unsigned index) const; - QString fileName() const; + bool isCursorOn(unsigned tokenIndex) const; + bool isCursorOn(const CPlusPlus::AST *ast) const; - virtual void apply(); - virtual CppRefactoringChanges *refactoringChanges() const; + Range range(unsigned tokenIndex) const; + Range range(CPlusPlus::AST *ast) const; const CPlusPlus::Token &tokenAt(unsigned index) const; - CPlusPlus::Scope *scopeAt(unsigned index) const; - int startOf(unsigned index) const; int startOf(const CPlusPlus::AST *ast) const; int endOf(unsigned index) const; int endOf(const CPlusPlus::AST *ast) const; void startAndEndOf(unsigned index, int *start, int *end) const; - bool isCursorOn(unsigned tokenIndex) const; - bool isCursorOn(const CPlusPlus::AST *ast) const; - QString textOf(const CPlusPlus::AST *ast) const; private: - CppRefactoringChanges *_refactoringChanges; - CPlusPlus::Document::Ptr _document; - CPlusPlus::AST *_topLevelNode; + QList<CPlusPlus::AST *> _path; + CPlusPlus::Snapshot _snapshot; + CppEditor::Internal::SemanticInfo _semanticInfo; + CPlusPlus::LookupContext _context; }; -class CppQuickFixCollector: public TextEditor::QuickFixCollector +class CPPEDITOR_EXPORT CppQuickFixOperation: public TextEditor::QuickFixOperation { - Q_OBJECT + Q_DISABLE_COPY(CppQuickFixOperation) public: - CppQuickFixCollector(); - virtual ~CppQuickFixCollector(); + CppQuickFixOperation(const CppQuickFixState &state, int priority = -1); + virtual ~CppQuickFixOperation(); - virtual bool supportsEditor(TextEditor::ITextEditable *editor); - virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::ITextEditable *editable); +protected: + QString fileName() const; + + CppRefactoringChanges *refactoringChanges() const; + + const CppQuickFixState &state() const; + +protected: // Utility functions forwarding to CppQuickFixState + typedef Utils::ChangeSet::Range Range; + + bool isCursorOn(unsigned tokenIndex) const { return state().isCursorOn(tokenIndex); } + bool isCursorOn(const CPlusPlus::AST *ast) const { return state().isCursorOn(ast); } + + Range range(int start, int end) const { return CppQuickFixState::range(start, end); } + Range range(unsigned tokenIndex) const { return state().range(tokenIndex); } + Range range(CPlusPlus::AST *ast) const { return state().range(ast); } + + int startOf(unsigned index) const { return state().startOf(index); } + int startOf(const CPlusPlus::AST *ast) const { return state().startOf(ast); } + int endOf(unsigned index) const { return state().endOf(index); } + int endOf(const CPlusPlus::AST *ast) const { return state().endOf(ast); } + +private: + CppQuickFixState _state; + QScopedPointer<CppRefactoringChanges> _refactoringChanges; }; -class CppQuickFixFactory: public TextEditor::IQuickFixFactory +class CPPEDITOR_EXPORT CppQuickFixFactory: public TextEditor::QuickFixFactory { Q_OBJECT public: - CppQuickFixFactory(QObject *parent = 0); + CppQuickFixFactory(); virtual ~CppQuickFixFactory(); - /* - * Returns true if this IQuickFixFactory can be used with the given editor. - */ - virtual QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations(TextEditor::BaseTextEditor *editor); + virtual QList<TextEditor::QuickFixOperation::Ptr> matchingOperations(TextEditor::QuickFixState *state); + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) = 0; + +protected: + static QList<CppQuickFixOperation::Ptr> singleResult(CppQuickFixOperation *operation); + static QList<CppQuickFixOperation::Ptr> noResult(); +}; + +namespace Internal { + +class CppQuickFixCollector: public TextEditor::QuickFixCollector +{ + Q_OBJECT + +public: + CppQuickFixCollector(); + virtual ~CppQuickFixCollector(); + + virtual bool supportsEditor(TextEditor::ITextEditable *editor); + virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::BaseTextEditor *editor); + + virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; + + static void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); }; } // end of namespace Internal diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d5eec24b630e8d8bfd54677125e3a358797735b4 --- /dev/null +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -0,0 +1,1733 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "cppeditor.h" +#include "cppquickfix.h" +#include "cppdeclfromdef.h" + +#include <ASTVisitor.h> +#include <AST.h> +#include <ASTMatcher.h> +#include <ASTPatternBuilder.h> +#include <CoreTypes.h> +#include <Literals.h> +#include <Name.h> +#include <Names.h> +#include <Symbol.h> +#include <Symbols.h> +#include <Token.h> +#include <TranslationUnit.h> +#include <Type.h> + +#include <cplusplus/DependencyTable.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/CppRewriter.h> +#include <cpptools/cpptoolsconstants.h> +#include <extensionsystem/iplugin.h> + +#include <QtGui/QApplication> +#include <QtGui/QTextBlock> +#include <QtGui/QTextCursor> + +using namespace CppEditor; +using namespace CppEditor::Internal; +using namespace TextEditor; +using namespace CPlusPlus; +using namespace Utils; + +namespace { + +/* + Rewrite + a op b -> !(a invop b) + (a op b) -> !(a invop b) + !(a op b) -> (a invob b) +*/ +class UseInverseOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + QList<CppQuickFixOperation::Ptr> result; + + const QList<AST *> &path = state.path(); + int index = path.size() - 1; + BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); + if (! binary) + return result; + if (! state.isCursorOn(binary->binary_op_token)) + return result; + + Kind invertToken; + switch (state.tokenAt(binary->binary_op_token).kind()) { + case T_LESS_EQUAL: + invertToken = T_GREATER; + break; + case T_LESS: + invertToken = T_GREATER_EQUAL; + break; + case T_GREATER: + invertToken = T_LESS_EQUAL; + break; + case T_GREATER_EQUAL: + invertToken = T_LESS; + break; + case T_EQUAL_EQUAL: + invertToken = T_EXCLAIM_EQUAL; + break; + case T_EXCLAIM_EQUAL: + invertToken = T_EQUAL_EQUAL; + break; + default: + return result; + } + + result.append(CppQuickFixOperation::Ptr(new Operation(state, index, binary, invertToken))); + return result; + } + +private: + class Operation: public CppQuickFixOperation + { + BinaryExpressionAST *binary; + NestedExpressionAST *nested; + UnaryExpressionAST *negation; + + QString replacement; + + public: + Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binary, Kind invertToken) + : CppQuickFixOperation(state, priority) + , binary(binary) + { + Token tok; + tok.f.kind = invertToken; + replacement = QLatin1String(tok.spell()); + + // check for enclosing nested expression + if (priority - 1 >= 0) + nested = state.path()[priority - 1]->asNestedExpression(); + + // check for ! before parentheses + if (nested && priority - 2 >= 0) { + negation = state.path()[priority - 2]->asUnaryExpression(); + if (negation && ! state.tokenAt(negation->unary_op_token).is(T_EXCLAIM)) + negation = 0; + } + } + + virtual QString description() const + { + return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); + } + + virtual void createChanges() + { + ChangeSet changes; + if (negation) { + // can't remove parentheses since that might break precedence + changes.remove(range(negation->unary_op_token)); + } else if (nested) { + changes.insert(state().startOf(nested), "!"); + } else { + changes.insert(state().startOf(binary), "!("); + changes.insert(state().endOf(binary), ")"); + } + changes.replace(range(binary->binary_op_token), replacement); + refactoringChanges()->changeFile(fileName(), changes); + } + }; +}; + +/* + Rewrite + a op b + + As + b flipop a +*/ +class FlipBinaryOp: public CppQuickFixFactory +{ +public: + virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + QList<QuickFixOperation::Ptr> result; + const QList<AST *> &path = state.path(); + + int index = path.size() - 1; + BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); + if (! binary) + return result; + if (! state.isCursorOn(binary->binary_op_token)) + return result; + + Kind flipToken; + switch (state.tokenAt(binary->binary_op_token).kind()) { + case T_LESS_EQUAL: + flipToken = T_GREATER_EQUAL; + break; + case T_LESS: + flipToken = T_GREATER; + break; + case T_GREATER: + flipToken = T_LESS; + break; + case T_GREATER_EQUAL: + flipToken = T_LESS_EQUAL; + break; + case T_EQUAL_EQUAL: + case T_EXCLAIM_EQUAL: + case T_AMPER_AMPER: + case T_PIPE_PIPE: + flipToken = T_EOF_SYMBOL; + break; + default: + return result; + } + + QString replacement; + if (flipToken != T_EOF_SYMBOL) { + Token tok; + tok.f.kind = flipToken; + replacement = QLatin1String(tok.spell()); + } + + result.append(QuickFixOperation::Ptr(new Operation(state, index, binary, replacement))); + return result; + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binary, QString replacement) + : CppQuickFixOperation(state) + , binary(binary) + , replacement(replacement) + { + setPriority(priority); + } + + virtual QString description() const + { + if (replacement.isEmpty()) + return QApplication::translate("CppTools::QuickFix", "Swap Operands"); + else + return QApplication::translate("CppTools::QuickFix", "Rewrite Using %1").arg(replacement); + } + + virtual void createChanges() + { + ChangeSet changes; + + changes.flip(range(binary->left_expression), range(binary->right_expression)); + if (! replacement.isEmpty()) + changes.replace(range(binary->binary_op_token), replacement); + + refactoringChanges()->changeFile(fileName(), changes); + } + + private: + BinaryExpressionAST *binary; + QString replacement; + }; +}; + +/* + Rewrite + !a && !b + + As + !(a || b) +*/ +class RewriteLogicalAndOp: public CppQuickFixFactory +{ +public: + virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + QList<QuickFixOperation::Ptr> result; + BinaryExpressionAST *expression = 0; + const QList<AST *> &path = state.path(); + + int index = path.size() - 1; + for (; index != -1; --index) { + expression = path.at(index)->asBinaryExpression(); + if (expression) + break; + } + + if (! expression) + return result; + + if (! state.isCursorOn(expression->binary_op_token)) + return result; + + QSharedPointer<Operation> op(new Operation(state)); + + if (expression->match(op->pattern, &matcher) && + state.tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) && + state.tokenAt(op->left->unary_op_token).is(T_EXCLAIM) && + state.tokenAt(op->right->unary_op_token).is(T_EXCLAIM)) { + op->setDescription(QApplication::translate("CppTools::QuickFix", "Rewrite Condition Using ||")); + op->setPriority(index); + result.append(op); + } + + return result; + } + +private: + class Operation: public CppQuickFixOperation + { + public: + QSharedPointer<ASTPatternBuilder> mk; + UnaryExpressionAST *left; + UnaryExpressionAST *right; + BinaryExpressionAST *pattern; + + Operation(const CppQuickFixState &state) + : CppQuickFixOperation(state) + , mk(new ASTPatternBuilder) + { + left = mk->UnaryExpression(); + right = mk->UnaryExpression(); + pattern = mk->BinaryExpression(left, right); + } + + virtual void createChanges() + { + ChangeSet changes; + changes.replace(range(pattern->binary_op_token), QLatin1String("||")); + changes.remove(range(left->unary_op_token)); + changes.remove(range(right->unary_op_token)); + const int start = state().startOf(pattern); + const int end = state().endOf(pattern); + changes.insert(start, QLatin1String("!(")); + changes.insert(end, QLatin1String(")")); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(pattern)); + } + }; + +private: + ASTMatcher matcher; +}; + +class SplitSimpleDeclarationOp: public CppQuickFixFactory +{ + static bool checkDeclaration(SimpleDeclarationAST *declaration) + { + if (! declaration->semicolon_token) + return false; + + if (! declaration->decl_specifier_list) + return false; + + for (SpecifierListAST *it = declaration->decl_specifier_list; it; it = it->next) { + SpecifierAST *specifier = it->value; + + if (specifier->asEnumSpecifier() != 0) + return false; + + else if (specifier->asClassSpecifier() != 0) + return false; + } + + if (! declaration->declarator_list) + return false; + + else if (! declaration->declarator_list->next) + return false; + + return true; + } + +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + QList<CppQuickFixOperation::Ptr> result; + CoreDeclaratorAST *core_declarator = 0; + const QList<AST *> &path = state.path(); + + for (int index = path.size() - 1; index != -1; --index) { + AST *node = path.at(index); + + if (CoreDeclaratorAST *coreDecl = node->asCoreDeclarator()) + core_declarator = coreDecl; + + else if (SimpleDeclarationAST *simpleDecl = node->asSimpleDeclaration()) { + if (checkDeclaration(simpleDecl)) { + SimpleDeclarationAST *declaration = simpleDecl; + + const int cursorPosition = state.selectionStart(); + + const int startOfDeclSpecifier = state.startOf(declaration->decl_specifier_list->firstToken()); + const int endOfDeclSpecifier = state.endOf(declaration->decl_specifier_list->lastToken() - 1); + + if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) { + // the AST node under cursor is a specifier. + return singleResult(new Operation(state, index, declaration)); + } + + if (core_declarator && state.isCursorOn(core_declarator)) { + // got a core-declarator under the text cursor. + return singleResult(new Operation(state, index, declaration)); + } + } + + break; + } + } + + return result; + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, SimpleDeclarationAST *decl) + : CppQuickFixOperation(state, priority) + , declaration(decl) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Split Declaration")); + } + + virtual void createChanges() + { + ChangeSet changes; + + SpecifierListAST *specifiers = declaration->decl_specifier_list; + int declSpecifiersStart = state().startOf(specifiers->firstToken()); + int declSpecifiersEnd = state().endOf(specifiers->lastToken() - 1); + int insertPos = state().endOf(declaration->semicolon_token); + + DeclaratorAST *prevDeclarator = declaration->declarator_list->value; + + for (DeclaratorListAST *it = declaration->declarator_list->next; it; it = it->next) { + DeclaratorAST *declarator = it->value; + + changes.insert(insertPos, QLatin1String("\n")); + changes.copy(declSpecifiersStart, declSpecifiersEnd, insertPos); + changes.insert(insertPos, QLatin1String(" ")); + changes.move(range(declarator), insertPos); + changes.insert(insertPos, QLatin1String(";")); + + const int prevDeclEnd = state().endOf(prevDeclarator); + changes.remove(prevDeclEnd, state().startOf(declarator)); + + prevDeclarator = declarator; + } + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(declaration)); + } + + private: + SimpleDeclarationAST *declaration; + }; +}; + +/* + Add curly braces to a if statement that doesn't already contain a + compound statement. +*/ +class AddBracesToIfOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + + // show when we're on the 'if' of an if statement + int index = path.size() - 1; + IfStatementAST *ifStatement = path.at(index)->asIfStatement(); + if (ifStatement && state.isCursorOn(ifStatement->if_token) && ifStatement->statement + && ! ifStatement->statement->asCompoundStatement()) { + return singleResult(new Operation(state, index, ifStatement->statement)); + } + + // or if we're on the statement contained in the if + // ### This may not be such a good idea, consider nested ifs... + for (; index != -1; --index) { + IfStatementAST *ifStatement = path.at(index)->asIfStatement(); + if (ifStatement && ifStatement->statement + && state.isCursorOn(ifStatement->statement) + && ! ifStatement->statement->asCompoundStatement()) { + return singleResult(new Operation(state, index, ifStatement->statement)); + } + } + + // ### This could very well be extended to the else branch + // and other nodes entirely. + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, StatementAST *statement) + : CppQuickFixOperation(state, priority) + , _statement(statement) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Add Curly Braces")); + } + + virtual void createChanges() + { + ChangeSet changes; + + const int start = endOf(_statement->firstToken() - 1); + changes.insert(start, QLatin1String(" {")); + + const int end = endOf(_statement->lastToken() - 1); + changes.insert(end, "\n}"); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(start, end)); + } + + private: + StatementAST *_statement; + }; +}; + +/* + Replace + if (Type name = foo()) {...} + + With + Type name = foo; + if (name) {...} +*/ +class MoveDeclarationOutOfIfOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + QSharedPointer<Operation> op(new Operation(state)); + + int index = path.size() - 1; + for (; index != -1; --index) { + if (IfStatementAST *statement = path.at(index)->asIfStatement()) { + if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { + DeclaratorAST *declarator = op->condition->declarator; + op->core = declarator->core_declarator; + if (! op->core) + return noResult(); + + if (state.isCursorOn(op->core)) { + QList<CppQuickFixOperation::Ptr> result; + op->setPriority(index); + result.append(op); + return result; + } + } + } + } + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state) + : CppQuickFixOperation(state) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Move Declaration out of Condition")); + + condition = mk.Condition(); + pattern = mk.IfStatement(condition); + } + + virtual void createChanges() + { + ChangeSet changes; + + changes.copy(range(core), startOf(condition)); + + int insertPos = startOf(pattern); + changes.move(range(condition), insertPos); + changes.insert(insertPos, QLatin1String(";\n")); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(pattern)); + } + + ASTMatcher matcher; + ASTPatternBuilder mk; + CPPEditor *editor; + ConditionAST *condition; + IfStatementAST *pattern; + CoreDeclaratorAST *core; + }; +}; + +/* + Replace + while (Type name = foo()) {...} + + With + Type name; + while ((name = foo()) != 0) {...} +*/ +class MoveDeclarationOutOfWhileOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + QSharedPointer<Operation> op(new Operation(state)); + + int index = path.size() - 1; + for (; index != -1; --index) { + if (WhileStatementAST *statement = path.at(index)->asWhileStatement()) { + if (statement->match(op->pattern, &op->matcher) && op->condition->declarator) { + DeclaratorAST *declarator = op->condition->declarator; + op->core = declarator->core_declarator; + + if (! op->core) + return noResult(); + + else if (! declarator->equals_token) + return noResult(); + + else if (! declarator->initializer) + return noResult(); + + if (state.isCursorOn(op->core)) { + QList<CppQuickFixOperation::Ptr> result; + op->setPriority(index); + result.append(op); + return result; + } + } + } + } + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state) + : CppQuickFixOperation(state) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Move Declaration out of Condition")); + + condition = mk.Condition(); + pattern = mk.WhileStatement(condition); + } + + virtual void createChanges() + { + ChangeSet changes; + + changes.insert(startOf(condition), QLatin1String("(")); + changes.insert(endOf(condition), QLatin1String(") != 0")); + + int insertPos = startOf(pattern); + const int conditionStart = startOf(condition); + changes.move(conditionStart, startOf(core), insertPos); + changes.copy(range(core), insertPos); + changes.insert(insertPos, QLatin1String(";\n")); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(pattern)); + } + + ASTMatcher matcher; + ASTPatternBuilder mk; + CPPEditor *editor; + ConditionAST *condition; + WhileStatementAST *pattern; + CoreDeclaratorAST *core; + }; +}; + +/* + Replace + if (something && something_else) { + } + + with + if (something) { + if (something_else) { + } + } + + and + if (something || something_else) + x; + + with + if (something) + x; + else if (something_else) + x; +*/ +class SplitIfStatementOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + IfStatementAST *pattern = 0; + const QList<AST *> &path = state.path(); + + int index = path.size() - 1; + for (; index != -1; --index) { + AST *node = path.at(index); + if (IfStatementAST *stmt = node->asIfStatement()) { + pattern = stmt; + break; + } + } + + if (! pattern || ! pattern->statement) + return noResult(); + + unsigned splitKind = 0; + for (++index; index < path.size(); ++index) { + AST *node = path.at(index); + BinaryExpressionAST *condition = node->asBinaryExpression(); + if (! condition) + return noResult(); + + Token binaryToken = state.tokenAt(condition->binary_op_token); + + // only accept a chain of ||s or &&s - no mixing + if (! splitKind) { + splitKind = binaryToken.kind(); + if (splitKind != T_AMPER_AMPER && splitKind != T_PIPE_PIPE) + return noResult(); + // we can't reliably split &&s in ifs with an else branch + if (splitKind == T_AMPER_AMPER && pattern->else_statement) + return noResult(); + } else if (splitKind != binaryToken.kind()) { + return noResult(); + } + + if (state.isCursorOn(condition->binary_op_token)) + return singleResult(new Operation(state, index, pattern, condition)); + } + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, + IfStatementAST *pattern, BinaryExpressionAST *condition) + : CppQuickFixOperation(state, priority) + , pattern(pattern) + , condition(condition) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Split if Statement")); + } + + virtual void createChanges() + { + const Token binaryToken = state().tokenAt(condition->binary_op_token); + + if (binaryToken.is(T_AMPER_AMPER)) + splitAndCondition(); + else + splitOrCondition(); + } + + void splitAndCondition() + { + ChangeSet changes; + + int startPos = startOf(pattern); + changes.insert(startPos, QLatin1String("if (")); + changes.move(range(condition->left_expression), startPos); + changes.insert(startPos, QLatin1String(") {\n")); + + const int lExprEnd = endOf(condition->left_expression); + changes.remove(lExprEnd, startOf(condition->right_expression)); + changes.insert(endOf(pattern), QLatin1String("\n}")); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(pattern)); + } + + void splitOrCondition() + { + ChangeSet changes; + + StatementAST *ifTrueStatement = pattern->statement; + CompoundStatementAST *compoundStatement = ifTrueStatement->asCompoundStatement(); + + int insertPos = endOf(ifTrueStatement); + if (compoundStatement) + changes.insert(insertPos, QLatin1String(" ")); + else + changes.insert(insertPos, QLatin1String("\n")); + changes.insert(insertPos, QLatin1String("else if (")); + + const int rExprStart = startOf(condition->right_expression); + changes.move(rExprStart, startOf(pattern->rparen_token), insertPos); + changes.insert(insertPos, QLatin1String(")")); + + const int rParenEnd = endOf(pattern->rparen_token); + changes.copy(rParenEnd, endOf(pattern->statement), insertPos); + + const int lExprEnd = endOf(condition->left_expression); + changes.remove(lExprEnd, startOf(condition->right_expression)); + + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(pattern)); + } + + private: + IfStatementAST *pattern; + BinaryExpressionAST *condition; + }; +}; + +/* + Replace + "abcd" + With + QLatin1String("abcd") +*/ +class WrapStringLiteral: public CppQuickFixFactory +{ +public: + enum Type { TypeString, TypeObjCString, TypeChar, TypeNone }; + + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + ExpressionAST *literal = 0; + Type type = TypeNone; + const QList<AST *> &path = state.path(); + + if (path.isEmpty()) + return noResult(); // nothing to do + + literal = path.last()->asStringLiteral(); + + if (! literal) { + literal = path.last()->asNumericLiteral(); + if (!literal || !state.tokenAt(literal->asNumericLiteral()->literal_token).is(T_CHAR_LITERAL)) + return noResult(); + else + type = TypeChar; + } else { + type = TypeString; + } + + if (path.size() > 1) { + if (CallAST *call = path.at(path.size() - 2)->asCall()) { + if (call->base_expression) { + if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { + const QByteArray id(state.tokenAt(functionName->identifier_token).identifier->chars()); + + if (id == "QT_TRANSLATE_NOOP" || id == "tr" || id == "trUtf8" + || (type == TypeString && (id == "QLatin1String" || id == "QLatin1Literal")) + || (type == TypeChar && id == "QLatin1Char")) + return noResult(); // skip it + } + } + } + } + + if (type == TypeString) { + if (state.charAt(state.startOf(literal)) == QLatin1Char('@')) + type = TypeObjCString; + } + return singleResult(new Operation(state, + path.size() - 1, // very high priority + type, + literal)); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, Type type, + ExpressionAST *literal) + : CppQuickFixOperation(state, priority) + , type(type) + , literal(literal) + { + if (type == TypeChar) + setDescription(QApplication::translate("CppTools::QuickFix", + "Enclose in QLatin1Char(...)")); + else + setDescription(QApplication::translate("CppTools::QuickFix", + "Enclose in QLatin1String(...)")); + } + + virtual void createChanges() + { + ChangeSet changes; + + const int startPos = startOf(literal); + QLatin1String replacement = (type == TypeChar ? QLatin1String("QLatin1Char(") + : QLatin1String("QLatin1String(")); + + if (type == TypeObjCString) + changes.replace(startPos, startPos + 1, replacement); + else + changes.insert(startPos, replacement); + + changes.insert(endOf(literal), ")"); + + refactoringChanges()->changeFile(fileName(), changes); + } + + private: + Type type; + ExpressionAST *literal; + }; +}; + +/* + Replace + "abcd" + With + tr("abcd") or + QCoreApplication::translate("CONTEXT", "abcd") or + QT_TRANSLATE_NOOP("GLOBAL", "abcd") +*/ +class TranslateStringLiteral: public CppQuickFixFactory +{ +public: + enum TranslationOption { unknown, useTr, useQCoreApplicationTranslate, useMacro }; + + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + // Initialize + ExpressionAST *literal = 0; + QString trContext; + + if (path.isEmpty()) + return noResult(); + + literal = path.last()->asStringLiteral(); + if (!literal) + return noResult(); // No string, nothing to do + + // Do we already have a translation markup? + if (path.size() >= 2) { + if (CallAST *call = path.at(path.size() - 2)->asCall()) { + if (call->base_expression) { + if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { + const QByteArray id(state.tokenAt(functionName->identifier_token).identifier->chars()); + + if (id == "tr" || id == "trUtf8" + || id == "translate" + || id == "QT_TRANSLATE_NOOP" + || id == "QLatin1String" || id == "QLatin1Literal") + return noResult(); // skip it + } + } + } + } + + QSharedPointer<Control> control = state.context().control(); + const Name *trName = control->nameId(control->findOrInsertIdentifier("tr")); + + // Check whether we are in a method: + for (int i = path.size() - 1; i >= 0; --i) + { + if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) { + Function *function = definition->symbol; + ClassOrNamespace *b = state.context().lookupType(function); + if (b) { + // Do we have a tr method? + foreach(const LookupItem &r, b->find(trName)) { + Symbol *s = r.declaration(); + if (s->type()->isFunctionType()) { + // no context required for tr + return singleResult(new Operation(state, path.size() - 1, literal, useTr, trContext)); + } + } + } + // We need to do a QCA::translate, so we need a context. + // Use fully qualified class name: + Overview oo; + foreach (const Name *n, LookupContext::path(function)) { + if (!trContext.isEmpty()) + trContext.append(QLatin1String("::")); + trContext.append(oo.prettyName(n)); + } + // ... or global if none available! + if (trContext.isEmpty()) + trContext = QLatin1String("GLOBAL"); + return singleResult(new Operation(state, path.size() - 1, literal, useQCoreApplicationTranslate, trContext)); + } + } + + // We need to use Q_TRANSLATE_NOOP + return singleResult(new Operation(state, path.size() - 1, literal, useMacro, QLatin1String("GLOBAL"))); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, ExpressionAST *literal, TranslationOption option, const QString &context) + : CppQuickFixOperation(state, priority) + , m_literal(literal) + , m_option(option) + , m_context(context) + { + setDescription(QApplication::translate("CppTools::QuickFix", "Mark as translateable")); + } + + virtual void createChanges() + { + ChangeSet changes; + + const int startPos = startOf(m_literal); + QString replacement(QLatin1String("tr(")); + if (m_option == useQCoreApplicationTranslate) { + replacement = QLatin1String("QCoreApplication::translate(\"") + + m_context + QLatin1String("\", "); + } else if (m_option == useMacro) { + replacement = QLatin1String("QT_TRANSLATE_NOOP(\"") + + m_context + QLatin1String("\", "); + } + + changes.insert(startPos, replacement); + changes.insert(endOf(m_literal), QLatin1String(")")); + + refactoringChanges()->changeFile(fileName(), changes); + } + + private: + ExpressionAST *m_literal; + TranslationOption m_option; + QString m_context; + }; +}; + +class CStringToNSString: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + if (state.editor()->mimeType() != CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE) + return noResult(); + + StringLiteralAST *stringLiteral = 0; + CallAST *qlatin1Call = 0; + const QList<AST *> &path = state.path(); + + if (path.isEmpty()) + return noResult(); // nothing to do + + stringLiteral = path.last()->asStringLiteral(); + + if (! stringLiteral) + return noResult(); + + else if (state.charAt(state.startOf(stringLiteral)) == QLatin1Char('@')) + return noResult(); // it's already an objc string literal. + + else if (path.size() > 1) { + if (CallAST *call = path.at(path.size() - 2)->asCall()) { + if (call->base_expression) { + if (SimpleNameAST *functionName = call->base_expression->asSimpleName()) { + const QByteArray id(state.tokenAt(functionName->identifier_token).identifier->chars()); + + if (id == "QLatin1String" || id == "QLatin1Literal") + qlatin1Call = call; + } + } + } + } + + return singleResult(new Operation(state, path.size() - 1, stringLiteral, qlatin1Call)); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, StringLiteralAST *stringLiteral, CallAST *qlatin1Call) + : CppQuickFixOperation(state, priority) + , stringLiteral(stringLiteral) + , qlatin1Call(qlatin1Call) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Convert to Objective-C String Literal")); + } + + virtual void createChanges() + { + ChangeSet changes; + + if (qlatin1Call) { + changes.replace(startOf(qlatin1Call), startOf(stringLiteral), QLatin1String("@")); + changes.remove(endOf(stringLiteral), endOf(qlatin1Call)); + } else { + changes.insert(startOf(stringLiteral), "@"); + } + + refactoringChanges()->changeFile(fileName(), changes); + } + + private: + StringLiteralAST *stringLiteral; + CallAST *qlatin1Call; + }; +}; + +/* + Base class for converting numeric literals between decimal, octal and hex. + Does the base check for the specific ones and parses the number. + Test cases: + 0xFA0Bu; + 0X856A; + 298.3; + 199; + 074; + 199L; + 074L; + -199; + -017; + 0783; // invalid octal + 0; // border case, allow only hex<->decimal +*/ +class ConvertNumericLiteral: public CppQuickFixFactory +{ +public: + virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + QList<QuickFixOperation::Ptr> result; + + const QList<AST *> &path = state.path(); + + if (path.isEmpty()) + return result; // nothing to do + + NumericLiteralAST *literal = path.last()->asNumericLiteral(); + + if (! literal) + return result; + + Token token = state.tokenAt(literal->asNumericLiteral()->literal_token); + if (!token.is(T_NUMERIC_LITERAL)) + return result; + const NumericLiteral *numeric = token.number; + if (numeric->isDouble() || numeric->isFloat()) + return result; + + // remove trailing L or U and stuff + const char * const spell = numeric->chars(); + int numberLength = numeric->size(); + while (numberLength > 0 && (spell[numberLength-1] < '0' || spell[numberLength-1] > 'F')) + --numberLength; + if (numberLength < 1) + return result; + + // convert to number + bool valid; + ulong value = QString::fromUtf8(spell).left(numberLength).toULong(&valid, 0); + if (!valid) // e.g. octal with digit > 7 + return result; + + const int priority = path.size() - 1; // very high priority + const int start = state.startOf(literal); + const char * const str = numeric->chars(); + + if (!numeric->isHex()) { + /* + Convert integer literal to hex representation. + Replace + 32 + 040 + With + 0x20 + + */ + QString replacement; + replacement.sprintf("0x%lX", value); + QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Hexadecimal")); + op->setPriority(priority); + result.append(op); + } + + if (value != 0) { + if (!(numberLength > 1 && str[0] == '0' && str[1] != 'x' && str[1] != 'X')) { + /* + Convert integer literal to octal representation. + Replace + 32 + 0x20 + With + 040 + */ + QString replacement; + replacement.sprintf("0%lo", value); + QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Octal")); + op->setPriority(priority); + result.append(op); + } + } + + if (value != 0 || numeric->isHex()) { + if (!(numberLength > 1 && str[0] != '0')) { + /* + Convert integer literal to decimal representation. + Replace + 0x20 + 040 + With + 32 + */ + QString replacement; + replacement.sprintf("%lu", value); + QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Decimal")); + op->setPriority(priority); + result.append(op); + } + } + + return result; + } + +private: + class ConvertNumeric: public CppQuickFixOperation + { + public: + ConvertNumeric(const CppQuickFixState &state, int start, int end, const QString &replacement) + : CppQuickFixOperation(state) + , start(start) + , end(end) + , replacement(replacement) + {} + + virtual void createChanges() + { + ChangeSet changes; + changes.replace(start, end, replacement); + refactoringChanges()->changeFile(fileName(), changes); + } + + protected: + int start, end; + QString replacement; + }; +}; + +/* + Adds missing case statements for "switch (enumVariable)" +*/ +class CompleteSwitchCaseStatement: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + + if (path.isEmpty()) + return noResult(); // nothing to do + + // look for switch statement + for (int depth = path.size() - 1; depth >= 0; --depth) { + AST *ast = path.at(depth); + SwitchStatementAST *switchStatement = ast->asSwitchStatement(); + if (switchStatement) { + if (!state.isCursorOn(switchStatement->switch_token) || !switchStatement->statement) + return noResult(); + CompoundStatementAST *compoundStatement = switchStatement->statement->asCompoundStatement(); + if (!compoundStatement) // we ignore pathologic case "switch (t) case A: ;" + return noResult(); + // look if the condition's type is an enum + if (Enum *e = conditionEnum(state, switchStatement)) { + // check the possible enum values + QStringList values; + Overview prettyPrint; + for (unsigned i = 0; i < e->memberCount(); ++i) { + if (Declaration *decl = e->memberAt(i)->asDeclaration()) { + values << prettyPrint(LookupContext::fullyQualifiedName(decl)); + } + } + // Get the used values + Block *block = switchStatement->symbol; + CaseStatementCollector caseValues(state.document(), state.snapshot(), + state.document()->scopeAt(block->line(), block->column())); + QStringList usedValues = caseValues(switchStatement); + // save the values that would be added + foreach (const QString &usedValue, usedValues) + values.removeAll(usedValue); + if (values.isEmpty()) + return noResult(); + return singleResult(new Operation(state, depth, compoundStatement, values)); + } + return noResult(); + } + } + + return noResult(); + } + +protected: + Enum *conditionEnum(const CppQuickFixState &state, SwitchStatementAST *statement) + { + Block *block = statement->symbol; + Scope *scope = state.document()->scopeAt(block->line(), block->column()); + TypeOfExpression typeOfExpression; + typeOfExpression.init(state.document(), state.snapshot()); + const QList<LookupItem> results = typeOfExpression(statement->condition, + state.document(), + scope); + foreach (LookupItem result, results) { + FullySpecifiedType fst = result.type(); + if (Enum *e = result.declaration()->type()->asEnumType()) + return e; + if (NamedType *namedType = fst->asNamedType()) { + QList<LookupItem> candidates = + typeOfExpression.context().lookup(namedType->name(), scope); + foreach (const LookupItem &r, candidates) { + Symbol *candidate = r.declaration(); + if (Enum *e = candidate->asEnum()) { + return e; + } + } + } + } + return 0; + } + + class CaseStatementCollector : public ASTVisitor + { + public: + CaseStatementCollector(Document::Ptr document, const Snapshot &snapshot, + Scope *scope) + : ASTVisitor(document->translationUnit()), + document(document), + scope(scope) + { + typeOfExpression.init(document, snapshot); + } + + QStringList operator ()(AST *ast) + { + values.clear(); + foundCaseStatementLevel = false; + accept(ast); + return values; + } + + bool preVisit(AST *ast) { + if (CaseStatementAST *cs = ast->asCaseStatement()) { + foundCaseStatementLevel = true; + ExpressionAST *expression = cs->expression->asSimpleName(); + if (!expression) + expression = cs->expression->asQualifiedName(); + if (expression) { + LookupItem item = typeOfExpression(expression, + document, + scope).first(); + values << prettyPrint(LookupContext::fullyQualifiedName(item.declaration())); + } + return true; + } else if (foundCaseStatementLevel) { + return false; + } + return true; + } + + Overview prettyPrint; + bool foundCaseStatementLevel; + QStringList values; + TypeOfExpression typeOfExpression; + Document::Ptr document; + Scope *scope; + }; + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, CompoundStatementAST *compoundStatement, const QStringList &values) + : CppQuickFixOperation(state, priority) + , compoundStatement(compoundStatement) + , values(values) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Complete Switch Statement")); + } + + + virtual void createChanges() + { + ChangeSet changes; + int start = endOf(compoundStatement->lbrace_token); + changes.insert(start, QLatin1String("\ncase ") + + values.join(QLatin1String(":\nbreak;\ncase ")) + + QLatin1String(":\nbreak;")); + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(compoundStatement)); + } + + CompoundStatementAST *compoundStatement; + QStringList values; + }; +}; + + +class FixForwardDeclarationOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + + for (int index = path.size() - 1; index != -1; --index) { + AST *ast = path.at(index); + if (NamedTypeSpecifierAST *namedTy = ast->asNamedTypeSpecifier()) { + if (Symbol *fwdClass = checkName(state, namedTy->name)) + return singleResult(new Operation(state, index, fwdClass)); + } else if (ElaboratedTypeSpecifierAST *eTy = ast->asElaboratedTypeSpecifier()) { + if (Symbol *fwdClass = checkName(state, eTy->name)) + return singleResult(new Operation(state, index, fwdClass)); + } + } + + return noResult(); + } + +protected: + static Symbol *checkName(const CppQuickFixState &state, NameAST *ast) + { + if (ast && state.isCursorOn(ast)) { + if (const Name *name = ast->name) { + unsigned line, column; + state.document()->translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); + + Symbol *fwdClass = 0; + + foreach (const LookupItem &r, state.context().lookup(name, state.document()->scopeAt(line, column))) { + if (! r.declaration()) + continue; + else if (ForwardClassDeclaration *fwd = r.declaration()->asForwardClassDeclaration()) + fwdClass = fwd; + else if (r.declaration()->isClass()) + return 0; // nothing to do. + } + + return fwdClass; + } + } + + return 0; + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, Symbol *fwdClass) + : CppQuickFixOperation(state, priority) + , fwdClass(fwdClass) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "#include header file")); + } + + virtual void createChanges() + { + Q_ASSERT(fwdClass != 0); + + if (Class *k = state().snapshot().findMatchingClassDeclaration(fwdClass)) { + const QString headerFile = QString::fromUtf8(k->fileName(), k->fileNameLength()); + + // collect the fwd headers + Snapshot fwdHeaders; + fwdHeaders.insert(state().snapshot().document(headerFile)); + foreach (Document::Ptr doc, state().snapshot()) { + QFileInfo headerFileInfo(doc->fileName()); + if (doc->globalSymbolCount() == 0 && doc->includes().size() == 1) + fwdHeaders.insert(doc); + else if (headerFileInfo.suffix().isEmpty()) + fwdHeaders.insert(doc); + } + + + DependencyTable dep; + dep.build(fwdHeaders); + QStringList candidates = dep.dependencyTable().value(headerFile); + + const QString className = QString::fromUtf8(k->identifier()->chars()); + + QString best; + foreach (const QString &c, candidates) { + QFileInfo headerFileInfo(c); + if (headerFileInfo.fileName() == className) { + best = c; + break; + } else if (headerFileInfo.fileName().at(0).isUpper()) { + best = c; + // and continue + } else if (! best.isEmpty()) { + if (c.count(QLatin1Char('/')) < best.count(QLatin1Char('/'))) + best = c; + } + } + + if (best.isEmpty()) + best = headerFile; + + int pos = startOf(1); + + unsigned currentLine = state().textCursor().blockNumber() + 1; + unsigned bestLine = 0; + foreach (const Document::Include &incl, state().document()->includes()) { + if (incl.line() < currentLine) + bestLine = incl.line(); + } + + if (bestLine) + pos = state().editor()->document()->findBlockByNumber(bestLine).position(); + + Utils::ChangeSet changes; + changes.insert(pos, QString("#include <%1>\n").arg(QFileInfo(best).fileName())); + refactoringChanges()->changeFile(fileName(), changes); + } + } + + private: + Symbol *fwdClass; + }; +}; + +class AddLocalDeclarationOp: public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + + for (int index = path.size() - 1; index != -1; --index) { + if (BinaryExpressionAST *binary = path.at(index)->asBinaryExpression()) { + if (binary->left_expression && binary->right_expression && state.tokenAt(binary->binary_op_token).is(T_EQUAL)) { + if (state.isCursorOn(binary->left_expression) && binary->left_expression->asSimpleName() != 0) { + SimpleNameAST *nameAST = binary->left_expression->asSimpleName(); + const QList<LookupItem> results = state.context().lookup(nameAST->name, state.scopeAt(nameAST->firstToken())); + Declaration *decl = 0; + foreach (const LookupItem &r, results) { + if (! r.declaration()) + continue; + else if (Declaration *d = r.declaration()->asDeclaration()) { + if (! d->type()->isFunctionType()) { + decl = d; + break; + } + } + } + + if (! decl) { + return singleResult(new Operation(state, index, binary)); + } + } + } + } + } + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binaryAST) + : CppQuickFixOperation(state, priority) + , binaryAST(binaryAST) + { + setDescription(QApplication::translate("CppTools::QuickFix", "Add local declaration")); + } + + virtual void createChanges() + { + TypeOfExpression typeOfExpression; + typeOfExpression.init(state().document(), state().snapshot(), state().context().bindings()); + const QList<LookupItem> result = typeOfExpression(state().textOf(binaryAST->right_expression), + state().scopeAt(binaryAST->firstToken()), + TypeOfExpression::Preprocess); + + if (! result.isEmpty()) { + + SubstitutionEnvironment env; + env.setContext(state().context()); + env.switchScope(result.first().scope()); + UseQualifiedNames q; + env.enter(&q); + + Control *control = state().context().control().data(); + FullySpecifiedType tn = rewriteType(result.first().type(), &env, control); + + Overview oo; + QString ty = oo(tn); + if (! ty.isEmpty()) { + const QChar ch = ty.at(ty.size() - 1); + + if (ch.isLetterOrNumber() || ch == QLatin1Char(' ') || ch == QLatin1Char('>')) + ty += QLatin1Char(' '); + + Utils::ChangeSet changes; + changes.insert(startOf(binaryAST), ty); + refactoringChanges()->changeFile(fileName(), changes); + } + } + } + + private: + BinaryExpressionAST *binaryAST; + }; +}; + +/* + * Turns "an_example_symbol" into "anExampleSymbol" and + * "AN_EXAMPLE_SYMBOL" into "AnExampleSymbol". + */ +class ToCamelCaseConverter : public CppQuickFixFactory +{ +public: + virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + { + const QList<AST *> &path = state.path(); + + if (path.isEmpty()) + return noResult(); + + AST * const ast = path.last(); + const Name *name = 0; + if (const NameAST * const nameAst = ast->asName()) { + if (nameAst->name && nameAst->name->asNameId()) + name = nameAst->name; + } else if (const NamespaceAST * const namespaceAst = ast->asNamespace()) { + name = namespaceAst->symbol->name(); + } + + if (!name) + return noResult(); + + QString newName = QString::fromUtf8(name->identifier()->chars()); + if (newName.length() < 3) + return noResult(); + for (int i = 1; i < newName.length() - 1; ++i) { + if (Operation::isConvertibleUnderscore(newName, i)) + return singleResult(new Operation(state, path.size() - 1, newName)); + } + + return noResult(); + } + +private: + class Operation: public CppQuickFixOperation + { + public: + Operation(const CppQuickFixState &state, int priority, const QString &newName) + : CppQuickFixOperation(state, priority) + , m_name(newName) + { + setDescription(QApplication::translate("CppTools::QuickFix", + "Convert to Camel Case ...")); + } + + virtual void createChanges() + { + for (int i = 1; i < m_name.length(); ++i) { + QCharRef c = m_name[i]; + if (c.isUpper()) { + c = c.toLower(); + } else if (i < m_name.length() - 1 + && isConvertibleUnderscore(m_name, i)) { + m_name.remove(i, 1); + m_name[i] = m_name.at(i).toUpper(); + } + } + static_cast<CppEditor::Internal::CPPEditor*>(state().editor())->renameUsagesNow(m_name); + } + + static bool isConvertibleUnderscore(const QString &name, int pos) + { + return name.at(pos) == QLatin1Char('_') && name.at(pos+1).isLetter() + && !(pos == 1 && name.at(0) == QLatin1Char('m')); + } + + private: + QString m_name; + }; +}; + +} // end of anonymous namespace + +void CppQuickFixCollector::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) +{ + plugIn->addAutoReleasedObject(new UseInverseOp); + plugIn->addAutoReleasedObject(new FlipBinaryOp); + plugIn->addAutoReleasedObject(new RewriteLogicalAndOp); + plugIn->addAutoReleasedObject(new SplitSimpleDeclarationOp); + plugIn->addAutoReleasedObject(new AddBracesToIfOp); + plugIn->addAutoReleasedObject(new MoveDeclarationOutOfIfOp); + plugIn->addAutoReleasedObject(new MoveDeclarationOutOfWhileOp); + plugIn->addAutoReleasedObject(new SplitIfStatementOp); + plugIn->addAutoReleasedObject(new WrapStringLiteral); + plugIn->addAutoReleasedObject(new TranslateStringLiteral); + plugIn->addAutoReleasedObject(new CStringToNSString); + plugIn->addAutoReleasedObject(new ConvertNumericLiteral); + plugIn->addAutoReleasedObject(new CompleteSwitchCaseStatement); + plugIn->addAutoReleasedObject(new FixForwardDeclarationOp); + plugIn->addAutoReleasedObject(new AddLocalDeclarationOp); + plugIn->addAutoReleasedObject(new ToCamelCaseConverter); + +#if NOT_YET + plugIn->addAutoReleasedObject(new Internal::DeclFromDef); +#endif +} diff --git a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp index 944d7b568a348c63325a182782b0eface4a81a2c..75da806ae7ff68665eadfc2643c885ee5405ae82 100644 --- a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp +++ b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp @@ -40,11 +40,10 @@ #include <QtCore/QFileInfo> using namespace QmlJS::AST; +using namespace QmlJSEditor; using namespace QmlJSEditor::Internal; -ComponentFromObjectDef::ComponentFromObjectDef(TextEditor::BaseTextEditor *editor) - : QmlJSQuickFixOperation(editor), _objDef(0) -{} +namespace { static QString getId(UiObjectDefinition *def) { @@ -74,60 +73,71 @@ static QString getId(UiObjectDefinition *def) return QString(); } -QString ComponentFromObjectDef::description() const +class Operation: public QmlJSQuickFixOperation { - return QCoreApplication::translate("QmlJSEditor::ComponentFromObjectDef", - "Extract Component"); -} + UiObjectDefinition *_objDef; + +public: + Operation(const QmlJSQuickFixState &state, UiObjectDefinition *objDef) + : QmlJSQuickFixOperation(state, 0) + , _objDef(objDef) + { + setDescription(QCoreApplication::translate("QmlJSEditor::ComponentFromObjectDef", + "Extract Component")); + } -void ComponentFromObjectDef::createChanges() -{ - Q_ASSERT(_objDef != 0); + virtual void createChanges() + { + Q_ASSERT(_objDef != 0); - QString componentName = getId(_objDef); - componentName[0] = componentName.at(0).toUpper(); + QString componentName = getId(_objDef); + componentName[0] = componentName.at(0).toUpper(); - const QString path = editor()->file()->fileName(); - const QString newFileName = QFileInfo(path).path() + QDir::separator() + componentName + QLatin1String(".qml"); + const QString path = fileName(); + const QString newFileName = QFileInfo(path).path() + QDir::separator() + componentName + QLatin1String(".qml"); - QString imports; - UiProgram *prog = semanticInfo().document->qmlProgram(); - if (prog && prog->imports) { - const int start = startPosition(prog->imports->firstSourceLocation()); - const int end = startPosition(prog->members->member->firstSourceLocation()); - imports = textOf(start, end); - } + QString imports; + UiProgram *prog = state().semanticInfo().document->qmlProgram(); + if (prog && prog->imports) { + const int start = startPosition(prog->imports->firstSourceLocation()); + const int end = startPosition(prog->members->member->firstSourceLocation()); + imports = state().textOf(start, end); + } - const int start = startPosition(_objDef->firstSourceLocation()); - const int end = startPosition(_objDef->lastSourceLocation()); - const QString txt = imports + textOf(start, end) + QLatin1String("}\n"); + const int start = startPosition(_objDef->firstSourceLocation()); + const int end = startPosition(_objDef->lastSourceLocation()); + const QString txt = imports + state().textOf(start, end) + + QLatin1String("}\n"); - Utils::ChangeSet changes; - changes.replace(start, end, componentName + QLatin1String(" {\n")); - qmljsRefactoringChanges()->changeFile(fileName(), changes); - qmljsRefactoringChanges()->reindent(fileName(), range(start, end + 1)); + Utils::ChangeSet changes; + changes.replace(start, end, componentName + QLatin1String(" {\n")); + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), range(start, end + 1)); - qmljsRefactoringChanges()->createFile(newFileName, txt); - qmljsRefactoringChanges()->reindent(newFileName, range(0, txt.length() - 1)); -} + refactoringChanges()->createFile(newFileName, txt); + refactoringChanges()->reindent(newFileName, range(0, txt.length() - 1)); + } +}; + +} // end of anonymous namespace -int ComponentFromObjectDef::check() +QList<QmlJSQuickFixOperation::Ptr> ComponentFromObjectDef::match(const QmlJSQuickFixState &state) { - _objDef = 0; - const int pos = textCursor().position(); + QList<QmlJSQuickFixOperation::Ptr> result; + const int pos = state.textCursor().position(); - QList<Node *> path = semanticInfo().astPath(pos); + QList<Node *> path = state.semanticInfo().astPath(pos); for (int i = path.size() - 1; i >= 0; --i) { Node *node = path.at(i); if (UiObjectDefinition *objDef = cast<UiObjectDefinition *>(node)) { if (i > 0 && !cast<UiProgram*>(path.at(i - 1))) { // node is not the root node if (!getId(objDef).isEmpty()) { - _objDef = objDef; - return 0; + result.append(QmlJSQuickFixOperation::Ptr(new Operation(state, objDef))); + return result; } } } } - return -1; + return result; } diff --git a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h index 382e51c7f06fa93f815e6808edb4bfd1793b0d46..662ccf1877b191e256c650e4c50f1a96d99398f7 100644 --- a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h +++ b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h @@ -35,17 +35,10 @@ namespace QmlJSEditor { namespace Internal { -class ComponentFromObjectDef: public QmlJSQuickFixOperation +class ComponentFromObjectDef: public QmlJSQuickFixFactory { public: - ComponentFromObjectDef(TextEditor::BaseTextEditor *editor); - - virtual QString description() const; - virtual void createChanges(); - virtual int check(); - -private: - QmlJS::AST::UiObjectDefinition *_objDef; + virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state); }; } // namespace Internal diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index f67dd204fe7593076d9f7ff7e71f3305379a7eca..104e036ac92758ae2764f812efb4b670bd551c63 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -179,6 +179,7 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e m_quickFixCollector = new QmlJSQuickFixCollector; addAutoReleasedObject(m_quickFixCollector); + QmlJSQuickFixCollector::registerQuickFixes(this); addAutoReleasedObject(new QmlJSOutlineWidgetFactory); diff --git a/src/plugins/qmljseditor/qmljsquickfix.cpp b/src/plugins/qmljseditor/qmljsquickfix.cpp index 2108147bb6178e885d72a2964ff5a9da641c540a..b7be83002c61ced07e64716baa6c4ef3cf60842e 100644 --- a/src/plugins/qmljseditor/qmljsquickfix.cpp +++ b/src/plugins/qmljseditor/qmljsquickfix.cpp @@ -33,6 +33,7 @@ #include "qmljsrefactoringchanges.h" #include "qmljs/parser/qmljsast_p.h" +#include <extensionsystem/iplugin.h> #include <extensionsystem/pluginmanager.h> #include <qmljs/qmljsmodelmanagerinterface.h> @@ -40,146 +41,157 @@ #include <QtGui/QApplication> #include <QtCore/QDebug> +using namespace QmlJS; +using namespace QmlJS::AST; using namespace QmlJSEditor; using namespace QmlJSEditor::Internal; +using namespace TextEditor; using TextEditor::RefactoringChanges; -class QmlJSQuickFixState: public TextEditor::QuickFixState -{ -public: - SemanticInfo semanticInfo; -}; - namespace { -class SplitInitializerOp: public QmlJSQuickFixOperation { +class SplitInitializerOp: public QmlJSQuickFixFactory +{ public: - SplitInitializerOp(TextEditor::BaseTextEditor *editor) - : QmlJSQuickFixOperation(editor), _objectInitializer(0) - {} - - virtual QString description() const + virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state) { - return QApplication::translate("QmlJSEditor::QuickFix", "Split initializer"); - } + QList<QmlJSQuickFixOperation::Ptr> result; - virtual void createChanges() - { - Q_ASSERT(_objectInitializer != 0); + UiObjectInitializer *objectInitializer = 0; - Utils::ChangeSet changes; + const int pos = state.textCursor().position(); - for (QmlJS::AST::UiObjectMemberList *it = _objectInitializer->members; it; it = it->next) { - if (QmlJS::AST::UiObjectMember *member = it->member) { - const QmlJS::AST::SourceLocation loc = member->firstSourceLocation(); + if (QmlJS::AST::Node *member = state.semanticInfo().declaringMember(pos)) { + if (QmlJS::AST::UiObjectBinding *b = QmlJS::AST::cast<QmlJS::AST::UiObjectBinding *>(member)) { + if (b->initializer->lbraceToken.startLine == b->initializer->rbraceToken.startLine) + objectInitializer = b->initializer; - // insert a newline at the beginning of this binding - changes.insert(startPosition(loc), QLatin1String("\n")); + } else if (QmlJS::AST::UiObjectDefinition *b = QmlJS::AST::cast<QmlJS::AST::UiObjectDefinition *>(member)) { + if (b->initializer->lbraceToken.startLine == b->initializer->rbraceToken.startLine) + objectInitializer = b->initializer; } } - // insert a newline before the closing brace - changes.insert(startPosition(_objectInitializer->rbraceToken), - QLatin1String("\n")); - - refactoringChanges()->changeFile(fileName(), changes); - refactoringChanges()->reindent(fileName(), - range(startPosition(_objectInitializer->lbraceToken), - startPosition(_objectInitializer->rbraceToken))); + if (objectInitializer) + result.append(QSharedPointer<QmlJSQuickFixOperation>(new Operation(state, objectInitializer))); + return result; + } + virtual QString description() const + { + return QApplication::translate("QmlJSEditor::QuickFix", "Split initializer"); } - virtual int check() +private: + class Operation: public QmlJSQuickFixOperation { - _objectInitializer = 0; + UiObjectInitializer *_objectInitializer; - const int pos = textCursor().position(); + public: + Operation(const QmlJSQuickFixState &state, UiObjectInitializer *objectInitializer) + : QmlJSQuickFixOperation(state, 0) + , _objectInitializer(objectInitializer) + {} - if (QmlJS::AST::Node *member = semanticInfo().declaringMember(pos)) { - if (QmlJS::AST::UiObjectBinding *b = QmlJS::AST::cast<QmlJS::AST::UiObjectBinding *>(member)) { - if (b->initializer->lbraceToken.startLine == b->initializer->rbraceToken.startLine) - _objectInitializer = b->initializer; + virtual void createChanges() + { + Q_ASSERT(_objectInitializer != 0); - } else if (QmlJS::AST::UiObjectDefinition *b = QmlJS::AST::cast<QmlJS::AST::UiObjectDefinition *>(member)) { - if (b->initializer->lbraceToken.startLine == b->initializer->rbraceToken.startLine) - _objectInitializer = b->initializer; + Utils::ChangeSet changes; + + for (QmlJS::AST::UiObjectMemberList *it = _objectInitializer->members; it; it = it->next) { + if (QmlJS::AST::UiObjectMember *member = it->member) { + const QmlJS::AST::SourceLocation loc = member->firstSourceLocation(); + + // insert a newline at the beginning of this binding + changes.insert(startPosition(loc), QLatin1String("\n")); + } } - } - if (! _objectInitializer) - return -1; + // insert a newline before the closing brace + changes.insert(startPosition(_objectInitializer->rbraceToken), + QLatin1String("\n")); - return 0; // very high priority - } + refactoringChanges()->changeFile(fileName(), changes); + refactoringChanges()->reindent(fileName(), + range(startPosition(_objectInitializer->lbraceToken), + startPosition(_objectInitializer->rbraceToken))); -private: - QmlJS::AST::UiObjectInitializer *_objectInitializer; + } + }; }; - } // end of anonymous namespace +QmlJSQuickFixState::QmlJSQuickFixState(TextEditor::BaseTextEditor *editor) + : QuickFixState(editor) +{ +} -QmlJSQuickFixOperation::QmlJSQuickFixOperation(TextEditor::BaseTextEditor *editor) - : TextEditor::QuickFixOperation(editor) - , _refactoringChanges(0) +SemanticInfo QmlJSQuickFixState::semanticInfo() const { + return _semanticInfo; } -QmlJSQuickFixOperation::~QmlJSQuickFixOperation() +Snapshot QmlJSQuickFixState::snapshot() const { - if (_refactoringChanges) - delete _refactoringChanges; + return _semanticInfo.snapshot; } -QmlJS::Document::Ptr QmlJSQuickFixOperation::document() const +Document::Ptr QmlJSQuickFixState::document() const { return _semanticInfo.document; } -const QmlJS::Snapshot &QmlJSQuickFixOperation::snapshot() const +unsigned QmlJSQuickFixState::startPosition(const QmlJS::AST::SourceLocation &loc) const { - return _semanticInfo.snapshot; + return position(loc.startLine, loc.startColumn); } -const SemanticInfo &QmlJSQuickFixOperation::semanticInfo() const +QmlJSQuickFixOperation::QmlJSQuickFixOperation(const QmlJSQuickFixState &state, int priority) + : QuickFixOperation(priority) + , _state(state) + , _refactoringChanges(new QmlJSRefactoringChanges(ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>(), + state.snapshot())) { - return _semanticInfo; } -int QmlJSQuickFixOperation::match(TextEditor::QuickFixState *state) +QmlJSQuickFixOperation::~QmlJSQuickFixOperation() { - QmlJS::ModelManagerInterface *modelManager = ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>(); - QmlJSQuickFixState *s = static_cast<QmlJSQuickFixState *>(state); - _semanticInfo = s->semanticInfo; - if (_refactoringChanges) { - delete _refactoringChanges; - } - _refactoringChanges = new QmlJSRefactoringChanges(modelManager, _semanticInfo.snapshot); - return check(); +} + +const QmlJSQuickFixState &QmlJSQuickFixOperation::state() const +{ + return _state; } QString QmlJSQuickFixOperation::fileName() const { - return document()->fileName(); + return state().document()->fileName(); } -void QmlJSQuickFixOperation::apply() +QmlJSRefactoringChanges *QmlJSQuickFixOperation::refactoringChanges() const { - _refactoringChanges->apply(); + return _refactoringChanges.data(); } -QmlJSRefactoringChanges *QmlJSQuickFixOperation::qmljsRefactoringChanges() const -{ return _refactoringChanges; } +QmlJSQuickFixFactory::QmlJSQuickFixFactory() +{ +} -RefactoringChanges *QmlJSQuickFixOperation::refactoringChanges() const -{ return qmljsRefactoringChanges(); } +QmlJSQuickFixFactory::~QmlJSQuickFixFactory() +{ +} -unsigned QmlJSQuickFixOperation::startPosition(const QmlJS::AST::SourceLocation &loc) const +QList<QuickFixOperation::Ptr> QmlJSQuickFixFactory::matchingOperations(QuickFixState *state) { - return position(loc.startLine, loc.startColumn); + if (QmlJSQuickFixState *qmljsState = static_cast<QmlJSQuickFixState *>(state)) + return match(*qmljsState); + else + return QList<TextEditor::QuickFixOperation::Ptr>(); } + QmlJSQuickFixCollector::QmlJSQuickFixCollector() { } @@ -190,35 +202,39 @@ QmlJSQuickFixCollector::~QmlJSQuickFixCollector() bool QmlJSQuickFixCollector::supportsEditor(TextEditor::ITextEditable *editable) { - if (qobject_cast<QmlJSTextEditor *>(editable->widget()) != 0) - return true; - - return false; + return qobject_cast<QmlJSTextEditor *>(editable->widget()) != 0; } -TextEditor::QuickFixState *QmlJSQuickFixCollector::initializeCompletion(TextEditor::ITextEditable *editable) +TextEditor::QuickFixState *QmlJSQuickFixCollector::initializeCompletion(TextEditor::BaseTextEditor *editor) { - if (QmlJSTextEditor *editor = qobject_cast<QmlJSTextEditor *>(editable->widget())) { - const SemanticInfo info = editor->semanticInfo(); + if (QmlJSTextEditor *qmljsEditor = qobject_cast<QmlJSTextEditor *>(editor)) { + const SemanticInfo info = qmljsEditor->semanticInfo(); - if (editor->isOutdated()) { + if (qmljsEditor->isOutdated()) { // outdated qWarning() << "TODO: outdated semantic info, force a reparse."; return 0; } - QmlJSQuickFixState *state = new QmlJSQuickFixState; - state->semanticInfo = info; + QmlJSQuickFixState *state = new QmlJSQuickFixState(editor); + state->_semanticInfo = info; return state; } return 0; } -QList<TextEditor::QuickFixOperation::Ptr> QmlJSQuickFixCollector::quickFixOperations(TextEditor::BaseTextEditor *editor) const +QList<TextEditor::QuickFixFactory *> QmlJSQuickFixCollector::quickFixFactories() const +{ + QList<TextEditor::QuickFixFactory *> results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (QmlJSQuickFixFactory *f, pm->getObjects<QmlJSEditor::QmlJSQuickFixFactory>()) + results.append(f); + return results; +} + +void QmlJSQuickFixCollector::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) { - QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations; - quickFixOperations.append(TextEditor::QuickFixOperation::Ptr(new SplitInitializerOp(editor))); - quickFixOperations.append(TextEditor::QuickFixOperation::Ptr(new ComponentFromObjectDef(editor))); - return quickFixOperations; + plugIn->addAutoReleasedObject(new SplitInitializerOp); + plugIn->addAutoReleasedObject(new ComponentFromObjectDef); } diff --git a/src/plugins/qmljseditor/qmljsquickfix.h b/src/plugins/qmljseditor/qmljsquickfix.h index 3fa6e7fad33e1ab84952d16b2f8d1946a2ac4e89..dbda6b59d7ce692d46b0fbf7f8dca7fc3141c0bf 100644 --- a/src/plugins/qmljseditor/qmljsquickfix.h +++ b/src/plugins/qmljseditor/qmljsquickfix.h @@ -31,49 +31,85 @@ #define QMLJSQUICKFIX_H #include "qmljseditor.h" +#include "qmljsrefactoringchanges.h" #include <texteditor/quickfix.h> #include <qmljs/parser/qmljsastfwd_p.h> #include <qmljs/qmljsdocument.h> +namespace ExtensionSystem { +class IPlugin; +} + namespace QmlJS { class ModelManagerInterface; } namespace QmlJSEditor { -class QmlJSRefactoringChanges; namespace Internal { +class QmlJSQuickFixCollector; +} // end of namespace Internal -class QmlJSQuickFixOperation: public TextEditor::QuickFixOperation +class QMLJS_EXPORT QmlJSQuickFixState: public TextEditor::QuickFixState { - Q_DISABLE_COPY(QmlJSQuickFixOperation) + friend class Internal::QmlJSQuickFixCollector; public: - QmlJSQuickFixOperation(TextEditor::BaseTextEditor *editor); - virtual ~QmlJSQuickFixOperation(); + QmlJSQuickFixState(TextEditor::BaseTextEditor *editor); + typedef Utils::ChangeSet::Range Range; + Internal::SemanticInfo semanticInfo() const; + QmlJS::Snapshot snapshot() const; QmlJS::Document::Ptr document() const; - const QmlJS::Snapshot &snapshot() const; - const SemanticInfo &semanticInfo() const; - virtual int check() = 0; - virtual int match(TextEditor::QuickFixState *state); + unsigned startPosition(const QmlJS::AST::SourceLocation &loc) const; + +private: + Internal::SemanticInfo _semanticInfo; +}; + +class QMLJS_EXPORT QmlJSQuickFixOperation: public TextEditor::QuickFixOperation +{ + Q_DISABLE_COPY(QmlJSQuickFixOperation) + +public: + QmlJSQuickFixOperation(const QmlJSQuickFixState &state, int priority = -1); + virtual ~QmlJSQuickFixOperation(); + + const QmlJSQuickFixState &state() const; protected: QString fileName() const; - virtual void apply(); - QmlJSRefactoringChanges *qmljsRefactoringChanges() const; - virtual TextEditor::RefactoringChanges *refactoringChanges() const; + QmlJSRefactoringChanges *refactoringChanges() const; - unsigned startPosition(const QmlJS::AST::SourceLocation &loc) const; +protected: // Utility functions forwarding to QmlJSQuickFixState + unsigned startPosition(const QmlJS::AST::SourceLocation &loc) const + { return state().startPosition(loc); } + + static QmlJSQuickFixState::Range range(int start, int end) + { return QmlJSQuickFixState::range(start, end); } private: - SemanticInfo _semanticInfo; - QmlJSRefactoringChanges *_refactoringChanges; + QmlJSQuickFixState _state; + QScopedPointer<QmlJSRefactoringChanges> _refactoringChanges; +}; + +class QMLJS_EXPORT QmlJSQuickFixFactory: public TextEditor::QuickFixFactory +{ + Q_OBJECT + +public: + QmlJSQuickFixFactory(); + virtual ~QmlJSQuickFixFactory(); + + virtual QList<TextEditor::QuickFixOperation::Ptr> matchingOperations(TextEditor::QuickFixState *state); + virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state) = 0; }; +namespace Internal { + class QmlJSQuickFixCollector: public TextEditor::QuickFixCollector { Q_OBJECT @@ -83,8 +119,11 @@ public: virtual ~QmlJSQuickFixCollector(); virtual bool supportsEditor(TextEditor::ITextEditable *editor); - virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::ITextEditable *editable); - virtual QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations(TextEditor::BaseTextEditor *editor) const; + virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::BaseTextEditor *editor); + + virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; + + static void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); }; } // end of namespace Internal diff --git a/src/plugins/qmljseditor/qmljsrefactoringchanges.h b/src/plugins/qmljseditor/qmljsrefactoringchanges.h index 3a4198743dd0302143e20ce5ea24a01d70cbacb2..ef286a3f7bf79874ca40ab380fbe39b076da240d 100644 --- a/src/plugins/qmljseditor/qmljsrefactoringchanges.h +++ b/src/plugins/qmljseditor/qmljsrefactoringchanges.h @@ -40,7 +40,7 @@ class ModelManagerInterface; namespace QmlJSEditor { -class QmlJSRefactoringChanges: public TextEditor::RefactoringChanges +class QMLJS_EXPORT QmlJSRefactoringChanges: public TextEditor::RefactoringChanges { public: QmlJSRefactoringChanges(QmlJS::ModelManagerInterface *modelManager, diff --git a/src/plugins/texteditor/quickfix.cpp b/src/plugins/texteditor/quickfix.cpp index 8e5e024750e9c305d19f9b16e61002b09cd2d59f..415c0119abcf01490f6731451a66b16690aac417 100644 --- a/src/plugins/texteditor/quickfix.cpp +++ b/src/plugins/texteditor/quickfix.cpp @@ -37,58 +37,56 @@ #include <QtCore/QDebug> -using TextEditor::RefactoringChanges; -using TextEditor::QuickFixOperation; -using TextEditor::QuickFixCollector; -using TextEditor::IQuickFixFactory; +using namespace TextEditor; -QuickFixOperation::QuickFixOperation(TextEditor::BaseTextEditor *editor) +QuickFixState::QuickFixState(TextEditor::BaseTextEditor *editor) : _editor(editor) + , _textCursor(editor->textCursor()) { } -QuickFixOperation::~QuickFixOperation() +QuickFixState::~QuickFixState() { } -TextEditor::BaseTextEditor *QuickFixOperation::editor() const +TextEditor::BaseTextEditor *QuickFixState::editor() const { return _editor; } -QTextCursor QuickFixOperation::textCursor() const +QTextCursor QuickFixState::textCursor() const { return _textCursor; } -void QuickFixOperation::setTextCursor(const QTextCursor &cursor) +void QuickFixState::setCursor(const QTextCursor &cursor) { _textCursor = cursor; } -int QuickFixOperation::selectionStart() const +int QuickFixState::selectionStart() const { return _textCursor.selectionStart(); } -int QuickFixOperation::selectionEnd() const +int QuickFixState::selectionEnd() const { return _textCursor.selectionEnd(); } -int QuickFixOperation::position(int line, int column) const +int QuickFixState::position(int line, int column) const { QTextDocument *doc = editor()->document(); return doc->findBlockByNumber(line - 1).position() + column - 1; } -QChar QuickFixOperation::charAt(int offset) const +QChar QuickFixState::charAt(int offset) const { QTextDocument *doc = _textCursor.document(); return doc->characterAt(offset); } -QString QuickFixOperation::textOf(int start, int end) const +QString QuickFixState::textOf(int start, int end) const { QTextCursor tc = _textCursor; tc.setPosition(start); @@ -96,51 +94,98 @@ QString QuickFixOperation::textOf(int start, int end) const return tc.selectedText(); } -TextEditor::RefactoringChanges::Range QuickFixOperation::range(int start, int end) +TextEditor::RefactoringChanges::Range QuickFixState::range(int start, int end) { return TextEditor::RefactoringChanges::Range(start, end); } +QuickFixOperation::QuickFixOperation(int priority) +{ + setPriority(priority); +} + +QuickFixOperation::~QuickFixOperation() +{ +} + +int QuickFixOperation::priority() const +{ + return _priority; +} + +void QuickFixOperation::setPriority(int priority) +{ + _priority = priority; +} + +QString QuickFixOperation::description() const +{ + return _description; +} + +void QuickFixOperation::setDescription(const QString &description) +{ + _description = description; +} + void QuickFixOperation::perform() { createChanges(); - apply(); + refactoringChanges()->apply(); +} + +QuickFixFactory::QuickFixFactory(QObject *parent) + : QObject(parent) +{ +} + +QuickFixFactory::~QuickFixFactory() +{ } QuickFixCollector::QuickFixCollector() : _editable(0) -{ } +{ +} QuickFixCollector::~QuickFixCollector() -{ } +{ +} TextEditor::ITextEditable *QuickFixCollector::editor() const -{ return _editable; } +{ + return _editable; +} int QuickFixCollector::startPosition() const -{ return _editable->position(); } +{ + return _editable->position(); +} bool QuickFixCollector::triggersCompletion(TextEditor::ITextEditable *) -{ return false; } +{ + return false; +} int QuickFixCollector::startCompletion(TextEditor::ITextEditable *editable) { Q_ASSERT(editable != 0); _editable = editable; + TextEditor::BaseTextEditor *editor = qobject_cast<TextEditor::BaseTextEditor *>(editable->widget()); + Q_ASSERT(editor != 0); - if (TextEditor::QuickFixState *state = initializeCompletion(editable)) { - TextEditor::BaseTextEditor *editor = qobject_cast<TextEditor::BaseTextEditor *>(editable->widget()); - Q_ASSERT(editor != 0); + if (TextEditor::QuickFixState *state = initializeCompletion(editor)) { + QMap<int, QList<QuickFixOperation::Ptr> > matchedOps; - const QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations = this->quickFixOperations(editor); - QMap<int, QList<TextEditor::QuickFixOperation::Ptr> > matchedOps; + foreach (QuickFixFactory *factory, quickFixFactories()) { + QList<QuickFixOperation::Ptr> ops = factory->matchingOperations(state); - foreach (TextEditor::QuickFixOperation::Ptr op, quickFixOperations) { - op->setTextCursor(editor->textCursor()); - int priority = op->match(state); - if (priority != -1) - matchedOps[priority].append(op); + foreach (QuickFixOperation::Ptr op, ops) { + const int priority = op->priority(); + if (priority != -1) + matchedOps[priority].append(op); + } } QMapIterator<int, QList<TextEditor::QuickFixOperation::Ptr> > it(matchedOps); @@ -185,26 +230,3 @@ void QuickFixCollector::cleanup() { _quickFixes.clear(); } - -QList<TextEditor::QuickFixOperation::Ptr> QuickFixCollector::quickFixOperations(TextEditor::BaseTextEditor *editor) const -{ - QList<TextEditor::IQuickFixFactory *> factories = - ExtensionSystem::PluginManager::instance()->getObjects<TextEditor::IQuickFixFactory>(); - - QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations; - - foreach (TextEditor::IQuickFixFactory *factory, factories) - quickFixOperations += factory->quickFixOperations(editor); - - return quickFixOperations; -} - -IQuickFixFactory::IQuickFixFactory(QObject *parent) - : QObject(parent) -{ -} - -IQuickFixFactory::~IQuickFixFactory() -{ - -} diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h index 2ad179e0ac9d19e5420ba572b1381b334bb36fad..342fce4ecfe603e1d967c5b19a3dff6b1ca54455 100644 --- a/src/plugins/texteditor/quickfix.h +++ b/src/plugins/texteditor/quickfix.h @@ -42,43 +42,28 @@ namespace TextEditor { class BaseTextEditor; -class QuickFixOperation; class TEXTEDITOR_EXPORT QuickFixState { - Q_DISABLE_COPY(QuickFixState) - public: - QuickFixState() {} - virtual ~QuickFixState() {} -}; - -class TEXTEDITOR_EXPORT QuickFixOperation -{ - Q_DISABLE_COPY(QuickFixOperation) - -public: - typedef QSharedPointer<QuickFixOperation> Ptr; - -public: - QuickFixOperation(TextEditor::BaseTextEditor *editor); - virtual ~QuickFixOperation(); - - virtual QString description() const = 0; - virtual void createChanges() = 0; - - virtual int match(QuickFixState *state) = 0; - - void perform(); + QuickFixState(TextEditor::BaseTextEditor *editor); + virtual ~QuickFixState(); TextEditor::BaseTextEditor *editor() const; QTextCursor textCursor() const; - void setTextCursor(const QTextCursor &cursor); + void setCursor(const QTextCursor &cursor); int selectionStart() const; int selectionEnd() const; + /** + * Calculates the offset in the document for the given line and column. + * + * \param line The line number, 1-based. + * \param column The column number, 1-based. + * \return The offset in the \c QTextDocument of the editor. + */ int position(int line, int column) const; QChar charAt(int offset) const; @@ -86,15 +71,50 @@ public: static TextEditor::RefactoringChanges::Range range(int start, int end); +private: + TextEditor::BaseTextEditor *_editor; + QTextCursor _textCursor; +}; + +class TEXTEDITOR_EXPORT QuickFixOperation +{ + Q_DISABLE_COPY(QuickFixOperation) + +public: + typedef QSharedPointer<QuickFixOperation> Ptr; + +public: + QuickFixOperation(int priority = -1); + virtual ~QuickFixOperation(); + + virtual int priority() const; + void setPriority(int priority); + virtual QString description() const; + void setDescription(const QString &description); + + virtual void perform(); + + virtual void createChanges() = 0; + protected: - virtual void apply() = 0; virtual TextEditor::RefactoringChanges *refactoringChanges() const = 0; private: TextEditor::BaseTextEditor *_editor; - QTextCursor _textCursor; + int _priority; + QString _description; }; +class TEXTEDITOR_EXPORT QuickFixFactory: public QObject +{ + Q_OBJECT + +public: + QuickFixFactory(QObject *parent = 0); + virtual ~QuickFixFactory() = 0; + + virtual QList<QuickFixOperation::Ptr> matchingOperations(QuickFixState *state) = 0; +}; class TEXTEDITOR_EXPORT QuickFixCollector: public TextEditor::IQuickFixCollector { @@ -114,23 +134,13 @@ public: virtual void fix(const TextEditor::CompletionItem &item); virtual void cleanup(); - virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::ITextEditable *editable) = 0; - virtual QList<TextEditor::QuickFixOperation::Ptr> quickFixOperations(TextEditor::BaseTextEditor *editor) const; + virtual TextEditor::QuickFixState *initializeCompletion(BaseTextEditor *editable) = 0; + + virtual QList<QuickFixFactory *> quickFixFactories() const = 0; private: TextEditor::ITextEditable *_editable; - QList<TextEditor::QuickFixOperation::Ptr> _quickFixes; -}; - -class TEXTEDITOR_EXPORT IQuickFixFactory: public QObject -{ - Q_OBJECT - -public: - IQuickFixFactory(QObject *parent = 0); - virtual ~IQuickFixFactory(); - - virtual QList<QuickFixOperation::Ptr> quickFixOperations(TextEditor::BaseTextEditor *editor) = 0; + QList<QuickFixOperation::Ptr> _quickFixes; }; } // end of namespace TextEditor diff --git a/tests/manual/cppquickfix/convertnumericliteral.cpp b/tests/manual/cppquickfix/convertnumericliteral.cpp index 6c1a27a2e489c449b657e3e08afae05a821bade2..6d64c98e7f90c36bd6fcce798796e28bb0eadc65 100644 --- a/tests/manual/cppquickfix/convertnumericliteral.cpp +++ b/tests/manual/cppquickfix/convertnumericliteral.cpp @@ -5,7 +5,7 @@ int main() 074; 0x856A; // with type specifier - 199L; + 199LL; 074L; 0xFA0Bu; // uppercase X