diff options
author | Leandro Melo <leandro.melo@nokia.com> | 2011-04-15 16:19:23 +0200 |
---|---|---|
committer | Leandro Melo <leandro.melo@nokia.com> | 2011-05-18 10:46:20 +0200 |
commit | bec4f02495b97b17e0b0f8cb67d0909634c16228 (patch) | |
tree | 21759e0b9ebc6b0dca84f01875223020665d7843 /src | |
parent | d835b769c7d6b37e59a8a74a0d68260d34e7a7f9 (diff) | |
download | qt-creator-bec4f02495b97b17e0b0f8cb67d0909634c16228.tar.gz |
New code assist API
This is a re-work of our completion engine. Primary goals are:
- Allow the computation to run in a separate thread so the GUI is not locked.
- Support a model-based approach. QStrings are still needed (filtering, etc), but
internal structures are free to use more efficient representations.
- Unifiy all kinds of *assist* into a more reusable and extensible framework.
- Remove unnecessary dependencies on the text editor so we have more generic
and easily "plugable" components (still things to be resolved).
Diffstat (limited to 'src')
119 files changed, 9184 insertions, 6432 deletions
diff --git a/src/plugins/cppeditor/cppcompleteswitch.cpp b/src/plugins/cppeditor/cppcompleteswitch.cpp index 62222ac7bf..05ec2c440e 100644 --- a/src/plugins/cppeditor/cppcompleteswitch.cpp +++ b/src/plugins/cppeditor/cppcompleteswitch.cpp @@ -31,6 +31,7 @@ **************************************************************************/ #include "cppcompleteswitch.h" +#include "cppquickfixassistant.h" #include <cplusplus/Overview.h> #include <cplusplus/TypeOfExpression.h> @@ -102,8 +103,11 @@ public: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, CompoundStatementAST *compoundStatement, const QStringList &values) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, + int priority, + CompoundStatementAST *compoundStatement, + const QStringList &values) + : CppQuickFixOperation(interface, priority) , compoundStatement(compoundStatement) , values(values) { @@ -150,15 +154,15 @@ static Enum *findEnum(const QList<LookupItem> &results, return 0; } -static Enum *conditionEnum(const CppQuickFixState &state, +static Enum *conditionEnum(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, SwitchStatementAST *statement) { Block *block = statement->symbol; - Scope *scope = state.document()->scopeAt(block->line(), block->column()); + Scope *scope = interface->semanticInfo().doc->scopeAt(block->line(), block->column()); TypeOfExpression typeOfExpression; - typeOfExpression.init(state.document(), state.snapshot()); + typeOfExpression.init(interface->semanticInfo().doc, interface->snapshot()); const QList<LookupItem> results = typeOfExpression(statement->condition, - state.document(), + interface->semanticInfo().doc, scope); return findEnum(results, typeOfExpression.context()); @@ -166,9 +170,10 @@ static Enum *conditionEnum(const CppQuickFixState &state, } // end of anonymous namespace -QList<CppQuickFixOperation::Ptr> CompleteSwitchCaseStatement::match(const CppQuickFixState &state) +QList<CppQuickFixOperation::Ptr> CompleteSwitchCaseStatement::match( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); if (path.isEmpty()) return noResult(); // nothing to do @@ -178,13 +183,13 @@ QList<CppQuickFixOperation::Ptr> CompleteSwitchCaseStatement::match(const CppQui AST *ast = path.at(depth); SwitchStatementAST *switchStatement = ast->asSwitchStatement(); if (switchStatement) { - if (!state.isCursorOn(switchStatement->switch_token) || !switchStatement->statement) + if (!interface->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)) { + if (Enum *e = conditionEnum(interface, switchStatement)) { // check the possible enum values QStringList values; Overview prettyPrint; @@ -195,8 +200,8 @@ QList<CppQuickFixOperation::Ptr> CompleteSwitchCaseStatement::match(const CppQui } // Get the used values Block *block = switchStatement->symbol; - CaseStatementCollector caseValues(state.document(), state.snapshot(), - state.document()->scopeAt(block->line(), block->column())); + CaseStatementCollector caseValues(interface->semanticInfo().doc, interface->snapshot(), + interface->semanticInfo().doc->scopeAt(block->line(), block->column())); QStringList usedValues = caseValues(switchStatement); // save the values that would be added foreach (const QString &usedValue, usedValues) @@ -204,7 +209,7 @@ QList<CppQuickFixOperation::Ptr> CompleteSwitchCaseStatement::match(const CppQui if (values.isEmpty()) return noResult(); else - return singleResult(new Operation(state, depth, compoundStatement, values)); + return singleResult(new Operation(interface, depth, compoundStatement, values)); } return noResult(); diff --git a/src/plugins/cppeditor/cppcompleteswitch.h b/src/plugins/cppeditor/cppcompleteswitch.h index f1d9c9b361..b0cd42cdde 100644 --- a/src/plugins/cppeditor/cppcompleteswitch.h +++ b/src/plugins/cppeditor/cppcompleteswitch.h @@ -46,7 +46,8 @@ namespace Internal { class CompleteSwitchCaseStatement: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state); + virtual QList<CppQuickFixOperation::Ptr> match( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index 354d4cea7d..0e63fffcd4 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -35,11 +35,10 @@ #include "cppplugin.h" #include "cpphighlighter.h" #include "cppchecksymbols.h" -#include "cppquickfix.h" #include "cpplocalsymbols.h" -#include "cppquickfixcollector.h" #include "cppqtstyleindenter.h" #include "cppautocompleter.h" +#include "cppquickfixassistant.h" #include <AST.h> #include <Control.h> @@ -66,6 +65,7 @@ #include <cpptools/cpptoolsplugin.h> #include <cpptools/cpptoolsconstants.h> #include <cpptools/cppcodeformatter.h> +#include <cpptools/cppcompletionassist.h> #include <coreplugin/icore.h> #include <coreplugin/actionmanager/actionmanager.h> @@ -83,6 +83,9 @@ #include <texteditor/fontsettings.h> #include <texteditor/tabsettings.h> #include <texteditor/texteditorconstants.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/genericproposal.h> #include <QtCore/QDebug> #include <QtCore/QTime> @@ -1617,22 +1620,29 @@ void CPPEditorWidget::contextMenuEvent(QContextMenuEvent *e) QMenu *quickFixMenu = new QMenu(tr("&Refactor"), menu); quickFixMenu->addAction(am->command(Constants::RENAME_SYMBOL_UNDER_CURSOR)->action()); - CppQuickFixCollector *quickFixCollector = CppPlugin::instance()->quickFixCollector(); QSignalMapper mapper; connect(&mapper, SIGNAL(mapped(int)), this, SLOT(performQuickFix(int))); - if (! isOutdated()) { - if (quickFixCollector->startCompletion(editor()) != -1) { - m_quickFixes = quickFixCollector->quickFixes(); - - if (! m_quickFixes.isEmpty()) - quickFixMenu->addSeparator(); - - for (int index = 0; index < m_quickFixes.size(); ++index) { - TextEditor::QuickFixOperation::Ptr op = m_quickFixes.at(index); - QAction *action = quickFixMenu->addAction(op->description()); - mapper.setMapping(action, index); - connect(action, SIGNAL(triggered()), &mapper, SLOT(map())); + TextEditor::IAssistInterface *interface = + createAssistInterface(TextEditor::QuickFix, TextEditor::ExplicitlyInvoked); + if (interface) { + QScopedPointer<TextEditor::IAssistProcessor> processor( + CppPlugin::instance()->quickFixProvider()->createProcessor()); + QScopedPointer<TextEditor::IAssistProposal> proposal(processor->perform(interface)); + if (!proposal.isNull()) { + TextEditor::BasicProposalItemListModel *model = + static_cast<TextEditor::BasicProposalItemListModel *>(proposal->model()); + for (int index = 0; index < model->size(); ++index) { + TextEditor::BasicProposalItem *item = + static_cast<TextEditor::BasicProposalItem *>(model->proposalItem(index)); + TextEditor::QuickFixOperation::Ptr op = + item->data().value<TextEditor::QuickFixOperation::Ptr>(); + m_quickFixes.append(op); + QAction *action = quickFixMenu->addAction(op->description()); + mapper.setMapping(action, index); + connect(action, SIGNAL(triggered()), &mapper, SLOT(map())); + } + delete model; } } } @@ -1646,7 +1656,6 @@ void CPPEditorWidget::contextMenuEvent(QContextMenuEvent *e) appendStandardContextMenuActions(menu); menu->exec(e->globalPos()); - quickFixCollector->cleanup(); m_quickFixes.clear(); delete menu; } @@ -1921,6 +1930,7 @@ void CPPEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo) } + setExtraSelections(UnusedSymbolSelection, unusedSelections); if (! m_renameSelections.isEmpty()) @@ -2205,4 +2215,32 @@ QVector<QString> CPPEditorWidget::highlighterFormatCategories() return categories; } +TextEditor::IAssistInterface *CPPEditorWidget::createAssistInterface( + TextEditor::AssistKind kind, + TextEditor::AssistReason reason) const +{ + if (kind == TextEditor::Completion) { + QStringList includePaths; + QStringList frameworkPaths; + if (ProjectExplorer::Project *project = + ProjectExplorer::ProjectExplorerPlugin::instance()->currentProject()) { + includePaths = m_modelManager->projectInfo(project).includePaths; + frameworkPaths = m_modelManager->projectInfo(project).frameworkPaths; + } + return new CppTools::Internal::CppCompletionAssistInterface( + document(), + position(), + editor()->file(), + reason, + m_modelManager->snapshot(), + includePaths, + frameworkPaths); + } else if (kind == TextEditor::QuickFix) { + if (!semanticInfo().doc || semanticInfo().revision != editorRevision()) + return 0; + return new CppQuickFixAssistInterface(const_cast<CPPEditorWidget *>(this), reason); + } + return 0; +} + #include "cppeditor.moc" diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 9250ba15bb..7ac1eee8e6 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -34,13 +34,13 @@ #define CPPEDITOR_H #include "cppeditorenums.h" -#include "cppquickfix.h" #include "cppsemanticinfo.h" #include <cplusplus/ModelManagerInterface.h> #include <cplusplus/CppDocument.h> #include <cplusplus/LookupContext.h> #include <texteditor/basetexteditor.h> +#include <texteditor/quickfix.h> #include <QtCore/QThread> #include <QtCore/QMutex> @@ -189,6 +189,9 @@ public: static QVector<QString> highlighterFormatCategories(); + virtual TextEditor::IAssistInterface *createAssistInterface(TextEditor::AssistKind kind, + TextEditor::AssistReason reason) const; + Q_SIGNALS: void outlineModelIndexChanged(const QModelIndex &index); diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 95abbbde12..0431b33ee5 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -14,7 +14,6 @@ HEADERS += cppplugin.h \ cppeditorenums.h \ cppeditor_global.h \ cppclasswizard.h \ - cppquickfix.h \ cppchecksymbols.h \ cppsemanticinfo.h \ cppoutline.h \ @@ -22,12 +21,13 @@ HEADERS += cppplugin.h \ cpplocalsymbols.h \ cpptypehierarchy.h \ cppelementevaluator.h \ - cppquickfixcollector.h \ cppqtstyleindenter.h \ cppautocompleter.h \ cppcompleteswitch.h \ cppsnippetprovider.h \ - cppinsertqtpropertymembers.h + cppinsertqtpropertymembers.h \ + cppquickfixassistant.h \ + cppquickfix.h SOURCES += cppplugin.cpp \ cppeditor.cpp \ @@ -35,7 +35,6 @@ SOURCES += cppplugin.cpp \ cpphoverhandler.cpp \ cppfilewizard.cpp \ cppclasswizard.cpp \ - cppquickfix.cpp \ cppquickfixes.cpp \ cppchecksymbols.cpp \ cppsemanticinfo.cpp \ @@ -44,12 +43,13 @@ SOURCES += cppplugin.cpp \ cpplocalsymbols.cpp \ cpptypehierarchy.cpp \ cppelementevaluator.cpp \ - cppquickfixcollector.cpp \ cppqtstyleindenter.cpp \ cppautocompleter.cpp \ cppcompleteswitch.cpp \ cppsnippetprovider.cpp \ - cppinsertqtpropertymembers.cpp + cppinsertqtpropertymembers.cpp \ + cppquickfixassistant.cpp \ + cppquickfix.cpp RESOURCES += cppeditor.qrc OTHER_FILES += CppEditor.mimetypes.xml diff --git a/src/plugins/cppeditor/cppinsertdecldef.cpp b/src/plugins/cppeditor/cppinsertdecldef.cpp index 10032a9862..019af89a13 100644 --- a/src/plugins/cppeditor/cppinsertdecldef.cpp +++ b/src/plugins/cppeditor/cppinsertdecldef.cpp @@ -31,6 +31,7 @@ **************************************************************************/ #include "cppinsertdecldef.h" +#include "cppquickfixassistant.h" #include <CPlusPlus.h> #include <cplusplus/ASTPath.h> @@ -53,11 +54,12 @@ namespace { class InsertDeclOperation: public CppQuickFixOperation { public: - InsertDeclOperation(const CppQuickFixState &state, int priority, + InsertDeclOperation(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, + int priority, const QString &targetFileName, const Class *targetSymbol, InsertionPointLocator::AccessSpec xsSpec, const QString &decl) - : CppQuickFixOperation(state, priority) + : CppQuickFixOperation(interface, priority) , m_targetFileName(targetFileName) , m_targetSymbol(targetSymbol) , m_xsSpec(xsSpec) @@ -108,10 +110,11 @@ private: } // anonymous namespace -QList<CppQuickFixOperation::Ptr> DeclFromDef::match(const CppQuickFixState &state) +QList<CppQuickFixOperation::Ptr> DeclFromDef::match( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); FunctionDefinitionAST *funDef = 0; int idx = 0; @@ -158,7 +161,7 @@ QList<CppQuickFixOperation::Ptr> DeclFromDef::match(const CppQuickFixState &stat if (!q->base()) return noResult(); - if (ClassOrNamespace *binding = state.context().lookupType(q->base(), enclosingScope)) { + if (ClassOrNamespace *binding = interface->context().lookupType(q->base(), enclosingScope)) { foreach (Symbol *s, binding->symbols()) { if (Class *matchingClass = s->asClass()) { for (Symbol *s = matchingClass->find(q->identifier()); s; s = s->next()) { @@ -177,11 +180,11 @@ QList<CppQuickFixOperation::Ptr> DeclFromDef::match(const CppQuickFixState &stat const QString fn = QString::fromUtf8(matchingClass->fileName(), matchingClass->fileNameLength()); - const QString decl = generateDeclaration(state, + const QString decl = generateDeclaration(interface, method, binding); return singleResult( - new InsertDeclOperation(state, idx, fn, matchingClass, + new InsertDeclOperation(interface, idx, fn, matchingClass, InsertionPointLocator::Public, decl)); } @@ -191,7 +194,7 @@ QList<CppQuickFixOperation::Ptr> DeclFromDef::match(const CppQuickFixState &stat return noResult(); } -QString DeclFromDef::generateDeclaration(const CppQuickFixState &, +QString DeclFromDef::generateDeclaration(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &, Function *method, ClassOrNamespace *targetBinding) { @@ -214,9 +217,9 @@ namespace { class InsertDefOperation: public CppQuickFixOperation { public: - InsertDefOperation(const CppQuickFixState &state, int priority, + InsertDefOperation(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, int priority, Declaration *decl, const InsertionLocation &loc) - : CppQuickFixOperation(state, priority) + : CppQuickFixOperation(interface, priority) , m_decl(decl) , m_loc(loc) { @@ -241,12 +244,12 @@ public: //-- SubstitutionEnvironment env; - env.setContext(state().context()); + env.setContext(assistInterface()->context()); env.switchScope(m_decl->enclosingScope()); UseQualifiedNames q; env.enter(&q); - Control *control = state().context().control().data(); + Control *control = assistInterface()->context().control().data(); FullySpecifiedType tn = rewriteType(m_decl->type(), &env, control); QString name = oo(LookupContext::fullyQualifiedName(m_decl)); //-- @@ -274,10 +277,11 @@ private: } // anonymous namespace -QList<CppQuickFixOperation::Ptr> DefFromDecl::match(const CppQuickFixState &state) +QList<CppQuickFixOperation::Ptr> DefFromDecl::match( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); int idx = path.size() - 1; for (; idx >= 0; --idx) { @@ -292,12 +296,12 @@ QList<CppQuickFixOperation::Ptr> DefFromDecl::match(const CppQuickFixState &stat && decl->enclosingScope()->isClass()) { DeclaratorAST *declarator = simpleDecl->declarator_list->value; if (file.isCursorOn(declarator->core_declarator)) { - CppRefactoringChanges refactoring(state.snapshot()); + CppRefactoringChanges refactoring(interface->snapshot()); InsertionPointLocator locator(&refactoring); QList<CppQuickFixOperation::Ptr> results; foreach (const InsertionLocation &loc, locator.methodDefinition(decl)) { if (loc.isValid()) - results.append(CppQuickFixOperation::Ptr(new InsertDefOperation(state, idx, decl, loc))); + results.append(CppQuickFixOperation::Ptr(new InsertDefOperation(interface, idx, decl, loc))); } return results; } diff --git a/src/plugins/cppeditor/cppinsertdecldef.h b/src/plugins/cppeditor/cppinsertdecldef.h index 61273cb33d..793640bcb8 100644 --- a/src/plugins/cppeditor/cppinsertdecldef.h +++ b/src/plugins/cppeditor/cppinsertdecldef.h @@ -47,10 +47,11 @@ namespace Internal { class DeclFromDef: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state); + virtual QList<CppQuickFixOperation::Ptr> + match(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); protected: - static QString generateDeclaration(const CppQuickFixState &state, + static QString generateDeclaration(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, CPlusPlus::Function *method, CPlusPlus::ClassOrNamespace *targetBinding); }; @@ -58,7 +59,8 @@ protected: class DefFromDecl: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state); + virtual QList<CppQuickFixOperation::Ptr> + match(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppinsertqtpropertymembers.cpp b/src/plugins/cppeditor/cppinsertqtpropertymembers.cpp index 485acecf4e..9945746f06 100644 --- a/src/plugins/cppeditor/cppinsertqtpropertymembers.cpp +++ b/src/plugins/cppeditor/cppinsertqtpropertymembers.cpp @@ -31,6 +31,7 @@ **************************************************************************/ #include "cppinsertqtpropertymembers.h" +#include "cppquickfixassistant.h" #include <AST.h> #include <Token.h> @@ -38,6 +39,7 @@ #include <cpptools/insertionpointlocator.h> #include <cpptools/cpprefactoringchanges.h> #include <cppeditor/cppquickfix.h> +#include <coreplugin/ifile.h> using namespace CPlusPlus; using namespace CppTools; @@ -46,9 +48,10 @@ using namespace Utils; using namespace CppEditor; using namespace CppEditor::Internal; -QList<CppQuickFixOperation::Ptr> InsertQtPropertyMembers::match(const CppQuickFixState &state) +QList<CppQuickFixOperation::Ptr> InsertQtPropertyMembers::match( + const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); if (path.isEmpty()) return noResult(); @@ -67,8 +70,8 @@ QList<CppQuickFixOperation::Ptr> InsertQtPropertyMembers::match(const CppQuickFi if (!klass) return noResult(); - CppRefactoringChanges refactoring(state.snapshot()); - const CppRefactoringFile &file = refactoring.file(state.document()->fileName()); + CppRefactoringChanges refactoring(interface->snapshot()); + const CppRefactoringFile &file = refactoring.file(interface->file()->fileName()); const QString propertyName = file.textOf(qtPropertyDeclaration->property_name); QString getterName; QString setterName; @@ -116,16 +119,17 @@ QList<CppQuickFixOperation::Ptr> InsertQtPropertyMembers::match(const CppQuickFi if (getterName.isEmpty() && setterName.isEmpty() && signalName.isEmpty()) return noResult(); - return singleResult(new Operation(state, path.size() - 1, qtPropertyDeclaration, c, + return singleResult(new Operation(interface, path.size() - 1, qtPropertyDeclaration, c, generateFlags, getterName, setterName, signalName, storageName)); } InsertQtPropertyMembers::Operation::Operation( - const CppQuickFixState &state, int priority, QtPropertyDeclarationAST *declaration, Class *klass, + const QSharedPointer<const CppQuickFixAssistInterface> &interface, + int priority, QtPropertyDeclarationAST *declaration, Class *klass, int generateFlags, const QString &getterName, const QString &setterName, const QString &signalName, const QString &storageName) - : CppQuickFixOperation(state, priority) + : CppQuickFixOperation(interface, priority) , m_declaration(declaration) , m_class(klass) , m_generateFlags(generateFlags) diff --git a/src/plugins/cppeditor/cppinsertqtpropertymembers.h b/src/plugins/cppeditor/cppinsertqtpropertymembers.h index 4a36a0a6e6..785ac87ba1 100644 --- a/src/plugins/cppeditor/cppinsertqtpropertymembers.h +++ b/src/plugins/cppeditor/cppinsertqtpropertymembers.h @@ -62,7 +62,8 @@ class InsertQtPropertyMembers : public CppQuickFixFactory Q_OBJECT public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state); + virtual QList<CppQuickFixOperation::Ptr> + match(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface); private: enum GenerateFlag { @@ -75,7 +76,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, + Operation(const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, + int priority, CPlusPlus::QtPropertyDeclarationAST *declaration, CPlusPlus::Class *klass, int generateFlags, const QString &getterName, const QString &setterName, const QString &signalName, diff --git a/src/plugins/cppeditor/cppplugin.cpp b/src/plugins/cppeditor/cppplugin.cpp index 22b60e21d9..2612fd01e6 100644 --- a/src/plugins/cppeditor/cppplugin.cpp +++ b/src/plugins/cppeditor/cppplugin.cpp @@ -37,11 +37,10 @@ #include "cppeditorenums.h" #include "cppfilewizard.h" #include "cpphoverhandler.h" -#include "cppquickfix.h" #include "cppoutline.h" -#include "cppquickfixcollector.h" #include "cpptypehierarchy.h" #include "cppsnippetprovider.h" +#include "cppquickfixassistant.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -54,7 +53,6 @@ #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/progressmanager/progressmanager.h> #include <coreplugin/navigationwidget.h> -#include <texteditor/completionsupport.h> #include <texteditor/fontsettings.h> #include <texteditor/storagesettings.h> #include <texteditor/texteditoractionhandler.h> @@ -75,6 +73,8 @@ using namespace CppEditor; using namespace CppEditor::Internal; +void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); + enum { QUICKFIX_INTERVAL = 20 }; //////////////////////////// CppEditorFactory ///////////////////////////// @@ -149,15 +149,10 @@ CppPlugin::CppPlugin() : m_renameSymbolUnderCursorAction(0), m_findUsagesAction(0), m_updateCodeModelAction(0), - m_openTypeHierarchyAction(0) + m_openTypeHierarchyAction(0), + m_quickFixProvider(0) { m_instance = this; - - m_quickFixCollector = 0; - m_quickFixTimer = new QTimer(this); - m_quickFixTimer->setInterval(20); - m_quickFixTimer->setSingleShot(true); - connect(m_quickFixTimer, SIGNAL(timeout()), this, SLOT(quickFixNow())); } CppPlugin::~CppPlugin() @@ -193,8 +188,10 @@ bool CppPlugin::sortedOutline() const return m_sortedOutline; } -CppQuickFixCollector *CppPlugin::quickFixCollector() const -{ return m_quickFixCollector; } +CppQuickFixAssistProvider *CppPlugin::quickFixProvider() const +{ + return m_quickFixProvider; +} bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMessage) { @@ -209,9 +206,9 @@ bool CppPlugin::initialize(const QStringList & /*arguments*/, QString *errorMess addAutoReleasedObject(new CppTypeHierarchyFactory); addAutoReleasedObject(new CppSnippetProvider); - m_quickFixCollector = new CppQuickFixCollector; - addAutoReleasedObject(m_quickFixCollector); - CppQuickFixCollector::registerQuickFixes(this); + m_quickFixProvider = new CppQuickFixAssistProvider; + addAutoReleasedObject(m_quickFixProvider); + registerQuickFixes(this); CppFileWizard::BaseFileWizardParameters wizardParameters(Core::IWizard::FileWizard); @@ -380,31 +377,6 @@ void CppPlugin::findUsages() editor->findUsages(); } -void CppPlugin::quickFix(TextEditor::ITextEditor *editor) -{ - m_currentEditor = editor; - quickFixNow(); -} - -void CppPlugin::quickFixNow() -{ - if (! m_currentEditor) - return; - - Core::EditorManager *em = Core::EditorManager::instance(); - CPPEditorWidget *currentEditor = qobject_cast<CPPEditorWidget*>(em->currentEditor()->widget()); - - if (CPPEditorWidget *editor = qobject_cast<CPPEditorWidget*>(m_currentEditor->widget())) { - if (currentEditor == editor) { - if (editor->isOutdated()) - m_quickFixTimer->start(QUICKFIX_INTERVAL); - else - TextEditor::CompletionSupport::instance()-> - complete(m_currentEditor, TextEditor::QuickFixCompletion, true); - } - } -} - void CppPlugin::onTaskStarted(const QString &type) { if (type == CppTools::Constants::TASK_INDEX) { diff --git a/src/plugins/cppeditor/cppplugin.h b/src/plugins/cppeditor/cppplugin.h index 5529de18d6..9a0946d2e4 100644 --- a/src/plugins/cppeditor/cppplugin.h +++ b/src/plugins/cppeditor/cppplugin.h @@ -50,6 +50,7 @@ namespace Internal { class CPPEditorWidget; class CppQuickFixCollector; +class CppQuickFixAssistProvider; class CppPlugin : public ExtensionSystem::IPlugin { @@ -70,7 +71,7 @@ public: bool sortedOutline() const; - CppQuickFixCollector *quickFixCollector() const; + CppQuickFixAssistProvider *quickFixProvider() const; signals: void outlineSortingChanged(bool sort); @@ -86,8 +87,6 @@ private slots: void onTaskStarted(const QString &type); void onAllTasksFinished(const QString &type); void findUsages(); - void quickFix(TextEditor::ITextEditor *editable); - void quickFixNow(); void currentEditorChanged(Core::IEditor *editor); void openTypeHierarchy(); @@ -105,9 +104,8 @@ private: QAction *m_updateCodeModelAction; QAction *m_openTypeHierarchyAction; - CppQuickFixCollector *m_quickFixCollector; + CppQuickFixAssistProvider *m_quickFixProvider; - QTimer *m_quickFixTimer; QPointer<TextEditor::ITextEditor> m_currentEditor; }; diff --git a/src/plugins/cppeditor/cppquickfix.cpp b/src/plugins/cppeditor/cppquickfix.cpp index cec45fd890..ae63fb43ed 100644 --- a/src/plugins/cppeditor/cppquickfix.cpp +++ b/src/plugins/cppeditor/cppquickfix.cpp @@ -32,7 +32,7 @@ #include "cppquickfix.h" #include "cppeditor.h" -#include "cppquickfixcollector.h" +#include "cppquickfixassistant.h" #include <AST.h> #include <TranslationUnit.h> @@ -58,53 +58,10 @@ using namespace TextEditor; using namespace CPlusPlus; using namespace Utils; -CppQuickFixState::CppQuickFixState(TextEditor::BaseTextEditorWidget *editor) - : QuickFixState(editor) -{} - -const QList<AST *> &CppQuickFixState::path() const -{ - return _path; -} - -Snapshot CppQuickFixState::snapshot() const -{ - return _snapshot; -} - -Document::Ptr CppQuickFixState::document() const -{ - return _semanticInfo.doc; -} - -SemanticInfo CppQuickFixState::semanticInfo() const -{ - return _semanticInfo; -} - -const LookupContext &CppQuickFixState::context() const -{ - return _context; -} - -const CppRefactoringFile CppQuickFixState::currentFile() const -{ - return CppRefactoringFile(editor(), document()); -} - -bool CppQuickFixState::isCursorOn(unsigned tokenIndex) const -{ - return currentFile().isCursorOn(tokenIndex); -} - -bool CppQuickFixState::isCursorOn(const CPlusPlus::AST *ast) const -{ - return currentFile().isCursorOn(ast); -} - -CppQuickFixOperation::CppQuickFixOperation(const CppQuickFixState &state, int priority) +CppQuickFixOperation::CppQuickFixOperation( + const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority) : QuickFixOperation(priority) - , _state(state) + , m_interface(interface) {} CppQuickFixOperation::~CppQuickFixOperation() @@ -112,19 +69,21 @@ CppQuickFixOperation::~CppQuickFixOperation() void CppQuickFixOperation::perform() { - CppRefactoringChanges refactoring(_state.snapshot()); + CppRefactoringChanges refactoring(m_interface->snapshot()); CppRefactoringFile current = refactoring.file(fileName()); performChanges(¤t, &refactoring); } -const CppQuickFixState &CppQuickFixOperation::state() const +const CppQuickFixAssistInterface *CppQuickFixOperation::assistInterface() const { - return _state; + return m_interface.data(); } QString CppQuickFixOperation::fileName() const -{ return state().document()->fileName(); } +{ + return m_interface->file()->fileName(); +} CppQuickFixFactory::CppQuickFixFactory() { @@ -134,12 +93,14 @@ CppQuickFixFactory::~CppQuickFixFactory() { } -QList<QuickFixOperation::Ptr> CppQuickFixFactory::matchingOperations(QuickFixState *state) +QList<QuickFixOperation::Ptr> CppQuickFixFactory::matchingOperations( + const QSharedPointer<const TextEditor::IAssistInterface> &interface) { - if (CppQuickFixState *cppState = static_cast<CppQuickFixState *>(state)) - return match(*cppState); - else - return QList<TextEditor::QuickFixOperation::Ptr>(); + QSharedPointer<const CppQuickFixAssistInterface> cppInterface = + interface.staticCast<const CppQuickFixAssistInterface>(); + if (cppInterface->path().isEmpty()) + return QList<QuickFixOperation::Ptr>(); + return match(cppInterface); } QList<CppQuickFixOperation::Ptr> CppQuickFixFactory::singleResult(CppQuickFixOperation *operation) diff --git a/src/plugins/cppeditor/cppquickfix.h b/src/plugins/cppeditor/cppquickfix.h index 034ab9e2f5..71da104263 100644 --- a/src/plugins/cppeditor/cppquickfix.h +++ b/src/plugins/cppeditor/cppquickfix.h @@ -53,40 +53,17 @@ class IPlugin; namespace CppEditor { namespace Internal { -class CppQuickFixCollector; -} // namespace Internal - -class CPPEDITOR_EXPORT CppQuickFixState: public TextEditor::QuickFixState -{ - friend class Internal::CppQuickFixCollector; - -public: - CppQuickFixState(TextEditor::BaseTextEditorWidget *editor); - - const QList<CPlusPlus::AST *> &path() const; - CPlusPlus::Snapshot snapshot() const; - CPlusPlus::Document::Ptr document() const; - CppEditor::Internal::SemanticInfo semanticInfo() const; - const CPlusPlus::LookupContext &context() const; - - const CppTools::CppRefactoringFile currentFile() const; - - bool isCursorOn(unsigned tokenIndex) const; - bool isCursorOn(const CPlusPlus::AST *ast) const; - -private: - QList<CPlusPlus::AST *> _path; - CPlusPlus::Snapshot _snapshot; - CppEditor::Internal::SemanticInfo _semanticInfo; - CPlusPlus::LookupContext _context; -}; +class CppQuickFixAssistInterface; +} class CPPEDITOR_EXPORT CppQuickFixOperation: public TextEditor::QuickFixOperation { Q_DISABLE_COPY(CppQuickFixOperation) public: - explicit CppQuickFixOperation(const CppQuickFixState &state, int priority = -1); + explicit CppQuickFixOperation( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface, + int priority = -1); virtual ~CppQuickFixOperation(); virtual void perform(); @@ -97,10 +74,10 @@ protected: QString fileName() const; - const CppQuickFixState &state() const; + const Internal::CppQuickFixAssistInterface *assistInterface() const; private: - CppQuickFixState _state; + QSharedPointer<const Internal::CppQuickFixAssistInterface> m_interface; }; class CPPEDITOR_EXPORT CppQuickFixFactory: public TextEditor::QuickFixFactory @@ -111,12 +88,15 @@ public: CppQuickFixFactory(); virtual ~CppQuickFixFactory(); - virtual QList<TextEditor::QuickFixOperation::Ptr> matchingOperations(TextEditor::QuickFixState *state); + virtual QList<TextEditor::QuickFixOperation::Ptr> + matchingOperations(const QSharedPointer<const TextEditor::IAssistInterface> &interface); + /*! Implement this method to match and create the appropriate CppQuickFixOperation objects. */ - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) = 0; + virtual QList<CppQuickFixOperation::Ptr> match( + const QSharedPointer<const Internal::CppQuickFixAssistInterface> &interface) = 0; protected: /*! diff --git a/src/plugins/cppeditor/cppquickfixassistant.cpp b/src/plugins/cppeditor/cppquickfixassistant.cpp new file mode 100644 index 0000000000..9b998c2db3 --- /dev/null +++ b/src/plugins/cppeditor/cppquickfixassistant.cpp @@ -0,0 +1,152 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "cppquickfixassistant.h" +#include "cppeditorconstants.h" +#include "cppeditor.h" + +// @TODO: temp +#include "cppquickfix.h" + +#include <AST.h> +#include <TranslationUnit.h> +#include <Token.h> + +#include <cplusplus/ASTPath.h> +#include <cplusplus/CppDocument.h> +#include <cplusplus/ResolveExpression.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/DependencyTable.h> +#include <cplusplus/CppRewriter.h> + +#include <cpptools/cpprefactoringchanges.h> + +#include <extensionsystem/pluginmanager.h> + +#include <QtCore/QFileInfo> +#include <QtGui/QTextBlock> + +using namespace CppEditor; +using namespace CppEditor::Internal; +using namespace TextEditor; +using namespace CppTools; +using namespace CPlusPlus; + +// ------------------------- +// CppQuickFixAssistProvider +// ------------------------- +bool CppQuickFixAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(CppEditor::Constants::CPPEDITOR_ID); +} + +IAssistProcessor *CppQuickFixAssistProvider::createProcessor() const +{ + return new CppQuickFixAssistProcessor(this); +} + +QList<TextEditor::QuickFixFactory *> CppQuickFixAssistProvider::quickFixFactories() const +{ + QList<TextEditor::QuickFixFactory *> results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (CppQuickFixFactory *f, pm->getObjects<CppEditor::CppQuickFixFactory>()) + results.append(f); + return results; +} + +// -------------------------- +// CppQuickFixAssistProcessor +// -------------------------- +CppQuickFixAssistProcessor::CppQuickFixAssistProcessor(const IAssistProvider *provider) + : m_provider(provider) +{} + +const IAssistProvider *CppQuickFixAssistProcessor::provider() const +{ + return m_provider; +} + +// -------------------------- +// CppQuickFixAssistInterface +// -------------------------- +CppQuickFixAssistInterface::CppQuickFixAssistInterface(CPPEditorWidget *editor, + TextEditor::AssistReason reason) + : DefaultAssistInterface(editor->document(), editor->position(), editor->file(), reason) + , m_editor(editor) + , m_semanticInfo(editor->semanticInfo()) + , m_snapshot(CPlusPlus::CppModelManagerInterface::instance()->snapshot()) + , m_context(m_semanticInfo.doc, m_snapshot) +{ + CPlusPlus::ASTPath astPath(m_semanticInfo.doc); + m_path = astPath(editor->textCursor()); +} + +const QList<AST *> &CppQuickFixAssistInterface::path() const +{ + return m_path; +} + +Snapshot CppQuickFixAssistInterface::snapshot() const +{ + return m_snapshot; +} + +SemanticInfo CppQuickFixAssistInterface::semanticInfo() const +{ + return m_semanticInfo; +} + +const LookupContext &CppQuickFixAssistInterface::context() const +{ + return m_context; +} + +CPPEditorWidget *CppQuickFixAssistInterface::editor() const +{ + return m_editor; +} + +const CppRefactoringFile CppQuickFixAssistInterface::currentFile() const +{ + return CppRefactoringFile(m_editor, m_semanticInfo.doc); +} + +bool CppQuickFixAssistInterface::isCursorOn(unsigned tokenIndex) const +{ + return currentFile().isCursorOn(tokenIndex); +} + +bool CppQuickFixAssistInterface::isCursorOn(const CPlusPlus::AST *ast) const +{ + return currentFile().isCursorOn(ast); +} diff --git a/src/plugins/cppeditor/cppquickfixassistant.h b/src/plugins/cppeditor/cppquickfixassistant.h new file mode 100644 index 0000000000..f08a9ea610 --- /dev/null +++ b/src/plugins/cppeditor/cppquickfixassistant.h @@ -0,0 +1,101 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef CPPQUICKFIXASSISTANT_H +#define CPPQUICKFIXASSISTANT_H + +#include "cppsemanticinfo.h" + +#include <ASTfwd.h> +#include <cplusplus/CppDocument.h> + +#include <texteditor/codeassist/defaultassistinterface.h> +#include <texteditor/codeassist/quickfixassistprovider.h> +#include <texteditor/codeassist/quickfixassistprocessor.h> + +namespace CppTools { +class CppRefactoringFile; +} + +namespace CppEditor { +namespace Internal { + +class CPPEditorWidget; + +class CppQuickFixAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + CppQuickFixAssistInterface(CPPEditorWidget *editor, TextEditor::AssistReason reason); + + const QList<CPlusPlus::AST *> &path() const; + CPlusPlus::Snapshot snapshot() const; + CppEditor::Internal::SemanticInfo semanticInfo() const; + const CPlusPlus::LookupContext &context() const; + CPPEditorWidget *editor() const; + + const CppTools::CppRefactoringFile currentFile() const; + + bool isCursorOn(unsigned tokenIndex) const; + bool isCursorOn(const CPlusPlus::AST *ast) const; + +private: + CPPEditorWidget *m_editor; + CppEditor::Internal::SemanticInfo m_semanticInfo; + CPlusPlus::Snapshot m_snapshot; + CPlusPlus::LookupContext m_context; + QList<CPlusPlus::AST *> m_path; +}; + +class CppQuickFixAssistProcessor : public TextEditor::QuickFixAssistProcessor +{ +public: + CppQuickFixAssistProcessor(const TextEditor::IAssistProvider *provider); + + virtual const TextEditor::IAssistProvider *provider() const; + +private: + const TextEditor::IAssistProvider *m_provider; +}; + +class CppQuickFixAssistProvider : public TextEditor::QuickFixAssistProvider +{ +public: + virtual bool supportsEditor(const QString &editorId) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; + + virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; +}; + +} // Internal +} // CppEditor + +#endif // CPPQUICKFIXASSISTANT_H diff --git a/src/plugins/cppeditor/cppquickfixcollector.cpp b/src/plugins/cppeditor/cppquickfixcollector.cpp deleted file mode 100644 index a48f832d85..0000000000 --- a/src/plugins/cppeditor/cppquickfixcollector.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "cppquickfixcollector.h" -#include "cppeditor.h" - -#include <extensionsystem/pluginmanager.h> - -#include <cplusplus/ModelManagerInterface.h> -#include <cpptools/cpprefactoringchanges.h> -#include <cpptools/cpptoolsconstants.h> - -#include <AST.h> -#include <cplusplus/ASTPath.h> - -namespace CppEditor { -namespace Internal { - -CppQuickFixCollector::CppQuickFixCollector() -{ -} - -CppQuickFixCollector::~CppQuickFixCollector() -{ -} - -bool CppQuickFixCollector::supportsEditor(TextEditor::ITextEditor *editor) const -{ - return CPlusPlus::CppModelManagerInterface::instance()->isCppEditor(editor); -} - -TextEditor::QuickFixState *CppQuickFixCollector::initializeCompletion(TextEditor::BaseTextEditorWidget *editor) -{ - if (CPPEditorWidget *cppEditor = qobject_cast<CPPEditorWidget *>(editor)) { - const SemanticInfo info = cppEditor->semanticInfo(); - - if (info.revision != cppEditor->editorRevision()) { - // outdated - qWarning() << "TODO: outdated semantic info, force a reparse."; - return 0; - } - - if (info.doc) { - CPlusPlus::ASTPath astPath(info.doc); - - const QList<CPlusPlus::AST *> path = astPath(cppEditor->textCursor()); - if (! path.isEmpty()) { - CppQuickFixState *state = new CppQuickFixState(editor); - state->_path = path; - state->_semanticInfo = info; - state->_snapshot = CPlusPlus::CppModelManagerInterface::instance()->snapshot(); - state->_context = CPlusPlus::LookupContext(info.doc, state->snapshot()); - return state; - } - } - } - - return 0; -} - -QList<TextEditor::QuickFixFactory *> CppQuickFixCollector::quickFixFactories() const -{ - QList<TextEditor::QuickFixFactory *> results; - ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); - foreach (CppQuickFixFactory *f, pm->getObjects<CppEditor::CppQuickFixFactory>()) - results.append(f); - return results; -} - -} // namespace Internal -} // namespace CppEditor diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index fa7da44fff..1f76416755 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -35,7 +35,8 @@ #include "cppquickfix.h" #include "cppinsertdecldef.h" #include "cppinsertqtpropertymembers.h" -#include "cppquickfixcollector.h" +#include "cppquickfixassistant.h" +#include "cppcompleteswitch.h" #include <ASTVisitor.h> #include <AST.h> @@ -85,17 +86,17 @@ namespace { class UseInverseOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { QList<CppQuickFixOperation::Ptr> result; - const CppRefactoringFile &file = state.currentFile(); + const CppRefactoringFile &file = interface->currentFile(); - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); int index = path.size() - 1; BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); if (! binary) return result; - if (! state.isCursorOn(binary->binary_op_token)) + if (! interface->isCursorOn(binary->binary_op_token)) return result; Kind invertToken; @@ -122,7 +123,7 @@ public: return result; } - result.append(CppQuickFixOperation::Ptr(new Operation(state, index, binary, invertToken))); + result.append(CppQuickFixOperation::Ptr(new Operation(interface, index, binary, invertToken))); return result; } @@ -136,8 +137,9 @@ private: QString replacement; public: - Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binary, Kind invertToken) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, + int priority, BinaryExpressionAST *binary, Kind invertToken) + : CppQuickFixOperation(interface, priority) , binary(binary), nested(0), negation(0) { Token tok; @@ -146,12 +148,12 @@ private: // check for enclosing nested expression if (priority - 1 >= 0) - nested = state.path()[priority - 1]->asNestedExpression(); + nested = interface->path()[priority - 1]->asNestedExpression(); // check for ! before parentheses if (nested && priority - 2 >= 0) { - negation = state.path()[priority - 2]->asUnaryExpression(); - if (negation && ! state.currentFile().tokenAt(negation->unary_op_token).is(T_EXCLAIM)) + negation = interface->path()[priority - 2]->asUnaryExpression(); + if (negation && ! interface->currentFile().tokenAt(negation->unary_op_token).is(T_EXCLAIM)) negation = 0; } } @@ -191,17 +193,17 @@ private: class FlipBinaryOp: public CppQuickFixFactory { public: - virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<QuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { QList<QuickFixOperation::Ptr> result; - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); int index = path.size() - 1; BinaryExpressionAST *binary = path.at(index)->asBinaryExpression(); if (! binary) return result; - if (! state.isCursorOn(binary->binary_op_token)) + if (! interface->isCursorOn(binary->binary_op_token)) return result; Kind flipToken; @@ -235,7 +237,7 @@ public: replacement = QLatin1String(tok.spell()); } - result.append(QuickFixOperation::Ptr(new Operation(state, index, binary, replacement))); + result.append(QuickFixOperation::Ptr(new Operation(interface, index, binary, replacement))); return result; } @@ -243,8 +245,9 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binary, QString replacement) - : CppQuickFixOperation(state) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, + int priority, BinaryExpressionAST *binary, QString replacement) + : CppQuickFixOperation(interface) , binary(binary) , replacement(replacement) { @@ -288,12 +291,12 @@ private: class RewriteLogicalAndOp: public CppQuickFixFactory { public: - virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<QuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { QList<QuickFixOperation::Ptr> result; BinaryExpressionAST *expression = 0; - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); int index = path.size() - 1; for (; index != -1; --index) { @@ -305,10 +308,10 @@ public: if (! expression) return result; - if (! state.isCursorOn(expression->binary_op_token)) + if (! interface->isCursorOn(expression->binary_op_token)) return result; - QSharedPointer<Operation> op(new Operation(state)); + QSharedPointer<Operation> op(new Operation(interface)); if (expression->match(op->pattern, &matcher) && file.tokenAt(op->pattern->binary_op_token).is(T_AMPER_AMPER) && @@ -331,8 +334,8 @@ private: UnaryExpressionAST *right; BinaryExpressionAST *pattern; - Operation(const CppQuickFixState &state) - : CppQuickFixOperation(state) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface) + : CppQuickFixOperation(interface) , mk(new ASTPatternBuilder) { left = mk->UnaryExpression(); @@ -400,12 +403,12 @@ class SplitSimpleDeclarationOp: public CppQuickFixFactory } public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { QList<CppQuickFixOperation::Ptr> result; CoreDeclaratorAST *core_declarator = 0; - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); for (int index = path.size() - 1; index != -1; --index) { AST *node = path.at(index); @@ -424,12 +427,12 @@ public: if (cursorPosition >= startOfDeclSpecifier && cursorPosition <= endOfDeclSpecifier) { // the AST node under cursor is a specifier. - return singleResult(new Operation(state, index, declaration)); + return singleResult(new Operation(interface, index, declaration)); } - if (core_declarator && state.isCursorOn(core_declarator)) { + if (core_declarator && interface->isCursorOn(core_declarator)) { // got a core-declarator under the text cursor. - return singleResult(new Operation(state, index, declaration)); + return singleResult(new Operation(interface, index, declaration)); } } @@ -444,8 +447,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, SimpleDeclarationAST *decl) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, SimpleDeclarationAST *decl) + : CppQuickFixOperation(interface, priority) , declaration(decl) { setDescription(QApplication::translate("CppTools::QuickFix", @@ -503,16 +506,16 @@ private: class AddBracesToIfOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->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 + if (ifStatement && interface->isCursorOn(ifStatement->if_token) && ifStatement->statement && ! ifStatement->statement->asCompoundStatement()) { - return singleResult(new Operation(state, index, ifStatement->statement)); + return singleResult(new Operation(interface, index, ifStatement->statement)); } // or if we're on the statement contained in the if @@ -520,9 +523,9 @@ public: for (; index != -1; --index) { IfStatementAST *ifStatement = path.at(index)->asIfStatement(); if (ifStatement && ifStatement->statement - && state.isCursorOn(ifStatement->statement) + && interface->isCursorOn(ifStatement->statement) && ! ifStatement->statement->asCompoundStatement()) { - return singleResult(new Operation(state, index, ifStatement->statement)); + return singleResult(new Operation(interface, index, ifStatement->statement)); } } @@ -536,8 +539,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, StatementAST *statement) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, StatementAST *statement) + : CppQuickFixOperation(interface, priority) , _statement(statement) { setDescription(QApplication::translate("CppTools::QuickFix", @@ -576,10 +579,10 @@ private: class MoveDeclarationOutOfIfOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); - QSharedPointer<Operation> op(new Operation(state)); + const QList<AST *> &path = interface->path(); + QSharedPointer<Operation> op(new Operation(interface)); int index = path.size() - 1; for (; index != -1; --index) { @@ -590,7 +593,7 @@ public: if (! op->core) return noResult(); - if (state.isCursorOn(op->core)) { + if (interface->isCursorOn(op->core)) { QList<CppQuickFixOperation::Ptr> result; op->setPriority(index); result.append(op); @@ -607,8 +610,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state) - : CppQuickFixOperation(state) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface) + : CppQuickFixOperation(interface) { setDescription(QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition")); @@ -653,10 +656,10 @@ private: class MoveDeclarationOutOfWhileOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); - QSharedPointer<Operation> op(new Operation(state)); + const QList<AST *> &path = interface->path(); + QSharedPointer<Operation> op(new Operation(interface)); int index = path.size() - 1; for (; index != -1; --index) { @@ -674,7 +677,7 @@ public: else if (! declarator->initializer) return noResult(); - if (state.isCursorOn(op->core)) { + if (interface->isCursorOn(op->core)) { QList<CppQuickFixOperation::Ptr> result; op->setPriority(index); result.append(op); @@ -691,8 +694,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state) - : CppQuickFixOperation(state) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface) + : CppQuickFixOperation(interface) { setDescription(QApplication::translate("CppTools::QuickFix", "Move Declaration out of Condition")); @@ -753,10 +756,10 @@ private: class SplitIfStatementOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { IfStatementAST *pattern = 0; - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); int index = path.size() - 1; for (; index != -1; --index) { @@ -777,7 +780,7 @@ public: if (! condition) return noResult(); - Token binaryToken = state.currentFile().tokenAt(condition->binary_op_token); + Token binaryToken = interface->currentFile().tokenAt(condition->binary_op_token); // only accept a chain of ||s or &&s - no mixing if (! splitKind) { @@ -791,8 +794,8 @@ public: return noResult(); } - if (state.isCursorOn(condition->binary_op_token)) - return singleResult(new Operation(state, index, pattern, condition)); + if (interface->isCursorOn(condition->binary_op_token)) + return singleResult(new Operation(interface, index, pattern, condition)); } return noResult(); @@ -802,9 +805,9 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, IfStatementAST *pattern, BinaryExpressionAST *condition) - : CppQuickFixOperation(state, priority) + : CppQuickFixOperation(interface, priority) , pattern(pattern) , condition(condition) { @@ -889,12 +892,12 @@ class WrapStringLiteral: public CppQuickFixFactory public: enum Type { TypeString, TypeObjCString, TypeChar, TypeNone }; - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { ExpressionAST *literal = 0; Type type = TypeNone; - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); if (path.isEmpty()) return noResult(); // nothing to do @@ -932,7 +935,7 @@ public: if (file.charAt(file.startOf(literal)) == QLatin1Char('@')) type = TypeObjCString; } - return singleResult(new Operation(state, + return singleResult(new Operation(interface, path.size() - 1, // very high priority type, literal)); @@ -942,9 +945,9 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, Type type, + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, Type type, ExpressionAST *literal) - : CppQuickFixOperation(state, priority) + : CppQuickFixOperation(interface, priority) , type(type) , literal(literal) { @@ -996,9 +999,9 @@ class TranslateStringLiteral: public CppQuickFixFactory public: enum TranslationOption { unknown, useTr, useQCoreApplicationTranslate, useMacro }; - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); // Initialize ExpressionAST *literal = 0; QString trContext; @@ -1016,7 +1019,7 @@ public: if (call->base_expression) { if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) { if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) { - const QByteArray id(state.currentFile().tokenAt(functionName->identifier_token).identifier->chars()); + const QByteArray id(interface->currentFile().tokenAt(functionName->identifier_token).identifier->chars()); if (id == "tr" || id == "trUtf8" || id == "translate" @@ -1029,7 +1032,7 @@ public: } } - QSharedPointer<Control> control = state.context().control(); + QSharedPointer<Control> control = interface->context().control(); const Name *trName = control->identifier("tr"); // Check whether we are in a method: @@ -1037,14 +1040,14 @@ public: { if (FunctionDefinitionAST *definition = path.at(i)->asFunctionDefinition()) { Function *function = definition->symbol; - ClassOrNamespace *b = state.context().lookupType(function); + ClassOrNamespace *b = interface->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)); + return singleResult(new Operation(interface, path.size() - 1, literal, useTr, trContext)); } } } @@ -1059,20 +1062,20 @@ public: // ... or global if none available! if (trContext.isEmpty()) trContext = QLatin1String("GLOBAL"); - return singleResult(new Operation(state, path.size() - 1, literal, useQCoreApplicationTranslate, trContext)); + return singleResult(new Operation(interface, 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"))); + return singleResult(new Operation(interface, 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) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, ExpressionAST *literal, TranslationOption option, const QString &context) + : CppQuickFixOperation(interface, priority) , m_literal(literal) , m_option(option) , m_context(context) @@ -1120,16 +1123,16 @@ private: class CStringToNSString: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const CppRefactoringFile &file = state.currentFile(); + const CppRefactoringFile &file = interface->currentFile(); - if (state.editor()->mimeType() != CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE) + if (interface->editor()->mimeType() != CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE) return noResult(); StringLiteralAST *stringLiteral = 0; CallAST *qlatin1Call = 0; - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); if (path.isEmpty()) return noResult(); // nothing to do @@ -1147,7 +1150,7 @@ public: if (call->base_expression) { if (IdExpressionAST *idExpr = call->base_expression->asIdExpression()) { if (SimpleNameAST *functionName = idExpr->name->asSimpleName()) { - const QByteArray id(state.currentFile().tokenAt(functionName->identifier_token).identifier->chars()); + const QByteArray id(interface->currentFile().tokenAt(functionName->identifier_token).identifier->chars()); if (id == "QLatin1String" || id == "QLatin1Literal") qlatin1Call = call; @@ -1157,15 +1160,15 @@ public: } } - return singleResult(new Operation(state, path.size() - 1, stringLiteral, qlatin1Call)); + return singleResult(new Operation(interface, path.size() - 1, stringLiteral, qlatin1Call)); } private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, StringLiteralAST *stringLiteral, CallAST *qlatin1Call) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, StringLiteralAST *stringLiteral, CallAST *qlatin1Call) + : CppQuickFixOperation(interface, priority) , stringLiteral(stringLiteral) , qlatin1Call(qlatin1Call) { @@ -1214,12 +1217,12 @@ private: class ConvertNumericLiteral: public CppQuickFixFactory { public: - virtual QList<QuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<QuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { QList<QuickFixOperation::Ptr> result; - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); if (path.isEmpty()) return result; // nothing to do @@ -1266,7 +1269,7 @@ public: */ QString replacement; replacement.sprintf("0x%lX", value); - QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + QuickFixOperation::Ptr op(new ConvertNumeric(interface, start, start + numberLength, replacement)); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Hexadecimal")); op->setPriority(priority); result.append(op); @@ -1284,7 +1287,7 @@ public: */ QString replacement; replacement.sprintf("0%lo", value); - QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + QuickFixOperation::Ptr op(new ConvertNumeric(interface, start, start + numberLength, replacement)); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Octal")); op->setPriority(priority); result.append(op); @@ -1303,7 +1306,7 @@ public: */ QString replacement; replacement.sprintf("%lu", value); - QuickFixOperation::Ptr op(new ConvertNumeric(state, start, start + numberLength, replacement)); + QuickFixOperation::Ptr op(new ConvertNumeric(interface, start, start + numberLength, replacement)); op->setDescription(QApplication::translate("CppTools::QuickFix", "Convert to Decimal")); op->setPriority(priority); result.append(op); @@ -1317,8 +1320,9 @@ private: class ConvertNumeric: public CppQuickFixOperation { public: - ConvertNumeric(const CppQuickFixState &state, int start, int end, const QString &replacement) - : CppQuickFixOperation(state) + ConvertNumeric(const QSharedPointer<const CppQuickFixAssistInterface> &interface, + int start, int end, const QString &replacement) + : CppQuickFixOperation(interface) , start(start) , end(end) , replacement(replacement) @@ -1345,18 +1349,18 @@ private: class FixForwardDeclarationOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->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)); + if (Symbol *fwdClass = checkName(interface, namedTy->name)) + return singleResult(new Operation(interface, index, fwdClass)); } else if (ElaboratedTypeSpecifierAST *eTy = ast->asElaboratedTypeSpecifier()) { - if (Symbol *fwdClass = checkName(state, eTy->name)) - return singleResult(new Operation(state, index, fwdClass)); + if (Symbol *fwdClass = checkName(interface, eTy->name)) + return singleResult(new Operation(interface, index, fwdClass)); } } @@ -1364,16 +1368,18 @@ public: } protected: - static Symbol *checkName(const CppQuickFixState &state, NameAST *ast) + static Symbol *checkName(const QSharedPointer<const CppQuickFixAssistInterface> &interface, NameAST *ast) { - if (ast && state.isCursorOn(ast)) { + if (ast && interface->isCursorOn(ast)) { if (const Name *name = ast->name) { unsigned line, column; - state.document()->translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); + interface->semanticInfo().doc->translationUnit()->getTokenStartPosition(ast->firstToken(), &line, &column); Symbol *fwdClass = 0; - foreach (const LookupItem &r, state.context().lookup(name, state.document()->scopeAt(line, column))) { + foreach (const LookupItem &r, + interface->context().lookup(name, + interface->semanticInfo().doc->scopeAt(line, column))) { if (! r.declaration()) continue; else if (ForwardClassDeclaration *fwd = r.declaration()->asForwardClassDeclaration()) @@ -1393,8 +1399,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, Symbol *fwdClass) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, Symbol *fwdClass) + : CppQuickFixOperation(interface, priority) , fwdClass(fwdClass) { setDescription(QApplication::translate("CppTools::QuickFix", @@ -1405,13 +1411,13 @@ private: { Q_ASSERT(fwdClass != 0); - if (Class *k = state().snapshot().findMatchingClassDeclaration(fwdClass)) { + if (Class *k = assistInterface()->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()) { + fwdHeaders.insert(assistInterface()->snapshot().document(headerFile)); + foreach (Document::Ptr doc, assistInterface()->snapshot()) { QFileInfo headerFileInfo(doc->fileName()); if (doc->globalSymbolCount() == 0 && doc->includes().size() == 1) fwdHeaders.insert(doc); @@ -1448,7 +1454,7 @@ private: unsigned currentLine = currentFile->cursor().blockNumber() + 1; unsigned bestLine = 0; - foreach (const Document::Include &incl, state().document()->includes()) { + foreach (const Document::Include &incl, assistInterface()->semanticInfo().doc->includes()) { if (incl.line() < currentLine) bestLine = incl.line(); } @@ -1479,18 +1485,18 @@ private: class AddLocalDeclarationOp: public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList<AST *> &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); for (int index = path.size() - 1; index != -1; --index) { if (BinaryExpressionAST *binary = path.at(index)->asBinaryExpression()) { if (binary->left_expression && binary->right_expression && file.tokenAt(binary->binary_op_token).is(T_EQUAL)) { IdExpressionAST *idExpr = binary->left_expression->asIdExpression(); - if (state.isCursorOn(binary->left_expression) && idExpr && idExpr->name->asSimpleName() != 0) { + if (interface->isCursorOn(binary->left_expression) && idExpr && idExpr->name->asSimpleName() != 0) { SimpleNameAST *nameAST = idExpr->name->asSimpleName(); - const QList<LookupItem> results = state.context().lookup(nameAST->name, file.scopeAt(nameAST->firstToken())); + const QList<LookupItem> results = interface->context().lookup(nameAST->name, file.scopeAt(nameAST->firstToken())); Declaration *decl = 0; foreach (const LookupItem &r, results) { if (! r.declaration()) @@ -1504,7 +1510,7 @@ public: } if (! decl) { - return singleResult(new Operation(state, index, binary)); + return singleResult(new Operation(interface, index, binary)); } } } @@ -1518,8 +1524,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, BinaryExpressionAST *binaryAST) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, BinaryExpressionAST *binaryAST) + : CppQuickFixOperation(interface, priority) , binaryAST(binaryAST) { setDescription(QApplication::translate("CppTools::QuickFix", "Add Local Declaration")); @@ -1528,7 +1534,8 @@ private: virtual void performChanges(CppRefactoringFile *currentFile, CppRefactoringChanges *) { TypeOfExpression typeOfExpression; - typeOfExpression.init(state().document(), state().snapshot(), state().context().bindings()); + typeOfExpression.init(assistInterface()->semanticInfo().doc, + assistInterface()->snapshot(), assistInterface()->context().bindings()); const QList<LookupItem> result = typeOfExpression(currentFile->textOf(binaryAST->right_expression), currentFile->scopeAt(binaryAST->firstToken()), TypeOfExpression::Preprocess); @@ -1536,12 +1543,12 @@ private: if (! result.isEmpty()) { SubstitutionEnvironment env; - env.setContext(state().context()); + env.setContext(assistInterface()->context()); env.switchScope(result.first().scope()); UseQualifiedNames q; env.enter(&q); - Control *control = state().context().control().data(); + Control *control = assistInterface()->context().control().data(); FullySpecifiedType tn = rewriteType(result.first().type(), &env, control); Overview oo; @@ -1573,9 +1580,9 @@ private: class ToCamelCaseConverter : public CppQuickFixFactory { public: - virtual QList<CppQuickFixOperation::Ptr> match(const CppQuickFixState &state) + virtual QList<CppQuickFixOperation::Ptr> match(const QSharedPointer<const CppQuickFixAssistInterface> &interface) { - const QList<AST *> &path = state.path(); + const QList<AST *> &path = interface->path(); if (path.isEmpty()) return noResult(); @@ -1597,7 +1604,7 @@ public: 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 singleResult(new Operation(interface, path.size() - 1, newName)); } return noResult(); @@ -1607,8 +1614,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, const QString &newName) - : CppQuickFixOperation(state, priority) + Operation(const QSharedPointer<const CppQuickFixAssistInterface> &interface, int priority, const QString &newName) + : CppQuickFixOperation(interface, priority) , m_name(newName) { setDescription(QApplication::translate("CppTools::QuickFix", @@ -1627,7 +1634,7 @@ private: m_name[i] = m_name.at(i).toUpper(); } } - static_cast<CppEditor::Internal::CPPEditorWidget*>(state().editor())->renameUsagesNow(m_name); + static_cast<CppEditor::Internal::CPPEditorWidget*>(assistInterface()->editor())->renameUsagesNow(m_name); } static bool isConvertibleUnderscore(const QString &name, int pos) @@ -1643,7 +1650,7 @@ private: } // end of anonymous namespace -void CppQuickFixCollector::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) +void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) { plugIn->addAutoReleasedObject(new UseInverseOp); plugIn->addAutoReleasedObject(new FlipBinaryOp); @@ -1657,11 +1664,11 @@ void CppQuickFixCollector::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) plugIn->addAutoReleasedObject(new TranslateStringLiteral); plugIn->addAutoReleasedObject(new CStringToNSString); plugIn->addAutoReleasedObject(new ConvertNumericLiteral); - plugIn->addAutoReleasedObject(new Internal::CompleteSwitchCaseStatement); + plugIn->addAutoReleasedObject(new CompleteSwitchCaseStatement); plugIn->addAutoReleasedObject(new FixForwardDeclarationOp); plugIn->addAutoReleasedObject(new AddLocalDeclarationOp); plugIn->addAutoReleasedObject(new ToCamelCaseConverter); - plugIn->addAutoReleasedObject(new Internal::InsertQtPropertyMembers); - plugIn->addAutoReleasedObject(new Internal::DeclFromDef); - plugIn->addAutoReleasedObject(new Internal::DefFromDecl); + plugIn->addAutoReleasedObject(new InsertQtPropertyMembers); + plugIn->addAutoReleasedObject(new DeclFromDef); + plugIn->addAutoReleasedObject(new DefFromDecl); } diff --git a/src/plugins/cpptools/cppcodecompletion.h b/src/plugins/cpptools/cppcodecompletion.h deleted file mode 100644 index 8d14095f83..0000000000 --- a/src/plugins/cpptools/cppcodecompletion.h +++ /dev/null @@ -1,175 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#ifndef CPPCODECOMPLETION_H -#define CPPCODECOMPLETION_H - -#include <ASTfwd.h> -#include <FullySpecifiedType.h> -#include <cplusplus/Icons.h> -#include <cplusplus/Overview.h> -#include <cplusplus/TypeOfExpression.h> - -#include <texteditor/icompletioncollector.h> -#include <texteditor/snippets/snippetcollector.h> - -#include <QtCore/QObject> -#include <QtCore/QPointer> - -QT_BEGIN_NAMESPACE -class QTextCursor; -QT_END_NAMESPACE - -namespace TextEditor { -class ITextEditor; -class BaseTextEditorWidget; -} - -namespace CPlusPlus { -class LookupItem; -class ClassOrNamespace; -} - -namespace CppTools { -namespace Internal { - -class CppModelManager; -class FunctionArgumentWidget; - -class CppCodeCompletion : public TextEditor::ICompletionCollector -{ - Q_OBJECT -public: - explicit CppCodeCompletion(CppModelManager *manager); - - void setObjcEnabled(bool objcEnabled) - { m_objcEnabled = objcEnabled; } - - TextEditor::ITextEditor *editor() const; - int startPosition() const; - bool shouldRestartCompletion(); - QList<TextEditor::CompletionItem> getCompletions(); - bool supportsEditor(TextEditor::ITextEditor *editor) const; - bool supportsPolicy(TextEditor::CompletionPolicy policy) const; - bool triggersCompletion(TextEditor::ITextEditor *editor); - int startCompletion(TextEditor::ITextEditor *editor); - void completions(QList<TextEditor::CompletionItem> *completions); - - bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - void complete(const TextEditor::CompletionItem &item, QChar typedChar); - bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); - void cleanup(); - - QIcon iconForSymbol(CPlusPlus::Symbol *symbol) const; - -private: - void addSnippets(); - void addKeywords(); - void addMacros(const QString &fileName, const CPlusPlus::Snapshot &snapshot); - void addMacros_helper(const CPlusPlus::Snapshot &snapshot, - const QString &fileName, - QSet<QString> *processed, - QSet<QString> *definedMacros); - void addCompletionItem(CPlusPlus::Symbol *symbol); - - bool completeInclude(const QTextCursor &cursor); - void completePreprocessor(); - - void globalCompletion(CPlusPlus::Scope *scope); - - bool completeConstructorOrFunction(const QList<CPlusPlus::LookupItem> &results, - int endOfExpression, bool toolTipOnly); - - bool completeMember(const QList<CPlusPlus::LookupItem> &results); - bool completeScope(const QList<CPlusPlus::LookupItem> &results); - - void completeNamespace(CPlusPlus::ClassOrNamespace *binding); - - void completeClass(CPlusPlus::ClassOrNamespace *b, - bool staticLookup = true); - - bool completeConstructors(CPlusPlus::Class *klass); - - bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, - bool wantSignals); - - bool completeSignal(const QList<CPlusPlus::LookupItem> &results) - { return completeQtMethod(results, true); } - - bool completeSlot(const QList<CPlusPlus::LookupItem> &results) - { return completeQtMethod(results, false); } - - int findStartOfName(int pos = -1) const; - - int startCompletionHelper(TextEditor::ITextEditor *editor); - - int startCompletionInternal(TextEditor::BaseTextEditorWidget *edit, - const QString fileName, - unsigned line, unsigned column, - const QString &expression, - int endOfExpression); - - QList<TextEditor::CompletionItem> removeDuplicates(const QList<TextEditor::CompletionItem> &items); - -private: - void completeObjCMsgSend(CPlusPlus::ClassOrNamespace *binding, - bool staticClassAccess); - bool tryObjCCompletion(TextEditor::BaseTextEditorWidget *edit); - bool objcKeywordsWanted() const; - - static QStringList preprocessorCompletions; - - CppModelManager *m_manager; - TextEditor::ITextEditor *m_editor; - int m_startPosition; // Position of the cursor from which completion started - bool m_shouldRestartCompletion; - - bool m_automaticCompletion; - unsigned m_completionOperator; - bool m_objcEnabled; - - TextEditor::SnippetCollector m_snippetProvider; - - CPlusPlus::Icons m_icons; - CPlusPlus::Overview overview; - CPlusPlus::TypeOfExpression typeOfExpression; - QPointer<FunctionArgumentWidget> m_functionArgumentWidget; - - QList<TextEditor::CompletionItem> m_completions; -}; - -} // namespace Internal -} // namespace CppTools - -Q_DECLARE_METATYPE(CPlusPlus::Symbol *) - -#endif // CPPCODECOMPLETION_H diff --git a/src/plugins/cpptools/cppcodecompletion.cpp b/src/plugins/cpptools/cppcompletionassist.cpp index 5b2c309277..d6e89dc3dc 100644 --- a/src/plugins/cpptools/cppcodecompletion.cpp +++ b/src/plugins/cpptools/cppcompletionassist.cpp @@ -30,11 +30,11 @@ ** **************************************************************************/ -#include "cppcodecompletion.h" #include "cppmodelmanager.h" +#include "cppcompletionassist.h" #include "cppdoxygen.h" +#include "cppmodelmanager.h" #include "cpptoolsconstants.h" -#include "cpptoolseditorsupport.h" #include <Control.h> #include <AST.h> @@ -55,277 +55,406 @@ #include <cplusplus/BackwardsScanner.h> #include <cplusplus/LookupContext.h> -#include <cppeditor/cppeditorconstants.h> - +#include <coreplugin/ifile.h> #include <coreplugin/icore.h> #include <coreplugin/mimedatabase.h> -#include <coreplugin/editormanager/editormanager.h> -#include <texteditor/completionsettings.h> -#include <texteditor/basetexteditor.h> +#include <cppeditor/cppeditorconstants.h> +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/ifunctionhintproposalmodel.h> +#include <texteditor/codeassist/functionhintproposal.h> +#include <texteditor/convenience.h> #include <texteditor/snippets/snippet.h> -#include <projectexplorer/projectexplorer.h> - -#include <utils/faketooltip.h> -#include <utils/qtcassert.h> - -#include <QtCore/QMap> -#include <QtCore/QFile> -#include <QtGui/QAction> -#include <QtGui/QApplication> -#include <QtGui/QDesktopWidget> -#include <QtGui/QKeyEvent> -#include <QtGui/QLabel> -#include <QtGui/QStyle> -#include <QtGui/QTextDocument> // Qt::escape() -#include <QtGui/QToolButton> -#include <QtGui/QVBoxLayout> -#include <QtAlgorithms> +#include <texteditor/texteditorsettings.h> +#include <texteditor/completionsettings.h> -namespace { - const bool debug = ! qgetenv("CPLUSPLUS_DEBUG").isEmpty(); -} +#include <QtCore/QLatin1String> +#include <QtGui/QTextCursor> +#include <QtGui/QTextDocument> +#include <QtGui/QIcon> using namespace CPlusPlus; +using namespace CppEditor; +using namespace CppTools; +using namespace Internal; +using namespace TextEditor; -namespace CppTools { -namespace Internal { +namespace { -class FunctionArgumentWidget : public QLabel +int activationSequenceChar(const QChar &ch, + const QChar &ch2, + const QChar &ch3, + unsigned *kind, + bool wantFunctionCall) { - Q_OBJECT + int referencePosition = 0; + int completionKind = T_EOF_SYMBOL; + switch (ch.toLatin1()) { + case '.': + if (ch2 != QLatin1Char('.')) { + completionKind = T_DOT; + referencePosition = 1; + } + break; + case ',': + completionKind = T_COMMA; + referencePosition = 1; + break; + case '(': + if (wantFunctionCall) { + completionKind = T_LPAREN; + referencePosition = 1; + } + break; + case ':': + if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) { + completionKind = T_COLON_COLON; + referencePosition = 2; + } + break; + case '>': + if (ch2 == QLatin1Char('-')) { + completionKind = T_ARROW; + referencePosition = 2; + } + break; + case '*': + if (ch2 == QLatin1Char('.')) { + completionKind = T_DOT_STAR; + referencePosition = 2; + } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) { + completionKind = T_ARROW_STAR; + referencePosition = 3; + } + break; + case '\\': + case '@': + if (ch2.isNull() || ch2.isSpace()) { + completionKind = T_DOXY_COMMENT; + referencePosition = 1; + } + break; + case '<': + completionKind = T_ANGLE_STRING_LITERAL; + referencePosition = 1; + break; + case '"': + completionKind = T_STRING_LITERAL; + referencePosition = 1; + break; + case '/': + completionKind = T_SLASH; + referencePosition = 1; + break; + case '#': + completionKind = T_POUND; + referencePosition = 1; + break; + } -public: - FunctionArgumentWidget(); - void showFunctionHint(QList<Function *> functionSymbols, - const LookupContext &context, - int startPosition); + if (kind) + *kind = completionKind; -protected: - bool eventFilter(QObject *obj, QEvent *e); + return referencePosition; +} -private slots: - void nextPage(); - void previousPage(); +} // Anonymous -private: - void updateArgumentHighlight(); - void updateHintText(); - void placeInsideScreen(); +namespace CppTools { +namespace Internal { - Function *currentFunction() const - { return m_items.at(m_current); } +struct CompleteFunctionDeclaration +{ + explicit CompleteFunctionDeclaration(Function *f = 0) + : function(f) + {} + + Function *function; +}; - int m_startpos; - int m_currentarg; - int m_current; - bool m_escapePressed; +// ---------------------- +// CppAssistProposalModel +// ---------------------- +class CppAssistProposalModel : public TextEditor::BasicProposalItemListModel +{ +public: + CppAssistProposalModel() + : TextEditor::BasicProposalItemListModel() + , m_sortable(false) + , m_completionOperator(T_EOF_SYMBOL) + , m_replaceDotForArrow(false) + {} - TextEditor::ITextEditor *m_editor; + virtual bool isSortable() const { return m_sortable; } + virtual IAssistProposalItem *proposalItem(int index) const; - QWidget *m_pager; - QLabel *m_numberLabel; - Utils::FakeToolTip *m_popupFrame; - QList<Function *> m_items; - LookupContext m_context; + bool m_sortable; + unsigned m_completionOperator; + bool m_replaceDotForArrow; + Snapshot m_snapshot; }; -class ConvertToCompletionItem: protected NameVisitor +// --------------------- +// CppAssistProposalItem +// --------------------- +class CppAssistProposalItem : public TextEditor::BasicProposalItem { - // The completion collector. - CppCodeCompletion *_collector; - - // The completion item. - TextEditor::CompletionItem _item; +public: + CppAssistProposalItem() : m_isOverloaded(false) {} - // The current symbol. - Symbol *_symbol; + virtual bool prematurelyApplies(const QChar &c) const; + virtual void applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const; - // The pretty printer. - Overview overview; + bool isOverloaded() const { return m_isOverloaded; } + void markAsOverloaded() { m_isOverloaded = true; } + void keepCompletionOperator(unsigned compOp) { m_completionOperator = compOp; } + void keepSnapshot(const Snapshot &snapshot) { m_snapshot = snapshot; } -public: - ConvertToCompletionItem(CppCodeCompletion *collector) - : _collector(collector), - _item(0), - _symbol(0) - { } +private: + bool m_isOverloaded; + unsigned m_completionOperator; + mutable QChar m_typedChar; + Snapshot m_snapshot; +}; - TextEditor::CompletionItem operator()(Symbol *symbol) - { - if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId()) - return 0; +} // Internal +} // CppTools - TextEditor::CompletionItem previousItem = switchCompletionItem(0); - Symbol *previousSymbol = switchSymbol(symbol); - accept(symbol->unqualifiedName()); - if (_item.isValid()) - _item.data = QVariant::fromValue(symbol); - (void) switchSymbol(previousSymbol); - return switchCompletionItem(previousItem); - } +Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration) -protected: - Symbol *switchSymbol(Symbol *symbol) - { - Symbol *previousSymbol = _symbol; - _symbol = symbol; - return previousSymbol; - } +IAssistProposalItem *CppAssistProposalModel::proposalItem(int index) const +{ + BasicProposalItem *item = + static_cast<BasicProposalItem *>(BasicProposalItemListModel::proposalItem(index)); + if (!item->data().canConvert<QString>()) { + CppAssistProposalItem *cppItem = static_cast<CppAssistProposalItem *>(item); + cppItem->keepCompletionOperator(m_completionOperator); + cppItem->keepSnapshot(m_snapshot); + } + return item; +} - TextEditor::CompletionItem switchCompletionItem(TextEditor::CompletionItem item) - { - TextEditor::CompletionItem previousItem = _item; - _item = item; - return previousItem; +bool CppAssistProposalItem::prematurelyApplies(const QChar &typedChar) const +{ + if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { + if (typedChar == QLatin1Char('(') || typedChar == QLatin1Char(',')) { + m_typedChar = typedChar; + return true; + } + } else if (m_completionOperator == T_STRING_LITERAL + || m_completionOperator == T_ANGLE_STRING_LITERAL) { + if (typedChar == QLatin1Char('/') && text().endsWith(QLatin1Char('/'))) { + m_typedChar = typedChar; + return true; + } + } else if (data().value<Symbol *>()) { + if (typedChar == QLatin1Char(':') + || typedChar == QLatin1Char(';') + || typedChar == QLatin1Char('.') + || typedChar == QLatin1Char(',') + || typedChar == QLatin1Char('(')) { + m_typedChar = typedChar; + return true; + } + } else if (data().canConvert<CompleteFunctionDeclaration>()) { + if (typedChar == QLatin1Char('(')) { + m_typedChar = typedChar; + return true; + } } - TextEditor::CompletionItem newCompletionItem(const Name *name) - { - TextEditor::CompletionItem item(_collector); - item.text = overview.prettyName(name); - item.icon = _collector->iconForSymbol(_symbol); - return item; - } + return false; +} - virtual void visit(const Identifier *name) - { _item = newCompletionItem(name); } +void CppAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const +{ + Symbol *symbol = 0; - virtual void visit(const TemplateNameId *name) - { - _item = newCompletionItem(name); - _item.text = QLatin1String(name->identifier()->chars()); - } + if (data().isValid()) + symbol = data().value<Symbol *>(); - virtual void visit(const DestructorNameId *name) - { _item = newCompletionItem(name); } + QString toInsert; + QString extraChars; + int extraLength = 0; + int cursorOffset = 0; - virtual void visit(const OperatorNameId *name) - { _item = newCompletionItem(name); } + bool autoParenthesesEnabled = true; - virtual void visit(const ConversionNameId *name) - { _item = newCompletionItem(name); } + if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { + toInsert = text(); + extraChars += QLatin1Char(')'); - virtual void visit(const QualifiedNameId *name) - { _item = newCompletionItem(name->name()); } -}; + if (m_typedChar == QLatin1Char('(')) // Eat the opening parenthesis + m_typedChar = QChar(); + } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) { + toInsert = text(); + if (!toInsert.endsWith(QLatin1Char('/'))) { + extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"'); + } else { + if (m_typedChar == QLatin1Char('/')) // Eat the slash + m_typedChar = QChar(); + } + } else { + toInsert = text(); -struct CompleteFunctionDeclaration -{ - explicit CompleteFunctionDeclaration(Function *f = 0) - : function(f) - {} + const CompletionSettings &completionSettings = + TextEditorSettings::instance()->completionSettings(); + const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets; - Function *function; -}; + if (autoInsertBrackets && symbol && symbol->type()) { + if (Function *function = symbol->type()->asFunctionType()) { + // If the member is a function, automatically place the opening parenthesis, + // except when it might take template parameters. + if (! function->hasReturnType() && (function->unqualifiedName() && !function->unqualifiedName()->isDestructorNameId())) { + // Don't insert any magic, since the user might have just wanted to select the class -} // namespace Internal -} // namespace CppTools + /// ### port me +#if 0 + } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) { + // If there are no arguments, then we need the template specification + if (function->argumentCount() == 0) { + extraChars += QLatin1Char('<'); + } +#endif + } else if (! function->isAmbiguous()) { + // When the user typed the opening parenthesis, he'll likely also type the closing one, + // in which case it would be annoying if we put the cursor after the already automatically + // inserted closing parenthesis. + const bool skipClosingParenthesis = m_typedChar != QLatin1Char('('); -using namespace CppTools::Internal; + if (completionSettings.m_spaceAfterFunctionName) + extraChars += QLatin1Char(' '); + extraChars += QLatin1Char('('); + if (m_typedChar == QLatin1Char('(')) + m_typedChar = QChar(); -Q_DECLARE_METATYPE(CompleteFunctionDeclaration) + // If the function doesn't return anything, automatically place the semicolon, + // unless we're doing a scope completion (then it might be function definition). + const QChar characterAtCursor = editor->characterAt(editor->position()); + bool endWithSemicolon = m_typedChar == QLatin1Char(';') + || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON); + const QChar semicolon = m_typedChar.isNull() ? QLatin1Char(';') : m_typedChar; + if (endWithSemicolon && characterAtCursor == semicolon) { + endWithSemicolon = false; + m_typedChar = QChar(); + } -FunctionArgumentWidget::FunctionArgumentWidget(): - m_startpos(-1), - m_current(0), - m_escapePressed(false) -{ - QObject *editorObject = Core::EditorManager::instance()->currentEditor(); - m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject); - - m_popupFrame = new Utils::FakeToolTip(m_editor->widget()); - - QToolButton *downArrow = new QToolButton; - downArrow->setArrowType(Qt::DownArrow); - downArrow->setFixedSize(16, 16); - downArrow->setAutoRaise(true); - - QToolButton *upArrow = new QToolButton; - upArrow->setArrowType(Qt::UpArrow); - upArrow->setFixedSize(16, 16); - upArrow->setAutoRaise(true); - - setParent(m_popupFrame); - setFocusPolicy(Qt::NoFocus); - - m_pager = new QWidget; - QHBoxLayout *hbox = new QHBoxLayout(m_pager); - hbox->setMargin(0); - hbox->setSpacing(0); - hbox->addWidget(upArrow); - m_numberLabel = new QLabel; - hbox->addWidget(m_numberLabel); - hbox->addWidget(downArrow); - - QHBoxLayout *layout = new QHBoxLayout; - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(m_pager); - layout->addWidget(this); - m_popupFrame->setLayout(layout); - - connect(upArrow, SIGNAL(clicked()), SLOT(previousPage())); - connect(downArrow, SIGNAL(clicked()), SLOT(nextPage())); - - setTextFormat(Qt::RichText); - - qApp->installEventFilter(this); -} + // If the function takes no arguments, automatically place the closing parenthesis + if (!isOverloaded() && ! function->hasArguments() && skipClosingParenthesis) { + extraChars += QLatin1Char(')'); + if (endWithSemicolon) { + extraChars += semicolon; + m_typedChar = QChar(); + } + } else if (autoParenthesesEnabled) { + const QChar lookAhead = editor->characterAt(editor->position() + 1); + if (MatchingText::shouldInsertMatchingText(lookAhead)) { + extraChars += QLatin1Char(')'); + --cursorOffset; + if (endWithSemicolon) { + extraChars += semicolon; + --cursorOffset; + m_typedChar = QChar(); + } + } + // TODO: When an opening parenthesis exists, the "semicolon" should really be + // inserted after the matching closing parenthesis. + } + } + } + } -void FunctionArgumentWidget::showFunctionHint(QList<Function *> functionSymbols, - const LookupContext &context, - int startPosition) -{ - Q_ASSERT(!functionSymbols.isEmpty()); + if (autoInsertBrackets && data().canConvert<CompleteFunctionDeclaration>()) { + if (m_typedChar == QLatin1Char('(')) + m_typedChar = QChar(); - if (m_startpos == startPosition) - return; + // everything from the closing parenthesis on are extra chars, to + // make sure an auto-inserted ")" gets replaced by ") const" if necessary + int closingParen = toInsert.lastIndexOf(QLatin1Char(')')); + extraChars = toInsert.mid(closingParen); + toInsert.truncate(closingParen); + } + } - m_pager->setVisible(functionSymbols.size() > 1); + // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before + if (!m_typedChar.isNull()) { + extraChars += m_typedChar; + if (cursorOffset != 0) + --cursorOffset; + } - m_items = functionSymbols; - m_context = context; - m_startpos = startPosition; - m_current = 0; - m_escapePressed = false; + // Avoid inserting characters that are already there + for (int i = 0; i < extraChars.length(); ++i) { + const QChar a = extraChars.at(i); + const QChar b = editor->characterAt(editor->position() + i); + if (a == b) + ++extraLength; + else + break; + } - // update the text - m_currentarg = -1; - updateArgumentHighlight(); + toInsert += extraChars; - m_popupFrame->show(); + // Insert the remainder of the name + int length = editor->position() - basePosition + extraLength; + editor->setCursorPosition(basePosition); + editor->replace(length, toInsert); + if (cursorOffset) + editor->setCursorPosition(editor->position() + cursorOffset); } -void FunctionArgumentWidget::nextPage() +// -------------------- +// CppFunctionHintModel +// -------------------- +class CppFunctionHintModel : public TextEditor::IFunctionHintProposalModel { - m_current = (m_current + 1) % m_items.size(); - updateHintText(); -} +public: + CppFunctionHintModel(QList<Function *> functionSymbols) + : m_functionSymbols(functionSymbols) + , m_currentArg(-1) + {} -void FunctionArgumentWidget::previousPage() + virtual void reset() {} + virtual int size() const { return m_functionSymbols.size(); } + virtual QString text(int index) const; + virtual int activeArgument(const QString &prefix) const; + +private: + QList<Function *> m_functionSymbols; + mutable int m_currentArg; +}; + +QString CppFunctionHintModel::text(int index) const { - if (m_current == 0) - m_current = m_items.size() - 1; - else - --m_current; + Overview overview; + overview.setShowReturnTypes(true); + overview.setShowArgumentNames(true); + overview.setMarkedArgument(m_currentArg + 1); + Function *f = m_functionSymbols.at(index); + + const QString prettyMethod = overview(f->type(), f->name()); + const int begin = overview.markedArgumentBegin(); + const int end = overview.markedArgumentEnd(); - updateHintText(); + QString hintText; + hintText += Qt::escape(prettyMethod.left(begin)); + hintText += "<b>"; + hintText += Qt::escape(prettyMethod.mid(begin, end - begin)); + hintText += "</b>"; + hintText += Qt::escape(prettyMethod.mid(end)); + return hintText; } -void FunctionArgumentWidget::updateArgumentHighlight() +int CppFunctionHintModel::activeArgument(const QString &prefix) const { - int curpos = m_editor->position(); - if (curpos < m_startpos) { - m_popupFrame->close(); - return; - } - - QString str = m_editor->textAt(m_startpos, curpos - m_startpos); int argnr = 0; int parcount = 0; SimpleLexer tokenize; - QList<Token> tokens = tokenize(str); + QList<Token> tokens = tokenize(prefix); for (int i = 0; i < tokens.count(); ++i) { const Token &tk = tokens.at(i); if (tk.is(T_LPAREN)) @@ -336,367 +465,279 @@ void FunctionArgumentWidget::updateArgumentHighlight() ++argnr; } - if (m_currentarg != argnr) { - m_currentarg = argnr; - updateHintText(); - } - if (parcount < 0) - m_popupFrame->close(); + return -1; + + if (argnr != m_currentArg) + m_currentArg = argnr; + + return argnr; } -bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e) +// --------------------------- +// CppCompletionAssistProvider +// --------------------------- +bool CppCompletionAssistProvider::supportsEditor(const QString &editorId) const { - switch (e->type()) { - case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - if (m_items.size() > 1) { - QKeyEvent *ke = static_cast<QKeyEvent*>(e); - if (ke->key() == Qt::Key_Up) { - previousPage(); - return true; - } else if (ke->key() == Qt::Key_Down) { - nextPage(); - return true; - } - return false; - } - break; - case QEvent::KeyRelease: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_escapePressed) { - m_popupFrame->close(); - return false; - } - updateArgumentHighlight(); - break; - case QEvent::WindowDeactivate: - case QEvent::FocusOut: - if (obj != m_editor->widget()) - break; - m_popupFrame->close(); - break; - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::Wheel: { - QWidget *widget = qobject_cast<QWidget *>(obj); - if (! (widget == this || m_popupFrame->isAncestorOf(widget))) { - m_popupFrame->close(); - } - } - break; - default: - break; - } - return false; + return editorId == QLatin1String(CppEditor::Constants::CPPEDITOR_ID); } -void FunctionArgumentWidget::updateHintText() +int CppCompletionAssistProvider::activationCharSequenceLength() const { - Overview overview; - overview.setShowReturnTypes(true); - overview.setShowArgumentNames(true); - overview.setMarkedArgument(m_currentarg + 1); - Function *f = currentFunction(); - - const QString prettyMethod = overview(f->type(), f->name()); - const int begin = overview.markedArgumentBegin(); - const int end = overview.markedArgumentEnd(); - - QString hintText; - hintText += Qt::escape(prettyMethod.left(begin)); - hintText += "<b>"; - hintText += Qt::escape(prettyMethod.mid(begin, end - begin)); - hintText += "</b>"; - hintText += Qt::escape(prettyMethod.mid(end)); - setText(hintText); - - m_numberLabel->setText(tr("%1 of %2").arg(m_current + 1).arg(m_items.size())); - - placeInsideScreen(); + return 3; } -void FunctionArgumentWidget::placeInsideScreen() +bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const { - const QDesktopWidget *desktop = QApplication::desktop(); -#ifdef Q_WS_MAC - const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_editor->widget())); -#else - const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_editor->widget())); -#endif + const QChar &ch = sequence.at(2); + const QChar &ch2 = sequence.at(1); + const QChar &ch3 = sequence.at(0); + if (activationSequenceChar(ch, ch2, ch3, 0, true) != 0) + return true; + return false; +} - m_pager->setFixedWidth(m_pager->minimumSizeHint().width()); - - setWordWrap(false); - const int maxDesiredWidth = screen.width() - 10; - const QSize minHint = m_popupFrame->minimumSizeHint(); - if (minHint.width() > maxDesiredWidth) { - setWordWrap(true); - m_popupFrame->setFixedWidth(maxDesiredWidth); - const int extra = - m_popupFrame->contentsMargins().bottom() + m_popupFrame->contentsMargins().top(); - m_popupFrame->setFixedHeight(heightForWidth(maxDesiredWidth - m_pager->width()) + extra); - } else { - m_popupFrame->setFixedSize(minHint); - } +IAssistProcessor *CppCompletionAssistProvider::createProcessor() const +{ + return new CppCompletionAssistProcessor; +} - const QSize sz = m_popupFrame->size(); - QPoint pos = m_editor->cursorRect(m_startpos).topLeft(); - pos.setY(pos.y() - sz.height() - 1); +// ----------------- +// CppAssistProposal +// ----------------- +class CppAssistProposal : public TextEditor::GenericProposal +{ +public: + CppAssistProposal(int cursorPos, TextEditor::IGenericProposalModel *model) + : TextEditor::GenericProposal(cursorPos, model) + , m_replaceDotForArrow(static_cast<CppAssistProposalModel *>(model)->m_replaceDotForArrow) + {} - if (pos.x() + sz.width() > screen.right()) - pos.setX(screen.right() - sz.width()); + virtual bool isCorrective() const { return m_replaceDotForArrow; } + virtual void makeCorrection(BaseTextEditor *editor); - m_popupFrame->move(pos); -} +private: + bool m_replaceDotForArrow; +}; -CppCodeCompletion::CppCodeCompletion(CppModelManager *manager) - : ICompletionCollector(manager), - m_manager(manager), - m_editor(0), - m_startPosition(-1), - m_shouldRestartCompletion(false), - m_automaticCompletion(false), - m_completionOperator(T_EOF_SYMBOL), - m_objcEnabled(true), - m_snippetProvider(CppEditor::Constants::CPP_SNIPPETS_GROUP_ID, - QIcon(QLatin1String(":/texteditor/images/snippet.png"))) +void CppAssistProposal::makeCorrection(BaseTextEditor *editor) { + editor->setCursorPosition(basePosition() - 1); + editor->replace(1, QLatin1String("->")); + moveBasePosition(1); } -QIcon CppCodeCompletion::iconForSymbol(Symbol *symbol) const -{ - return m_icons.iconForSymbol(symbol); -} +namespace { -/* - Searches backwards for an access operator. -*/ -static int startOfOperator(TextEditor::ITextEditor *editor, - int pos, unsigned *kind, - bool wantFunctionCall) +class ConvertToCompletionItem: protected NameVisitor { - const QChar ch = pos > -1 ? editor->characterAt(pos - 1) : QChar(); - const QChar ch2 = pos > 0 ? editor->characterAt(pos - 2) : QChar(); - const QChar ch3 = pos > 1 ? editor->characterAt(pos - 3) : QChar(); + // The completion item. + BasicProposalItem *_item; - int start = pos; - int completionKind = T_EOF_SYMBOL; + // The current symbol. + Symbol *_symbol; - switch (ch.toLatin1()) { - case '.': - if (ch2 != QLatin1Char('.')) { - completionKind = T_DOT; - --start; - } - break; - case ',': - completionKind = T_COMMA; - --start; - break; - case '(': - if (wantFunctionCall) { - completionKind = T_LPAREN; - --start; - } - break; - case ':': - if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) { - completionKind = T_COLON_COLON; - start -= 2; - } - break; - case '>': - if (ch2 == QLatin1Char('-')) { - completionKind = T_ARROW; - start -= 2; - } - break; - case '*': - if (ch2 == QLatin1Char('.')) { - completionKind = T_DOT_STAR; - start -= 2; - } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) { - completionKind = T_ARROW_STAR; - start -= 3; - } - break; - case '\\': - case '@': - if (ch2.isNull() || ch2.isSpace()) { - completionKind = T_DOXY_COMMENT; - --start; - } - break; - case '<': - completionKind = T_ANGLE_STRING_LITERAL; - --start; - break; - case '"': - completionKind = T_STRING_LITERAL; - --start; - break; - case '/': - completionKind = T_SLASH; - --start; - break; - case '#': - completionKind = T_POUND; - --start; - break; - } + // The pretty printer. + Overview overview; - if (start == pos) - return start; +public: + ConvertToCompletionItem() + : _item(0) + , _symbol(0) + { } - TextEditor::BaseTextEditorWidget *edit = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); - QTextCursor tc(edit->textCursor()); - tc.setPosition(pos); + BasicProposalItem *operator()(Symbol *symbol) + { + if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId()) + return 0; - // Include completion: make sure the quote character is the first one on the line - if (completionKind == T_STRING_LITERAL) { - QTextCursor s = tc; - s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); - QString sel = s.selectedText(); - if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) { - completionKind = T_EOF_SYMBOL; - start = pos; - } + BasicProposalItem *previousItem = switchCompletionItem(0); + Symbol *previousSymbol = switchSymbol(symbol); + accept(symbol->unqualifiedName()); + if (_item) + _item->setData(QVariant::fromValue(symbol)); + (void) switchSymbol(previousSymbol); + return switchCompletionItem(previousItem); } - if (completionKind == T_COMMA) { - ExpressionUnderCursor expressionUnderCursor; - if (expressionUnderCursor.startOfFunctionCall(tc) == -1) { - completionKind = T_EOF_SYMBOL; - start = pos; - } +protected: + Symbol *switchSymbol(Symbol *symbol) + { + Symbol *previousSymbol = _symbol; + _symbol = symbol; + return previousSymbol; } - SimpleLexer tokenize; - tokenize.setQtMocRunEnabled(true); - tokenize.setObjCEnabled(true); - tokenize.setSkipComments(false); - const QList<Token> &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block())); - const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor - const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); - - if (completionKind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) { - completionKind = T_EOF_SYMBOL; - start = pos; - } - // Don't complete in comments or strings, but still check for include completion - else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) || - (tk.isLiteral() && (completionKind != T_STRING_LITERAL - && completionKind != T_ANGLE_STRING_LITERAL - && completionKind != T_SLASH))) { - completionKind = T_EOF_SYMBOL; - start = pos; - } - // Include completion: can be triggered by slash, but only in a string - else if (completionKind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) { - completionKind = T_EOF_SYMBOL; - start = pos; + BasicProposalItem *switchCompletionItem(BasicProposalItem *item) + { + BasicProposalItem *previousItem = _item; + _item = item; + return previousItem; } - else if (completionKind == T_LPAREN) { - if (tokenIdx > 0) { - const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN - switch (previousToken.kind()) { - case T_IDENTIFIER: - case T_GREATER: - case T_SIGNAL: - case T_SLOT: - break; // good - - default: - // that's a bad token :) - completionKind = T_EOF_SYMBOL; - start = pos; - } - } + + BasicProposalItem *newCompletionItem(const Name *name) + { + BasicProposalItem *item = new CppAssistProposalItem; + item->setText(overview.prettyName(name)); + return item; } - // Check for include preprocessor directive - else if (completionKind == T_STRING_LITERAL || completionKind == T_ANGLE_STRING_LITERAL || completionKind == T_SLASH) { - bool include = false; - if (tokens.size() >= 3) { - if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) || - tokens.at(2).is(T_ANGLE_STRING_LITERAL))) { - const Token &directiveToken = tokens.at(1); - QString directive = tc.block().text().mid(directiveToken.begin(), - directiveToken.length()); - if (directive == QLatin1String("include") || - directive == QLatin1String("include_next") || - directive == QLatin1String("import")) { - include = true; - } - } - } - if (!include) { - completionKind = T_EOF_SYMBOL; - start = pos; - } + virtual void visit(const Identifier *name) + { _item = newCompletionItem(name); } + + virtual void visit(const TemplateNameId *name) + { + _item = newCompletionItem(name); + _item->setText(QLatin1String(name->identifier()->chars())); } - if (kind) - *kind = completionKind; + virtual void visit(const DestructorNameId *name) + { _item = newCompletionItem(name); } - return start; -} + virtual void visit(const OperatorNameId *name) + { _item = newCompletionItem(name); } + + virtual void visit(const ConversionNameId *name) + { _item = newCompletionItem(name); } -bool CppCodeCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const + virtual void visit(const QualifiedNameId *name) + { _item = newCompletionItem(name->name()); } +}; + +Class *asClassOrTemplateClassType(FullySpecifiedType ty) { - return policy == TextEditor::SemanticCompletion; + if (Class *classTy = ty->asClassType()) + return classTy; + else if (Template *templ = ty->asTemplateType()) { + if (Symbol *decl = templ->declaration()) + return decl->asClass(); + } + return 0; } -bool CppCodeCompletion::supportsEditor(TextEditor::ITextEditor *editor) const +Scope *enclosingNonTemplateScope(Symbol *symbol) { - return m_manager->isCppEditor(editor); + if (symbol) { + if (Scope *scope = symbol->enclosingScope()) { + if (Template *templ = scope->asTemplate()) + return templ->enclosingScope(); + return scope; + } + } + return 0; } -TextEditor::ITextEditor *CppCodeCompletion::editor() const -{ return m_editor; } +Function *asFunctionOrTemplateFunctionType(FullySpecifiedType ty) +{ + if (Function *funTy = ty->asFunctionType()) + return funTy; + else if (Template *templ = ty->asTemplateType()) { + if (Symbol *decl = templ->declaration()) + return decl->asFunction(); + } + return 0; +} -int CppCodeCompletion::startPosition() const -{ return m_startPosition; } +} // Anonymous + +// ---------------------------- +// CppCompletionAssistProcessor +// ---------------------------- +CppCompletionAssistProcessor::CppCompletionAssistProcessor() + : m_startPosition(-1) + , m_objcEnabled(true) + , m_snippetCollector(CppEditor::Constants::CPP_SNIPPETS_GROUP_ID, + QIcon(QLatin1String(":/texteditor/images/snippet.png"))) + , preprocessorCompletions(QStringList() + << QLatin1String("define") + << QLatin1String("error") + << QLatin1String("include") + << QLatin1String("line") + << QLatin1String("pragma") + << QLatin1String("undef") + << QLatin1String("if") + << QLatin1String("ifdef") + << QLatin1String("ifndef") + << QLatin1String("elif") + << QLatin1String("else") + << QLatin1String("endif")) + , m_model(new CppAssistProposalModel) + , m_hintProposal(0) +{} -bool CppCodeCompletion::shouldRestartCompletion() -{ return m_shouldRestartCompletion; } +CppCompletionAssistProcessor::~CppCompletionAssistProcessor() +{} -bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) +IAssistProposal * CppCompletionAssistProcessor::perform(const IAssistInterface *interface) { - m_editor = editor; - m_automaticCompletion = false; + m_interface.reset(static_cast<const CppCompletionAssistInterface *>(interface)); + + if (interface->reason() != ExplicitlyInvoked && !accepts()) + return 0; - const int pos = editor->position(); + m_model->m_snapshot = m_interface->snapshot(); + + int index = startCompletionHelper(); + if (index != -1) { + if (m_hintProposal) + return m_hintProposal; + + if (interface->reason() == IdleEditor) { + const int pos = m_interface->position(); + const QChar ch = m_interface->characterAt(pos); + if (! (ch.isLetterOrNumber() || ch == QLatin1Char('_'))) { + for (int i = pos - 1;; --i) { + const QChar ch = m_interface->characterAt(i); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) { + const QString wordUnderCursor = m_interface->textAt(i, pos - i); + if (wordUnderCursor.at(0).isLetter() || wordUnderCursor.at(0) == QLatin1Char('_')) { + foreach (const BasicProposalItem *item, m_completions) { + if (item->text() == wordUnderCursor) + return 0; + } + } else { + return 0; + } + } else + break; + } + } + } + + if (m_model->m_completionOperator != T_EOF_SYMBOL) + m_model->m_sortable = true; + else + m_model->m_sortable = false; + return createContentProposal(); + } + + return 0; +} + +bool CppCompletionAssistProcessor::accepts() const +{ + const int pos = m_interface->position(); unsigned token = T_EOF_SYMBOL; - if (startOfOperator(editor, pos, &token, /*want function call=*/ true) != pos) { + const int start = startOfOperator(pos, &token, /*want function call=*/ true); + if (start != pos) { if (token == T_POUND) { - int line, column; - editor->convertPosition(pos, &line, &column); + const int column = pos - m_interface->document()->findBlock(start).position(); if (column != 1) return false; } return true; - } else if (completionSettings().m_completionTrigger == TextEditor::AutomaticCompletion) { + } else { // Trigger completion after three characters of a name have been typed, when not editing an existing name - QChar characterUnderCursor = editor->characterAt(pos); + QChar characterUnderCursor = m_interface->characterAt(pos); if (!characterUnderCursor.isLetterOrNumber()) { const int startOfName = findStartOfName(pos); if (pos - startOfName >= 3) { - const QChar firstCharacter = editor->characterAt(startOfName); + const QChar firstCharacter = m_interface->characterAt(startOfName); if (firstCharacter.isLetter() || firstCharacter == QLatin1Char('_')) { // Finally check that we're not inside a comment or string (code copied from startOfOperator) - TextEditor::BaseTextEditorWidget *edit = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); - QTextCursor tc(edit->textCursor()); + QTextCursor tc(m_interface->document()); tc.setPosition(pos); SimpleLexer tokenize; @@ -707,10 +748,8 @@ bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); - if (!tk.isComment() && !tk.isLiteral()) { - m_automaticCompletion = true; + if (!tk.isComment() && !tk.isLiteral()) return true; - } } } } @@ -719,215 +758,201 @@ bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) return false; } -int CppCodeCompletion::startCompletion(TextEditor::ITextEditor *editor) +IAssistProposal *CppCompletionAssistProcessor::createContentProposal() { - int index = startCompletionHelper(editor); - if (index != -1) { - if (m_automaticCompletion) { - const int pos = editor->position(); - const QChar ch = editor->characterAt(pos); - if (! (ch.isLetterOrNumber() || ch == QLatin1Char('_'))) { - for (int i = pos - 1;; --i) { - const QChar ch = editor->characterAt(i); - if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) { - const QString wordUnderCursor = editor->textAt(i, pos - i); - if (wordUnderCursor.at(0).isLetter() || wordUnderCursor.at(0) == QLatin1Char('_')) { - foreach (const TextEditor::CompletionItem &i, m_completions) { - if (i.text == wordUnderCursor) { - cleanup(); - return -1; - } - } - } else { - cleanup(); - return -1; + // Duplicates are kept only if they are snippets. + QSet<QString> processed; + QList<BasicProposalItem *>::iterator it = m_completions.begin(); + while (it != m_completions.end()) { + CppAssistProposalItem *item = static_cast<CppAssistProposalItem *>(*it); + if (!processed.contains(item->text()) || item->data().canConvert<QString>()) { + ++it; + if (!item->data().canConvert<QString>()) { + processed.insert(item->text()); + if (!item->isOverloaded()) { + if (Symbol *symbol = qvariant_cast<Symbol *>(item->data())) { + if (Function *funTy = symbol->type()->asFunctionType()) { + if (funTy->hasArguments()) + item->markAsOverloaded(); } - } else - break; + } } } + } else { + it = m_completions.erase(it); } - - if (m_completionOperator != T_EOF_SYMBOL) - sortCompletion(m_completions); - - // always remove duplicates - m_completions = removeDuplicates(m_completions); } - for (int i = 0; i < m_completions.size(); ++i) - m_completions[i].originalIndex = i; + m_model->loadContent(m_completions); + return new CppAssistProposal(m_startPosition, m_model.take()); +} - return index; +IAssistProposal *CppCompletionAssistProcessor::createHintProposal( + QList<CPlusPlus::Function *> functionSymbols) const +{ + IFunctionHintProposalModel *model = new CppFunctionHintModel(functionSymbols); + IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model); + return proposal; } -void CppCodeCompletion::completeObjCMsgSend(ClassOrNamespace *binding, - bool staticClassAccess) +int CppCompletionAssistProcessor::startOfOperator(int pos, + unsigned *kind, + bool wantFunctionCall) const { - QList<Scope*> memberScopes; - foreach (Symbol *s, binding->symbols()) { - if (ObjCClass *c = s->asObjCClass()) - memberScopes.append(c); - } + const QChar ch = pos > -1 ? m_interface->characterAt(pos - 1) : QChar(); + const QChar ch2 = pos > 0 ? m_interface->characterAt(pos - 2) : QChar(); + const QChar ch3 = pos > 1 ? m_interface->characterAt(pos - 3) : QChar(); + + int start = pos - activationSequenceChar(ch, ch2, ch3, kind, wantFunctionCall); + if (start != pos) { + QTextCursor tc(m_interface->document()); + tc.setPosition(pos); + + // Include completion: make sure the quote character is the first one on the line + if (*kind == T_STRING_LITERAL) { + QTextCursor s = tc; + s.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString sel = s.selectedText(); + if (sel.indexOf(QLatin1Char('"')) < sel.length() - 1) { + *kind = T_EOF_SYMBOL; + start = pos; + } + } - foreach (Scope *scope, memberScopes) { - for (unsigned i = 0; i < scope->memberCount(); ++i) { - Symbol *symbol = scope->memberAt(i); + if (*kind == T_COMMA) { + ExpressionUnderCursor expressionUnderCursor; + if (expressionUnderCursor.startOfFunctionCall(tc) == -1) { + *kind = T_EOF_SYMBOL; + start = pos; + } + } - if (ObjCMethod *method = symbol->type()->asObjCMethodType()) { - if (method->isStatic() == staticClassAccess) { - Overview oo; - const SelectorNameId *selectorName = - method->name()->asSelectorNameId(); - QString text; - QString data; - if (selectorName->hasArguments()) { - for (unsigned i = 0; i < selectorName->nameCount(); ++i) { - if (i > 0) - text += QLatin1Char(' '); - Symbol *arg = method->argumentAt(i); - text += selectorName->nameAt(i)->identifier()->chars(); - text += QLatin1Char(':'); - text += TextEditor::Snippet::kVariableDelimiter; - text += QLatin1Char('('); - text += oo(arg->type()); - text += QLatin1Char(')'); - text += oo(arg->name()); - text += TextEditor::Snippet::kVariableDelimiter; - } - } else { - text = selectorName->identifier()->chars(); - } - data = text; + SimpleLexer tokenize; + tokenize.setQtMocRunEnabled(true); + tokenize.setObjCEnabled(true); + tokenize.setSkipComments(false); + const QList<Token> &tokens = tokenize(tc.block().text(), BackwardsScanner::previousBlockState(tc.block())); + const int tokenIdx = SimpleLexer::tokenBefore(tokens, qMax(0, tc.positionInBlock() - 1)); // get the token at the left of the cursor + const Token tk = (tokenIdx == -1) ? Token() : tokens.at(tokenIdx); - if (!text.isEmpty()) { - TextEditor::CompletionItem item(this); - item.text = text; - item.data = QVariant::fromValue(data); - m_completions.append(item); - } + if (*kind == T_DOXY_COMMENT && !(tk.is(T_DOXY_COMMENT) || tk.is(T_CPP_DOXY_COMMENT))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + // Don't complete in comments or strings, but still check for include completion + else if (tk.is(T_COMMENT) || tk.is(T_CPP_COMMENT) || + (tk.isLiteral() && (*kind != T_STRING_LITERAL + && *kind != T_ANGLE_STRING_LITERAL + && *kind != T_SLASH))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + // Include completion: can be triggered by slash, but only in a string + else if (*kind == T_SLASH && (tk.isNot(T_STRING_LITERAL) && tk.isNot(T_ANGLE_STRING_LITERAL))) { + *kind = T_EOF_SYMBOL; + start = pos; + } + else if (*kind == T_LPAREN) { + if (tokenIdx > 0) { + const Token &previousToken = tokens.at(tokenIdx - 1); // look at the token at the left of T_LPAREN + switch (previousToken.kind()) { + case T_IDENTIFIER: + case T_GREATER: + case T_SIGNAL: + case T_SLOT: + break; // good + + default: + // that's a bad token :) + *kind = T_EOF_SYMBOL; + start = pos; } } } - } -} - -bool CppCodeCompletion::tryObjCCompletion(TextEditor::BaseTextEditorWidget *edit) -{ - Q_ASSERT(edit); - - int end = m_editor->position(); - while (m_editor->characterAt(end).isSpace()) - ++end; - if (m_editor->characterAt(end) != QLatin1Char(']')) - return false; - - QTextCursor tc(edit->document()); - tc.setPosition(end); - BackwardsScanner tokens(tc); - if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET)) - return false; - - const int start = tokens.startOfMatchingBrace(tokens.startToken()); - if (start == tokens.startToken()) - return false; - - const int startPos = tokens[start].begin() + tokens.startPosition(); - const QString expr = m_editor->textAt(startPos, m_editor->position() - startPos); - - const Snapshot snapshot = m_manager->snapshot(); - Document::Ptr thisDocument = snapshot.document(m_editor->file()->fileName()); - if (! thisDocument) - return false; - - typeOfExpression.init(thisDocument, snapshot); - int line = 0, column = 0; - edit->convertPosition(m_editor->position(), &line, &column); - Scope *scope = thisDocument->scopeAt(line, column); - if (!scope) - return false; - - const QList<LookupItem> items = typeOfExpression(expr, scope); - LookupContext lookupContext(thisDocument, snapshot); - - foreach (const LookupItem &item, items) { - FullySpecifiedType ty = item.type().simplified(); - if (ty->isPointerType()) { - ty = ty->asPointerType()->elementType().simplified(); - - if (NamedType *namedTy = ty->asNamedType()) { - ClassOrNamespace *binding = lookupContext.lookupType(namedTy->name(), item.scope()); - completeObjCMsgSend(binding, false); + // Check for include preprocessor directive + else if (*kind == T_STRING_LITERAL || *kind == T_ANGLE_STRING_LITERAL || *kind == T_SLASH) { + bool include = false; + if (tokens.size() >= 3) { + if (tokens.at(0).is(T_POUND) && tokens.at(1).is(T_IDENTIFIER) && (tokens.at(2).is(T_STRING_LITERAL) || + tokens.at(2).is(T_ANGLE_STRING_LITERAL))) { + const Token &directiveToken = tokens.at(1); + QString directive = tc.block().text().mid(directiveToken.begin(), + directiveToken.length()); + if (directive == QLatin1String("include") || + directive == QLatin1String("include_next") || + directive == QLatin1String("import")) { + include = true; + } + } } - } else { - if (ObjCClass *clazz = ty->asObjCClassType()) { - ClassOrNamespace *binding = lookupContext.lookupType(clazz->name(), item.scope()); - completeObjCMsgSend(binding, true); + + if (!include) { + *kind = T_EOF_SYMBOL; + start = pos; } } } - if (m_completions.isEmpty()) - return false; - - m_startPosition = m_editor->position(); - return true; + return start; } -int CppCodeCompletion::startCompletionHelper(TextEditor::ITextEditor *editor) +int CppCompletionAssistProcessor::findStartOfName(int pos) const { - TextEditor::BaseTextEditorWidget *edit = qobject_cast<TextEditor::BaseTextEditorWidget *>(editor->widget()); - if (! edit) - return -1; + if (pos == -1) + pos = m_interface->position(); + QChar chr; - m_editor = editor; + // Skip to the start of a name + do { + chr = m_interface->characterAt(--pos); + } while (chr.isLetterOrNumber() || chr == QLatin1Char('_')); + return pos + 1; +} + +int CppCompletionAssistProcessor::startCompletionHelper() +{ if (m_objcEnabled) { - if (tryObjCCompletion(edit)) + if (tryObjCCompletion()) return m_startPosition; } const int startOfName = findStartOfName(); m_startPosition = startOfName; - m_completionOperator = T_EOF_SYMBOL; + m_model->m_completionOperator = T_EOF_SYMBOL; int endOfOperator = m_startPosition; // Skip whitespace preceding this position - while (editor->characterAt(endOfOperator - 1).isSpace()) + while (m_interface->characterAt(endOfOperator - 1).isSpace()) --endOfOperator; - int endOfExpression = startOfOperator(editor, endOfOperator, - &m_completionOperator, + int endOfExpression = startOfOperator(endOfOperator, + &m_model->m_completionOperator, /*want function call =*/ true); - Core::IFile *file = editor->file(); + const Core::IFile *file = m_interface->file(); QString fileName = file->fileName(); - if (m_completionOperator == T_DOXY_COMMENT) { - for (int i = 1; i < T_DOXY_LAST_TAG; ++i) { - TextEditor::CompletionItem item(this); - item.text.append(QString::fromLatin1(doxygenTagSpell(i))); - item.icon = m_icons.keywordIcon(); - m_completions.append(item); - } - + if (m_model->m_completionOperator == T_DOXY_COMMENT) { + for (int i = 1; i < T_DOXY_LAST_TAG; ++i) + addCompletionItem(QString::fromLatin1(doxygenTagSpell(i)), m_icons.keywordIcon()); return m_startPosition; } // Pre-processor completion - if (m_completionOperator == T_POUND) { + if (m_model->m_completionOperator == T_POUND) { completePreprocessor(); m_startPosition = startOfName; return m_startPosition; } // Include completion - if (m_completionOperator == T_STRING_LITERAL - || m_completionOperator == T_ANGLE_STRING_LITERAL - || m_completionOperator == T_SLASH) { + if (m_model->m_completionOperator == T_STRING_LITERAL + || m_model->m_completionOperator == T_ANGLE_STRING_LITERAL + || m_model->m_completionOperator == T_SLASH) { - QTextCursor c = edit->textCursor(); + QTextCursor c(m_interface->document()); c.setPosition(endOfExpression); if (completeInclude(c)) m_startPosition = startOfName; @@ -935,76 +960,271 @@ int CppCodeCompletion::startCompletionHelper(TextEditor::ITextEditor *editor) } ExpressionUnderCursor expressionUnderCursor; - QTextCursor tc(edit->document()); + QTextCursor tc(m_interface->document()); - if (m_completionOperator == T_COMMA) { + if (m_model->m_completionOperator == T_COMMA) { tc.setPosition(endOfExpression); const int start = expressionUnderCursor.startOfFunctionCall(tc); if (start == -1) { - m_completionOperator = T_EOF_SYMBOL; + m_model->m_completionOperator = T_EOF_SYMBOL; return -1; } endOfExpression = start; m_startPosition = start + 1; - m_completionOperator = T_LPAREN; + m_model->m_completionOperator = T_LPAREN; } QString expression; - int startOfExpression = editor->position(); + int startOfExpression = m_interface->position(); tc.setPosition(endOfExpression); - if (m_completionOperator) { + if (m_model->m_completionOperator) { expression = expressionUnderCursor(tc); startOfExpression = endOfExpression - expression.length(); - if (m_completionOperator == T_LPAREN) { + if (m_model->m_completionOperator == T_LPAREN) { if (expression.endsWith(QLatin1String("SIGNAL"))) - m_completionOperator = T_SIGNAL; + m_model->m_completionOperator = T_SIGNAL; else if (expression.endsWith(QLatin1String("SLOT"))) - m_completionOperator = T_SLOT; + m_model->m_completionOperator = T_SLOT; - else if (editor->position() != endOfOperator) { + else if (m_interface->position() != endOfOperator) { // We don't want a function completion when the cursor isn't at the opening brace expression.clear(); - m_completionOperator = T_EOF_SYMBOL; + m_model->m_completionOperator = T_EOF_SYMBOL; m_startPosition = startOfName; - startOfExpression = editor->position(); + startOfExpression = m_interface->position(); } } } else if (expression.isEmpty()) { - while (startOfExpression > 0 && editor->characterAt(startOfExpression).isSpace()) + while (startOfExpression > 0 && m_interface->characterAt(startOfExpression).isSpace()) --startOfExpression; } int line = 0, column = 0; - edit->convertPosition(startOfExpression, &line, &column); -// qDebug() << "***** line:" << line << "column:" << column; -// qDebug() << "***** expression:" << expression; - return startCompletionInternal(edit, fileName, line, column, expression, endOfExpression); + Convenience::convertPosition(m_interface->document(), startOfExpression, &line, &column); + return startCompletionInternal(fileName, line, column, expression, endOfExpression); } -int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget *edit, - const QString fileName, - unsigned line, unsigned column, - const QString &expr, - int endOfExpression) +bool CppCompletionAssistProcessor::tryObjCCompletion() +{ + int end = m_interface->position(); + while (m_interface->characterAt(end).isSpace()) + ++end; + if (m_interface->characterAt(end) != QLatin1Char(']')) + return false; + + QTextCursor tc(m_interface->document()); + tc.setPosition(end); + BackwardsScanner tokens(tc); + if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET)) + return false; + + const int start = tokens.startOfMatchingBrace(tokens.startToken()); + if (start == tokens.startToken()) + return false; + + const int startPos = tokens[start].begin() + tokens.startPosition(); + const QString expr = m_interface->textAt(startPos, m_interface->position() - startPos); + + Document::Ptr thisDocument = m_model->m_snapshot.document(m_interface->file()->fileName()); + if (! thisDocument) + return false; + + typeOfExpression.init(thisDocument, m_model->m_snapshot); + int line = 0, column = 0; + Convenience::convertPosition(m_interface->document(), m_interface->position(), &line, &column); + Scope *scope = thisDocument->scopeAt(line, column); + if (!scope) + return false; + + const QList<LookupItem> items = typeOfExpression(expr, scope); + LookupContext lookupContext(thisDocument, m_model->m_snapshot); + + foreach (const LookupItem &item, items) { + FullySpecifiedType ty = item.type().simplified(); + if (ty->isPointerType()) { + ty = ty->asPointerType()->elementType().simplified(); + + if (NamedType *namedTy = ty->asNamedType()) { + ClassOrNamespace *binding = lookupContext.lookupType(namedTy->name(), item.scope()); + completeObjCMsgSend(binding, false); + } + } else { + if (ObjCClass *clazz = ty->asObjCClassType()) { + ClassOrNamespace *binding = lookupContext.lookupType(clazz->name(), item.scope()); + completeObjCMsgSend(binding, true); + } + } + } + + if (m_completions.isEmpty()) + return false; + + m_startPosition = m_interface->position(); + return true; +} + +void CppCompletionAssistProcessor::addCompletionItem(const QString &text, + const QIcon &icon, + int order, + const QVariant &data) +{ + BasicProposalItem *item = new CppAssistProposalItem; + item->setText(text); + item->setIcon(icon); + item->setOrder(order); + item->setData(data); + m_completions.append(item); +} + +void CppCompletionAssistProcessor::addCompletionItem(CPlusPlus::Symbol *symbol) +{ + ConvertToCompletionItem toCompletionItem; + BasicProposalItem *item = toCompletionItem(symbol); + if (item) { + item->setIcon(m_icons.iconForSymbol(symbol)); + m_completions.append(item); + } +} + +void CppCompletionAssistProcessor::completeObjCMsgSend(CPlusPlus::ClassOrNamespace *binding, + bool staticClassAccess) +{ + QList<Scope*> memberScopes; + foreach (Symbol *s, binding->symbols()) { + if (ObjCClass *c = s->asObjCClass()) + memberScopes.append(c); + } + + foreach (Scope *scope, memberScopes) { + for (unsigned i = 0; i < scope->memberCount(); ++i) { + Symbol *symbol = scope->memberAt(i); + + if (ObjCMethod *method = symbol->type()->asObjCMethodType()) { + if (method->isStatic() == staticClassAccess) { + Overview oo; + const SelectorNameId *selectorName = + method->name()->asSelectorNameId(); + QString text; + QString data; + if (selectorName->hasArguments()) { + for (unsigned i = 0; i < selectorName->nameCount(); ++i) { + if (i > 0) + text += QLatin1Char(' '); + Symbol *arg = method->argumentAt(i); + text += selectorName->nameAt(i)->identifier()->chars(); + text += QLatin1Char(':'); + text += TextEditor::Snippet::kVariableDelimiter; + text += QLatin1Char('('); + text += oo(arg->type()); + text += QLatin1Char(')'); + text += oo(arg->name()); + text += TextEditor::Snippet::kVariableDelimiter; + } + } else { + text = selectorName->identifier()->chars(); + } + data = text; + + if (!text.isEmpty()) + addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data)); + } + } + } + } +} + +bool CppCompletionAssistProcessor::completeInclude(const QTextCursor &cursor) +{ + QString directoryPrefix; + if (m_model->m_completionOperator == T_SLASH) { + QTextCursor c = cursor; + c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + QString sel = c.selectedText(); + int startCharPos = sel.indexOf(QLatin1Char('"')); + if (startCharPos == -1) { + startCharPos = sel.indexOf(QLatin1Char('<')); + m_model->m_completionOperator = T_ANGLE_STRING_LITERAL; + } else { + m_model->m_completionOperator = T_STRING_LITERAL; + } + if (startCharPos != -1) + directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1); + } + + // Make completion for all relevant includes + CppModelManagerInterface *manager = CppModelManagerInterface::instance(); + QStringList includePaths = m_interface->includePaths(); + const QString ¤tFilePath = QFileInfo(m_interface->file()->fileName()).path(); + if (!includePaths.contains(currentFilePath)) + includePaths.append(currentFilePath); + + foreach (const QString &includePath, includePaths) { + QString realPath = includePath; + if (!directoryPrefix.isEmpty()) { + realPath += QLatin1Char('/'); + realPath += directoryPrefix; + } + foreach (const QString &itemText, manager->includesInPath(realPath)) + addCompletionItem(itemText, m_icons.keywordIcon()); + } + + foreach (const QString &frameworkPath, m_interface->frameworkPaths()) { + QString realPath = frameworkPath; + if (!directoryPrefix.isEmpty()) { + realPath += QLatin1Char('/'); + realPath += directoryPrefix; + realPath += QLatin1String(".framework/Headers"); + } + foreach (const QString &itemText, manager->includesInPath(realPath)) + addCompletionItem(itemText, m_icons.keywordIcon()); + } + + return !m_completions.isEmpty(); +} + +void CppCompletionAssistProcessor::completePreprocessor() +{ + foreach (const QString &preprocessorCompletion, preprocessorCompletions) + addCompletionItem(preprocessorCompletion); + + if (objcKeywordsWanted()) + addCompletionItem(QLatin1String("import")); +} + +bool CppCompletionAssistProcessor::objcKeywordsWanted() const +{ + if (!m_objcEnabled) + return false; + + const Core::IFile *file = m_interface->file(); + QString fileName = file->fileName(); + + const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase(); + return mdb->findByFile(fileName).type() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE; +} + +int CppCompletionAssistProcessor::startCompletionInternal(const QString fileName, + unsigned line, unsigned column, + const QString &expr, + int endOfExpression) { QString expression = expr.trimmed(); - const Snapshot snapshot = m_manager->snapshot(); - Document::Ptr thisDocument = snapshot.document(fileName); + Document::Ptr thisDocument = m_model->m_snapshot.document(fileName); if (! thisDocument) return -1; - typeOfExpression.init(thisDocument, snapshot); + typeOfExpression.init(thisDocument, m_model->m_snapshot); Scope *scope = thisDocument->scopeAt(line, column); Q_ASSERT(scope != 0); if (expression.isEmpty()) { - if (m_completionOperator == T_EOF_SYMBOL || m_completionOperator == T_COLON_COLON) { + if (m_model->m_completionOperator == T_EOF_SYMBOL || m_model->m_completionOperator == T_COLON_COLON) { (void) typeOfExpression(expression, scope); globalCompletion(scope); if (m_completions.isEmpty()) @@ -1012,7 +1232,7 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget return m_startPosition; } - else if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { + else if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) { // Apply signal/slot completion on 'this' expression = QLatin1String("this"); } @@ -1021,7 +1241,7 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget QList<LookupItem> results = typeOfExpression(expression, scope, TypeOfExpression::Preprocess); if (results.isEmpty()) { - if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { + if (m_model->m_completionOperator == T_SIGNAL || m_model->m_completionOperator == T_SLOT) { if (! (expression.isEmpty() || expression == QLatin1String("this"))) { expression = QLatin1String("this"); results = typeOfExpression(expression, scope); @@ -1030,14 +1250,14 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget if (results.isEmpty()) return -1; - } else if (m_completionOperator == T_LPAREN) { + } else if (m_model->m_completionOperator == T_LPAREN) { // Find the expression that precedes the current name int index = endOfExpression; - while (m_editor->characterAt(index - 1).isSpace()) + while (m_interface->characterAt(index - 1).isSpace()) --index; index = findStartOfName(index); - QTextCursor tc(edit->document()); + QTextCursor tc(m_interface->document()); tc.setPosition(index); ExpressionUnderCursor expressionUnderCursor; @@ -1066,7 +1286,7 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget } } - switch (m_completionOperator) { + switch (m_model->m_completionOperator) { case T_LPAREN: if (completeConstructorOrFunction(results, endOfExpression, false)) return m_startPosition; @@ -1101,11 +1321,11 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget return -1; } -void CppCodeCompletion::globalCompletion(Scope *currentScope) +void CppCompletionAssistProcessor::globalCompletion(CPlusPlus::Scope *currentScope) { const LookupContext &context = typeOfExpression.context(); - if (m_completionOperator == T_COLON_COLON) { + if (m_model->m_completionOperator == T_COLON_COLON) { completeNamespace(context.globalNamespace()); return; } @@ -1168,266 +1388,29 @@ void CppCodeCompletion::globalCompletion(Scope *currentScope) addSnippets(); } -static Scope *enclosingNonTemplateScope(Symbol *symbol) -{ - if (symbol) { - if (Scope *scope = symbol->enclosingScope()) { - if (Template *templ = scope->asTemplate()) - return templ->enclosingScope(); - return scope; - } - } - return 0; -} - -static Function *asFunctionOrTemplateFunctionType(FullySpecifiedType ty) -{ - if (Function *funTy = ty->asFunctionType()) - return funTy; - else if (Template *templ = ty->asTemplateType()) { - if (Symbol *decl = templ->declaration()) - return decl->asFunction(); - } - return 0; -} - -static Class *asClassOrTemplateClassType(FullySpecifiedType ty) -{ - if (Class *classTy = ty->asClassType()) - return classTy; - else if (Template *templ = ty->asTemplateType()) { - if (Symbol *decl = templ->declaration()) - return decl->asClass(); - } - return 0; -} - -bool CppCodeCompletion::completeConstructorOrFunction(const QList<LookupItem> &results, - int endOfExpression, bool toolTipOnly) +bool CppCompletionAssistProcessor::completeMember(const QList<CPlusPlus::LookupItem> &baseResults) { const LookupContext &context = typeOfExpression.context(); - QList<Function *> functions; - - foreach (const LookupItem &result, results) { - FullySpecifiedType exprTy = result.type().simplified(); - - if (Class *klass = asClassOrTemplateClassType(exprTy)) { - const Name *className = klass->name(); - if (! className) - continue; // nothing to do for anonymous classes. - - for (unsigned i = 0; i < klass->memberCount(); ++i) { - Symbol *member = klass->memberAt(i); - const Name *memberName = member->name(); - - if (! memberName) - continue; // skip anonymous member. - - else if (memberName->isQualifiedNameId()) - continue; // skip - - if (Function *funTy = member->type()->asFunctionType()) { - if (memberName->isEqualTo(className)) { - // it's a ctor. - functions.append(funTy); - } - } - } - - break; - } - } - - if (functions.isEmpty()) { - foreach (const LookupItem &result, results) { - FullySpecifiedType ty = result.type().simplified(); - - if (Function *fun = asFunctionOrTemplateFunctionType(ty)) { - - if (! fun->name()) - continue; - else if (! functions.isEmpty() && enclosingNonTemplateScope(functions.first()) != enclosingNonTemplateScope(fun)) - continue; // skip fun, it's an hidden declaration. - - bool newOverload = true; - - foreach (Function *f, functions) { - if (fun->isEqualTo(f)) { - newOverload = false; - break; - } - } - - if (newOverload) - functions.append(fun); - } - } - } - - if (functions.isEmpty()) { - const Name *functionCallOp = context.control()->operatorNameId(OperatorNameId::FunctionCallOp); - - foreach (const LookupItem &result, results) { - FullySpecifiedType ty = result.type().simplified(); - Scope *scope = result.scope(); - - if (NamedType *namedTy = ty->asNamedType()) { - if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) { - foreach (const LookupItem &r, b->lookup(functionCallOp)) { - Symbol *overload = r.declaration(); - FullySpecifiedType overloadTy = overload->type().simplified(); - - if (Function *funTy = overloadTy->asFunctionType()) { - functions.append(funTy); - } - } - } - } - } - } - - // There are two different kinds of completion we want to provide: - // 1. If this is a function call, we want to pop up a tooltip that shows the user - // the possible overloads with their argument types and names. - // 2. If this is a function definition, we want to offer autocompletion of - // the function signature. - - // check if function signature autocompletion is appropriate - // Also check if the function name is a destructor name. - bool isDestructor = false; - if (! functions.isEmpty() && ! toolTipOnly) { - - // function definitions will only happen in class or namespace scope, - // so get the current location's enclosing scope. - - // get current line and column - TextEditor::BaseTextEditorWidget *edit = qobject_cast<TextEditor::BaseTextEditorWidget *>(m_editor->widget()); - int lineSigned = 0, columnSigned = 0; - edit->convertPosition(m_editor->position(), &lineSigned, &columnSigned); - unsigned line = lineSigned, column = columnSigned; - - // find a scope that encloses the current location, starting from the lastVisibileSymbol - // and moving outwards - - Scope *sc = context.thisDocument()->scopeAt(line, column); - - if (sc && (sc->isClass() || sc->isNamespace())) { - // It may still be a function call. If the whole line parses as a function - // declaration, we should be certain that it isn't. - bool autocompleteSignature = false; - - QTextCursor tc(edit->document()); - tc.setPosition(endOfExpression); - BackwardsScanner bs(tc); - const int startToken = bs.startToken(); - int lineStartToken = bs.startOfLine(startToken); - // make sure the required tokens are actually available - bs.LA(startToken - lineStartToken); - QString possibleDecl = bs.mid(lineStartToken).trimmed().append("();"); - - Document::Ptr doc = Document::create(QLatin1String("<completion>")); - doc->setSource(possibleDecl.toLatin1()); - if (doc->parse(Document::ParseDeclaration)) { - doc->check(); - if (SimpleDeclarationAST *sd = doc->translationUnit()->ast()->asSimpleDeclaration()) { - if (sd->declarator_list && - sd->declarator_list && sd->declarator_list->value->postfix_declarator_list - && sd->declarator_list->value->postfix_declarator_list->value->asFunctionDeclarator()) { - - autocompleteSignature = true; - - CoreDeclaratorAST *coreDecl = sd->declarator_list->value->core_declarator; - if (coreDecl && coreDecl->asDeclaratorId() && coreDecl->asDeclaratorId()->name) { - NameAST *declName = coreDecl->asDeclaratorId()->name; - if (declName->asDestructorName()) { - isDestructor = true; - } else if (QualifiedNameAST *qName = declName->asQualifiedName()) { - if (qName->unqualified_name && qName->unqualified_name->asDestructorName()) - isDestructor = true; - } - } - } - } - } - - if (autocompleteSignature && !isDestructor) { - // set up signature autocompletion - foreach (Function *f, functions) { - Overview overview; - overview.setShowArgumentNames(true); - overview.setShowDefaultArguments(false); - - // gets: "parameter list) cv-spec", - QString completion = overview(f->type()).mid(1); - - TextEditor::CompletionItem item(this); - item.text = completion; - item.data = QVariant::fromValue(CompleteFunctionDeclaration(f)); - m_completions.append(item); - } - return true; - } - } - } - - if (! functions.empty() && !isDestructor) { - // set up function call tooltip - - // Recreate if necessary - if (!m_functionArgumentWidget) - m_functionArgumentWidget = new FunctionArgumentWidget; - - m_functionArgumentWidget->showFunctionHint(functions, - typeOfExpression.context(), - m_startPosition); - } - - return false; -} - -bool CppCodeCompletion::completeMember(const QList<LookupItem> &baseResults) -{ - const LookupContext &context = typeOfExpression.context(); - -// if (debug) -// qDebug() << Q_FUNC_INFO << __LINE__; if (baseResults.isEmpty()) return false; ResolveExpression resolveExpression(context); - bool replacedDotOperator = false; - - if (ClassOrNamespace *binding = resolveExpression.baseExpression(baseResults, - m_completionOperator, - &replacedDotOperator)) { -// if (debug) -// qDebug() << "cool we got a binding for the base expression"; - - if (replacedDotOperator && binding) { - // Replace . with -> - int length = m_editor->position() - m_startPosition + 1; - m_editor->setCursorPosition(m_startPosition - 1); - m_editor->replace(length, QLatin1String("->")); - ++m_startPosition; - } - + if (ClassOrNamespace *binding = + resolveExpression.baseExpression(baseResults, + m_model->m_completionOperator, + &m_model->m_replaceDotForArrow)) { if (binding) completeClass(binding, /*static lookup = */ false); return ! m_completions.isEmpty(); } -// if (debug) { -// Overview oo; -// qDebug() << "hmm, got:" << oo(baseResults.first().type()); -// } - return false; } -bool CppCodeCompletion::completeScope(const QList<LookupItem> &results) +bool CppCompletionAssistProcessor::completeScope(const QList<CPlusPlus::LookupItem> &results) { const LookupContext &context = typeOfExpression.context(); if (results.isEmpty()) @@ -1461,162 +1444,7 @@ bool CppCodeCompletion::completeScope(const QList<LookupItem> &results) return ! m_completions.isEmpty(); } -void CppCodeCompletion::addKeywords() -{ - int keywordLimit = T_FIRST_OBJC_AT_KEYWORD; - if (objcKeywordsWanted()) - keywordLimit = T_LAST_OBJC_AT_KEYWORD + 1; - - // keyword completion items. - for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i) { - TextEditor::CompletionItem item(this); - item.text = QLatin1String(Token::name(i)); - item.icon = m_icons.keywordIcon(); - m_completions.append(item); - } -} - -void CppCodeCompletion::addMacros(const QString &fileName, const Snapshot &snapshot) -{ - QSet<QString> processed; - QSet<QString> definedMacros; - - addMacros_helper(snapshot, fileName, &processed, &definedMacros); - - foreach (const QString ¯oName, definedMacros) { - TextEditor::CompletionItem item(this); - item.text = macroName; - item.icon = m_icons.macroIcon(); - m_completions.append(item); - } -} - -void CppCodeCompletion::addMacros_helper(const Snapshot &snapshot, - const QString &fileName, - QSet<QString> *processed, - QSet<QString> *definedMacros) -{ - Document::Ptr doc = snapshot.document(fileName); - - if (! doc || processed->contains(doc->fileName())) - return; - - processed->insert(doc->fileName()); - - foreach (const Document::Include &i, doc->includes()) { - addMacros_helper(snapshot, i.fileName(), processed, definedMacros); - } - - foreach (const Macro ¯o, doc->definedMacros()) { - const QString macroName = QString::fromUtf8(macro.name().constData(), macro.name().length()); - if (! macro.isHidden()) - definedMacros->insert(macroName); - else - definedMacros->remove(macroName); - } -} - -void CppCodeCompletion::addCompletionItem(Symbol *symbol) -{ - ConvertToCompletionItem toCompletionItem(this); - TextEditor::CompletionItem item = toCompletionItem(symbol); - if (item.isValid()) - m_completions.append(item); -} - -bool CppCodeCompletion::completeInclude(const QTextCursor &cursor) -{ - QString directoryPrefix; - if (m_completionOperator == T_SLASH) { - QTextCursor c = cursor; - c.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); - QString sel = c.selectedText(); - int startCharPos = sel.indexOf(QLatin1Char('"')); - if (startCharPos == -1) { - startCharPos = sel.indexOf(QLatin1Char('<')); - m_completionOperator = T_ANGLE_STRING_LITERAL; - } else { - m_completionOperator = T_STRING_LITERAL; - } - if (startCharPos != -1) - directoryPrefix = sel.mid(startCharPos + 1, sel.length() - 1); - } - - // Make completion for all relevant includes - if (ProjectExplorer::Project *project = ProjectExplorer::ProjectExplorerPlugin::instance()->currentProject()) { - QStringList includePaths = m_manager->projectInfo(project).includePaths; - const QString currentFilePath = QFileInfo(m_editor->file()->fileName()).path(); - if (!includePaths.contains(currentFilePath)) - includePaths.append(currentFilePath); - - foreach (const QString &includePath, includePaths) { - QString realPath = includePath; - if (!directoryPrefix.isEmpty()) { - realPath += QLatin1Char('/'); - realPath += directoryPrefix; - } - foreach (const QString &itemText, m_manager->includesInPath(realPath)) { - TextEditor::CompletionItem item(this); - item.text += itemText; - // TODO: Icon for include files - item.icon = m_icons.keywordIcon(); - m_completions.append(item); - } - } - - QStringList frameworkPaths = m_manager->projectInfo(project).frameworkPaths; - foreach (const QString &frameworkPath, frameworkPaths) { - QString realPath = frameworkPath; - if (!directoryPrefix.isEmpty()) { - realPath += QLatin1Char('/'); - realPath += directoryPrefix; - realPath += QLatin1String(".framework/Headers"); - } - foreach (const QString &itemText, m_manager->includesInPath(realPath)) { - TextEditor::CompletionItem item(this); - item.text += itemText; - // TODO: Icon for include files - item.icon = m_icons.keywordIcon(); - m_completions.append(item); - } - } - } - - return !m_completions.isEmpty(); -} - -QStringList CppCodeCompletion::preprocessorCompletions - = QStringList() - << QLatin1String("define") - << QLatin1String("error") - << QLatin1String("include") - << QLatin1String("line") - << QLatin1String("pragma") - << QLatin1String("undef") - << QLatin1String("if") - << QLatin1String("ifdef") - << QLatin1String("ifndef") - << QLatin1String("elif") - << QLatin1String("else") - << QLatin1String("endif") - ; - -void CppCodeCompletion::completePreprocessor() -{ - TextEditor::CompletionItem item(this); - - foreach (const QString &preprocessorCompletion, preprocessorCompletions) { - item.text = preprocessorCompletion; - m_completions.append(item); - } - - if (objcKeywordsWanted()) { - item.text = QLatin1String("import"); - m_completions.append(item); - } -} - -void CppCodeCompletion::completeNamespace(ClassOrNamespace *b) +void CppCompletionAssistProcessor::completeNamespace(CPlusPlus::ClassOrNamespace *b) { QSet<ClassOrNamespace *> bindingsVisited; QList<ClassOrNamespace *> bindingsToVisit; @@ -1657,7 +1485,7 @@ void CppCodeCompletion::completeNamespace(ClassOrNamespace *b) } } -void CppCodeCompletion::completeClass(ClassOrNamespace *b, bool staticLookup) +void CppCompletionAssistProcessor::completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup) { QSet<ClassOrNamespace *> bindingsVisited; QList<ClassOrNamespace *> bindingsToVisit; @@ -1709,15 +1537,14 @@ void CppCodeCompletion::completeClass(ClassOrNamespace *b, bool staticLookup) } } -bool CppCodeCompletion::completeQtMethod(const QList<LookupItem> &results, - bool wantSignals) +bool CppCompletionAssistProcessor::completeQtMethod(const QList<CPlusPlus::LookupItem> &results, bool wantSignals) { if (results.isEmpty()) return false; const LookupContext &context = typeOfExpression.context(); - ConvertToCompletionItem toCompletionItem(this); + ConvertToCompletionItem toCompletionItem; Overview o; o.setShowReturnTypes(false); o.setShowArgumentNames(false); @@ -1770,14 +1597,14 @@ bool CppCodeCompletion::completeQtMethod(const QList<LookupItem> &results, continue; else if (! wantSignals && ! fun->isSlot()) continue; - TextEditor::CompletionItem item = toCompletionItem(fun); - if (item.isValid()) { + BasicProposalItem *item = toCompletionItem(fun); + if (item) { unsigned count = fun->argumentCount(); while (true) { - TextEditor::CompletionItem ci = item; + BasicProposalItem *ci = item; QString signature; - signature += overview.prettyName(fun->name()); + signature += Overview().prettyName(fun->name()); signature += QLatin1Char('('); for (unsigned i = 0; i < count; ++i) { Symbol *arg = fun->argumentAt(i); @@ -1795,7 +1622,7 @@ bool CppCodeCompletion::completeQtMethod(const QList<LookupItem> &results, if (! signatures.contains(signature)) { signatures.insert(signature); - ci.text = signature; // fix the completion item. + ci->setText(signature); // fix the completion item. m_completions.append(ci); } @@ -1812,312 +1639,228 @@ bool CppCodeCompletion::completeQtMethod(const QList<LookupItem> &results, return ! m_completions.isEmpty(); } -void CppCodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) +void CppCompletionAssistProcessor::addSnippets() { - const int length = m_editor->position() - m_startPosition; - if (length < 0) - return; - - const QString key = m_editor->textAt(m_startPosition, length); - - if (length == 0) - *completions = m_completions; - else if (length > 0) { - /* Close on the trailing slash for include completion, to enable the slash to - * trigger a new completion list. */ - if ((m_completionOperator == T_STRING_LITERAL || - m_completionOperator == T_ANGLE_STRING_LITERAL) && key.endsWith(QLatin1Char('/'))) - return; - - if (m_completionOperator != T_LPAREN) { - filter(m_completions, completions, key); - - } else if (m_completionOperator == T_LPAREN || - m_completionOperator == T_SIGNAL || - m_completionOperator == T_SLOT) { - foreach (const TextEditor::CompletionItem &item, m_completions) { - if (item.text.startsWith(key, Qt::CaseInsensitive)) { - completions->append(item); - } - } - } - } - - if (m_automaticCompletion && completions->size() == 1 && key == completions->first().text) { - completions->clear(); - } + m_completions.append(m_snippetCollector.collect()); } -QList<TextEditor::CompletionItem> CppCodeCompletion::removeDuplicates(const QList<TextEditor::CompletionItem> &items) +void CppCompletionAssistProcessor::addKeywords() { - // Duplicates are kept only if they are snippets. - QList<TextEditor::CompletionItem> uniquelist; - QSet<QString> processed; - foreach (const TextEditor::CompletionItem &item, items) { - if (!processed.contains(item.text) || item.isSnippet) { - uniquelist.append(item); - if (!item.isSnippet) { - processed.insert(item.text); - if (Symbol *symbol = qvariant_cast<Symbol *>(item.data)) { - if (Function *funTy = symbol->type()->asFunctionType()) { - if (funTy->hasArguments()) - ++uniquelist.back().duplicateCount; - } - } - } - } - } + int keywordLimit = T_FIRST_OBJC_AT_KEYWORD; + if (objcKeywordsWanted()) + keywordLimit = T_LAST_OBJC_AT_KEYWORD + 1; - return uniquelist; + // keyword completion items. + for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i) + addCompletionItem(QLatin1String(Token::name(i)), m_icons.keywordIcon()); } -QList<TextEditor::CompletionItem> CppCodeCompletion::getCompletions() +void CppCompletionAssistProcessor::addMacros(const QString &fileName, const CPlusPlus::Snapshot &snapshot) { - QList<TextEditor::CompletionItem> completionItems; - completions(&completionItems); + QSet<QString> processed; + QSet<QString> definedMacros; + + addMacros_helper(snapshot, fileName, &processed, &definedMacros); - return completionItems; + foreach (const QString ¯oName, definedMacros) + addCompletionItem(macroName, m_icons.macroIcon()); } -bool CppCodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) +void CppCompletionAssistProcessor::addMacros_helper(const CPlusPlus::Snapshot &snapshot, + const QString &fileName, + QSet<QString> *processed, + QSet<QString> *definedMacros) { - if (m_automaticCompletion) - return false; - - if (item.data.canConvert<QString>()) // snippet - return false; - - if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) - return typedChar == QLatin1Char('(') - || typedChar == QLatin1Char(','); + Document::Ptr doc = snapshot.document(fileName); - if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) - return typedChar == QLatin1Char('/') - && item.text.endsWith(QLatin1Char('/')); + if (! doc || processed->contains(doc->fileName())) + return; - if (item.data.value<Symbol *>()) - return typedChar == QLatin1Char(':') - || typedChar == QLatin1Char(';') - || typedChar == QLatin1Char('.') - || typedChar == QLatin1Char(',') - || typedChar == QLatin1Char('('); + processed->insert(doc->fileName()); - if (item.data.canConvert<CompleteFunctionDeclaration>()) - return typedChar == QLatin1Char('('); + foreach (const Document::Include &i, doc->includes()) { + addMacros_helper(snapshot, i.fileName(), processed, definedMacros); + } - return false; + foreach (const Macro ¯o, doc->definedMacros()) { + const QString macroName = QString::fromUtf8(macro.name().constData(), macro.name().length()); + if (! macro.isHidden()) + definedMacros->insert(macroName); + else + definedMacros->remove(macroName); + } } -void CppCodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) +bool CppCompletionAssistProcessor::completeConstructorOrFunction(const QList<CPlusPlus::LookupItem> &results, + int endOfExpression, + bool toolTipOnly) { - m_shouldRestartCompletion = false; // Enabled for specific cases + const LookupContext &context = typeOfExpression.context(); + QList<Function *> functions; - Symbol *symbol = 0; + foreach (const LookupItem &result, results) { + FullySpecifiedType exprTy = result.type().simplified(); - if (item.data.isValid()) { - if (item.data.canConvert<QString>()) { - TextEditor::BaseTextEditorWidget *edit = qobject_cast<TextEditor::BaseTextEditorWidget *>(m_editor->widget()); - QTextCursor tc = edit->textCursor(); - tc.setPosition(m_startPosition, QTextCursor::KeepAnchor); - edit->insertCodeSnippet(tc, item.data.toString()); - return; - } else { - symbol = item.data.value<Symbol *>(); - } - } + if (Class *klass = asClassOrTemplateClassType(exprTy)) { + const Name *className = klass->name(); + if (! className) + continue; // nothing to do for anonymous classes. - QString toInsert; - QString extraChars; - int extraLength = 0; - int cursorOffset = 0; + for (unsigned i = 0; i < klass->memberCount(); ++i) { + Symbol *member = klass->memberAt(i); + const Name *memberName = member->name(); - bool autoParenthesesEnabled = true; + if (! memberName) + continue; // skip anonymous member. - if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { - toInsert = item.text; - extraChars += QLatin1Char(')'); + else if (memberName->isQualifiedNameId()) + continue; // skip - if (typedChar == QLatin1Char('(')) // Eat the opening parenthesis - typedChar = QChar(); - } else if (m_completionOperator == T_STRING_LITERAL || m_completionOperator == T_ANGLE_STRING_LITERAL) { - toInsert = item.text; - if (!toInsert.endsWith(QLatin1Char('/'))) { - extraChars += QLatin1Char((m_completionOperator == T_ANGLE_STRING_LITERAL) ? '>' : '"'); - } else { - m_shouldRestartCompletion = true; // Re-trigger for subdirectory - if (typedChar == QLatin1Char('/')) // Eat the slash - typedChar = QChar(); + if (Function *funTy = member->type()->asFunctionType()) { + if (memberName->isEqualTo(className)) { + // it's a ctor. + functions.append(funTy); + } + } + } + + break; } - } else { - toInsert = item.text; + } - //qDebug() << "current symbol:" << overview.prettyName(symbol->name()) - //<< overview.prettyType(symbol->type()); + if (functions.isEmpty()) { + foreach (const LookupItem &result, results) { + FullySpecifiedType ty = result.type().simplified(); - const bool autoInsertBrackets = completionSettings().m_autoInsertBrackets; + if (Function *fun = asFunctionOrTemplateFunctionType(ty)) { - if (autoInsertBrackets && symbol && symbol->type()) { - if (Function *function = symbol->type()->asFunctionType()) { - // If the member is a function, automatically place the opening parenthesis, - // except when it might take template parameters. - if (! function->hasReturnType() && (function->unqualifiedName() && !function->unqualifiedName()->isDestructorNameId())) { - // Don't insert any magic, since the user might have just wanted to select the class + if (! fun->name()) + continue; + else if (! functions.isEmpty() && enclosingNonTemplateScope(functions.first()) != enclosingNonTemplateScope(fun)) + continue; // skip fun, it's an hidden declaration. - /// ### port me -#if 0 - } else if (function->templateParameterCount() != 0 && typedChar != QLatin1Char('(')) { - // If there are no arguments, then we need the template specification - if (function->argumentCount() == 0) { - extraChars += QLatin1Char('<'); + bool newOverload = true; + + foreach (Function *f, functions) { + if (fun->isEqualTo(f)) { + newOverload = false; + break; } -#endif - } else if (! function->isAmbiguous()) { - // When the user typed the opening parenthesis, he'll likely also type the closing one, - // in which case it would be annoying if we put the cursor after the already automatically - // inserted closing parenthesis. - const bool skipClosingParenthesis = typedChar != QLatin1Char('('); + } - if (completionSettings().m_spaceAfterFunctionName) - extraChars += QLatin1Char(' '); - extraChars += QLatin1Char('('); - if (typedChar == QLatin1Char('(')) - typedChar = QChar(); + if (newOverload) + functions.append(fun); + } + } + } - // If the function doesn't return anything, automatically place the semicolon, - // unless we're doing a scope completion (then it might be function definition). - const QChar characterAtCursor = m_editor->characterAt(m_editor->position()); - bool endWithSemicolon = typedChar == QLatin1Char(';') - || (function->returnType()->isVoidType() && m_completionOperator != T_COLON_COLON); - const QChar semicolon = typedChar.isNull() ? QLatin1Char(';') : typedChar; + if (functions.isEmpty()) { + const Name *functionCallOp = context.control()->operatorNameId(OperatorNameId::FunctionCallOp); - if (endWithSemicolon && characterAtCursor == semicolon) { - endWithSemicolon = false; - typedChar = QChar(); - } + foreach (const LookupItem &result, results) { + FullySpecifiedType ty = result.type().simplified(); + Scope *scope = result.scope(); - // If the function takes no arguments, automatically place the closing parenthesis - if (item.duplicateCount == 0 && ! function->hasArguments() && skipClosingParenthesis) { - extraChars += QLatin1Char(')'); - if (endWithSemicolon) { - extraChars += semicolon; - typedChar = QChar(); - } - } else if (autoParenthesesEnabled) { - const QChar lookAhead = m_editor->characterAt(m_editor->position() + 1); - if (MatchingText::shouldInsertMatchingText(lookAhead)) { - extraChars += QLatin1Char(')'); - --cursorOffset; - if (endWithSemicolon) { - extraChars += semicolon; - --cursorOffset; - typedChar = QChar(); - } + if (NamedType *namedTy = ty->asNamedType()) { + if (ClassOrNamespace *b = context.lookupType(namedTy->name(), scope)) { + foreach (const LookupItem &r, b->lookup(functionCallOp)) { + Symbol *overload = r.declaration(); + FullySpecifiedType overloadTy = overload->type().simplified(); + + if (Function *funTy = overloadTy->asFunctionType()) { + functions.append(funTy); } - // TODO: When an opening parenthesis exists, the "semicolon" should really be - // inserted after the matching closing parenthesis. } } } } - - if (autoInsertBrackets && item.data.canConvert<CompleteFunctionDeclaration>()) { - if (typedChar == QLatin1Char('(')) - typedChar = QChar(); - - // everything from the closing parenthesis on are extra chars, to - // make sure an auto-inserted ")" gets replaced by ") const" if necessary - int closingParen = toInsert.lastIndexOf(QLatin1Char(')')); - extraChars = toInsert.mid(closingParen); - toInsert.truncate(closingParen); - } } - // Append an unhandled typed character, adjusting cursor offset when it had been adjusted before - if (!typedChar.isNull()) { - extraChars += typedChar; - if (cursorOffset != 0) - --cursorOffset; - } + // There are two different kinds of completion we want to provide: + // 1. If this is a function call, we want to pop up a tooltip that shows the user + // the possible overloads with their argument types and names. + // 2. If this is a function definition, we want to offer autocompletion of + // the function signature. - if (!extraChars.isEmpty() && extraChars.length() + cursorOffset > 0) { - const QChar c = extraChars.at(extraChars.length() - 1 + cursorOffset); - if (c == QLatin1Char('.') || c == QLatin1Char('(')) - m_shouldRestartCompletion = true; - } + // check if function signature autocompletion is appropriate + // Also check if the function name is a destructor name. + bool isDestructor = false; + if (! functions.isEmpty() && ! toolTipOnly) { - // Avoid inserting characters that are already there - for (int i = 0; i < extraChars.length(); ++i) { - const QChar a = extraChars.at(i); - const QChar b = m_editor->characterAt(m_editor->position() + i); - if (a == b) - ++extraLength; - else - break; - } + // function definitions will only happen in class or namespace scope, + // so get the current location's enclosing scope. - toInsert += extraChars; + // get current line and column + int lineSigned = 0, columnSigned = 0; + Convenience::convertPosition(m_interface->document(), m_interface->position(), &lineSigned, &columnSigned); + unsigned line = lineSigned, column = columnSigned; - // Insert the remainder of the name - int length = m_editor->position() - m_startPosition + extraLength; - m_editor->setCursorPosition(m_startPosition); - m_editor->replace(length, toInsert); - if (cursorOffset) - m_editor->setCursorPosition(m_editor->position() + cursorOffset); -} + // find a scope that encloses the current location, starting from the lastVisibileSymbol + // and moving outwards -bool CppCodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) -{ - if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { - return false; - } else if (completionItems.count() == 1) { - complete(completionItems.first(), QChar()); - return true; - } else if (m_completionOperator != T_LPAREN) { - return TextEditor::ICompletionCollector::partiallyComplete(completionItems); - } + Scope *sc = context.thisDocument()->scopeAt(line, column); - return false; -} + if (sc && (sc->isClass() || sc->isNamespace())) { + // It may still be a function call. If the whole line parses as a function + // declaration, we should be certain that it isn't. + bool autocompleteSignature = false; -void CppCodeCompletion::cleanup() -{ - m_automaticCompletion = false; - m_completions.clear(); + QTextCursor tc(m_interface->document()); + tc.setPosition(endOfExpression); + BackwardsScanner bs(tc); + const int startToken = bs.startToken(); + int lineStartToken = bs.startOfLine(startToken); + // make sure the required tokens are actually available + bs.LA(startToken - lineStartToken); + QString possibleDecl = bs.mid(lineStartToken).trimmed().append("();"); - // Set empty map in order to avoid referencing old versions of the documents - // until the next completion - typeOfExpression.reset(); -} + Document::Ptr doc = Document::create(QLatin1String("<completion>")); + doc->setSource(possibleDecl.toLatin1()); + if (doc->parse(Document::ParseDeclaration)) { + doc->check(); + if (SimpleDeclarationAST *sd = doc->translationUnit()->ast()->asSimpleDeclaration()) { + if (sd->declarator_list && + sd->declarator_list && sd->declarator_list->value->postfix_declarator_list + && sd->declarator_list->value->postfix_declarator_list->value->asFunctionDeclarator()) { -int CppCodeCompletion::findStartOfName(int pos) const -{ - if (pos == -1) - pos = m_editor->position(); - QChar chr; + autocompleteSignature = true; - // Skip to the start of a name - do { - chr = m_editor->characterAt(--pos); - } while (chr.isLetterOrNumber() || chr == QLatin1Char('_')); + CoreDeclaratorAST *coreDecl = sd->declarator_list->value->core_declarator; + if (coreDecl && coreDecl->asDeclaratorId() && coreDecl->asDeclaratorId()->name) { + NameAST *declName = coreDecl->asDeclaratorId()->name; + if (declName->asDestructorName()) { + isDestructor = true; + } else if (QualifiedNameAST *qName = declName->asQualifiedName()) { + if (qName->unqualified_name && qName->unqualified_name->asDestructorName()) + isDestructor = true; + } + } + } + } + } - return pos + 1; -} + if (autocompleteSignature && !isDestructor) { + // set up signature autocompletion + foreach (Function *f, functions) { + Overview overview; + overview.setShowArgumentNames(true); + overview.setShowDefaultArguments(false); -bool CppCodeCompletion::objcKeywordsWanted() const -{ - if (!m_objcEnabled) - return false; + // gets: "parameter list) cv-spec", + QString completion = overview(f->type()).mid(1); - Core::IFile *file = m_editor->file(); - QString fileName = file->fileName(); + addCompletionItem(completion, QIcon(), 0, + QVariant::fromValue(CompleteFunctionDeclaration(f))); + } + return true; + } + } + } - const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase(); - return mdb->findByFile(fileName).type() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE; -} + if (! functions.empty() && !isDestructor) { + m_hintProposal = createHintProposal(functions); + return true; + } -void CppCodeCompletion::addSnippets() -{ - m_completions.append(m_snippetProvider.getSnippets(this)); + return false; } - -#include "cppcodecompletion.moc" diff --git a/src/plugins/cpptools/cppcompletionassist.h b/src/plugins/cpptools/cppcompletionassist.h new file mode 100644 index 0000000000..8ed0282736 --- /dev/null +++ b/src/plugins/cpptools/cppcompletionassist.h @@ -0,0 +1,172 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef CPPCOMPLETIONASSIST_H +#define CPPCOMPLETIONASSIST_H + +#include <cplusplus/Icons.h> +#include <cplusplus/Overview.h> +#include <cplusplus/TypeOfExpression.h> +#include <cplusplus/CppDocument.h> + +#include <texteditor/codeassist/completionassistprovider.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/snippets/snippetassistcollector.h> +#include <texteditor/codeassist/defaultassistinterface.h> + +#include <QtCore/QStringList> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QTextCursor; +QT_END_NAMESPACE + +namespace CPlusPlus { +class LookupItem; +class ClassOrNamespace; +class Function; +class LookupContext; +} + +namespace CppTools { +namespace Internal { + +class CppCompletionAssistInterface; +class CppAssistProposalModel; + +class CppCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + virtual bool supportsEditor(const QString &editorId) const; + virtual int activationCharSequenceLength() const; + virtual bool isActivationCharSequence(const QString &sequence) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; +}; + +class CppCompletionAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + CppCompletionAssistProcessor(); + virtual ~CppCompletionAssistProcessor(); + + virtual TextEditor::IAssistProposal *perform(const TextEditor::IAssistInterface *interface); + +private: + TextEditor::IAssistProposal *createContentProposal(); + TextEditor::IAssistProposal *createHintProposal(QList<CPlusPlus::Function *> symbols) const; + bool accepts() const; + + int startOfOperator(int pos, unsigned *kind, bool wantFunctionCall) const; + int findStartOfName(int pos = -1) const; + int startCompletionHelper(); + bool tryObjCCompletion(); + bool objcKeywordsWanted() const; + int startCompletionInternal(const QString fileName, + unsigned line, unsigned column, + const QString &expression, + int endOfExpression); + + void completeObjCMsgSend(CPlusPlus::ClassOrNamespace *binding, bool staticClassAccess); + bool completeInclude(const QTextCursor &cursor); + void completePreprocessor(); + bool completeConstructorOrFunction(const QList<CPlusPlus::LookupItem> &results, + int endOfExpression, + bool toolTipOnly); + bool completeMember(const QList<CPlusPlus::LookupItem> &results); + bool completeScope(const QList<CPlusPlus::LookupItem> &results); + void completeNamespace(CPlusPlus::ClassOrNamespace *binding); + void completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup = true); + bool completeQtMethod(const QList<CPlusPlus::LookupItem> &results, bool wantSignals); + bool completeSignal(const QList<CPlusPlus::LookupItem> &results) + { return completeQtMethod(results, true); } + bool completeSlot(const QList<CPlusPlus::LookupItem> &results) + { return completeQtMethod(results, false); } + void globalCompletion(CPlusPlus::Scope *scope); + + void addCompletionItem(const QString &text, + const QIcon &icon = QIcon(), + int order = 0, + const QVariant &data = QVariant()); + void addCompletionItem(CPlusPlus::Symbol *symbol); + void addSnippets(); + void addKeywords(); + void addMacros(const QString &fileName, const CPlusPlus::Snapshot &snapshot); + void addMacros_helper(const CPlusPlus::Snapshot &snapshot, + const QString &fileName, + QSet<QString> *processed, + QSet<QString> *definedMacros); + + int m_startPosition; + bool m_objcEnabled; + QScopedPointer<const CppCompletionAssistInterface> m_interface; + QList<TextEditor::BasicProposalItem *> m_completions; + TextEditor::SnippetAssistCollector m_snippetCollector; + const CppCompletionAssistProvider *m_provider; + CPlusPlus::Icons m_icons; + CPlusPlus::TypeOfExpression typeOfExpression; + QStringList preprocessorCompletions; + QScopedPointer<CppAssistProposalModel> m_model; + TextEditor::IAssistProposal *m_hintProposal; +}; + +class CppCompletionAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + CppCompletionAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + TextEditor::AssistReason reason, + const CPlusPlus::Snapshot &snapshot, + const QStringList &includePaths, + const QStringList &frameworkPaths) + : TextEditor::DefaultAssistInterface(document, position, file, reason) + , m_snapshot(snapshot) + , m_includePaths(includePaths) + , m_frameworkPaths(frameworkPaths) + {} + + const CPlusPlus::Snapshot &snapshot() const { return m_snapshot; } + const QStringList &includePaths() const { return m_includePaths; } + const QStringList &frameworkPaths() const { return m_frameworkPaths; } + +private: + CPlusPlus::Snapshot m_snapshot; + QStringList m_includePaths; + QStringList m_frameworkPaths; +}; + +} // Internal +} // CppTools + +Q_DECLARE_METATYPE(CPlusPlus::Symbol *) + +#endif // CPPCOMPLETIONASSIST_H diff --git a/src/plugins/cpptools/cpptools.pro b/src/plugins/cpptools/cpptools.pro index f970e74860..ca8d89ce8a 100644 --- a/src/plugins/cpptools/cpptools.pro +++ b/src/plugins/cpptools/cpptools.pro @@ -10,7 +10,6 @@ INCLUDEPATH += . DEFINES += CPPTOOLS_LIBRARY HEADERS += completionsettingspage.h \ cppclassesfilter.h \ - cppcodecompletion.h \ cppcurrentdocumentfilter.h \ cppfunctionsfilter.h \ cppmodelmanager.h \ @@ -28,11 +27,11 @@ HEADERS += completionsettingspage.h \ uicodecompletionsupport.h \ insertionpointlocator.h \ cpprefactoringchanges.h \ - abstracteditorsupport.h + abstracteditorsupport.h \ + cppcompletionassist.h SOURCES += completionsettingspage.cpp \ cppclassesfilter.cpp \ - cppcodecompletion.cpp \ cppcurrentdocumentfilter.cpp \ cppfunctionsfilter.cpp \ cppmodelmanager.cpp \ @@ -48,7 +47,8 @@ SOURCES += completionsettingspage.cpp \ symbolsfindfilter.cpp \ uicodecompletionsupport.cpp \ insertionpointlocator.cpp \ - cpprefactoringchanges.cpp + cpprefactoringchanges.cpp \ + cppcompletionassist.cpp FORMS += completionsettingspage.ui \ cppfilesettingspage.ui diff --git a/src/plugins/cpptools/cpptoolsplugin.cpp b/src/plugins/cpptools/cpptoolsplugin.cpp index 44f2da4639..8bff1f858f 100644 --- a/src/plugins/cpptools/cpptoolsplugin.cpp +++ b/src/plugins/cpptools/cpptoolsplugin.cpp @@ -34,13 +34,13 @@ #include "completionsettingspage.h" #include "cppfilesettingspage.h" #include "cppclassesfilter.h" -#include "cppcodecompletion.h" #include "cppfunctionsfilter.h" #include "cppcurrentdocumentfilter.h" #include "cppmodelmanager.h" #include "cpptoolsconstants.h" #include "cpplocatorfilter.h" #include "symbolsfindfilter.h" +#include "cppcompletionassist.h" #include <extensionsystem/pluginmanager.h> @@ -113,9 +113,7 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) m_modelManager, SLOT(updateSourceFiles(QStringList))); addAutoReleasedObject(m_modelManager); - CppCodeCompletion *completion = new CppCodeCompletion(m_modelManager); - addAutoReleasedObject(completion); - + addAutoReleasedObject(new CppCompletionAssistProvider); addAutoReleasedObject(new CppLocatorFilter(m_modelManager)); addAutoReleasedObject(new CppClassesFilter(m_modelManager)); addAutoReleasedObject(new CppFunctionsFilter(m_modelManager)); @@ -141,12 +139,6 @@ bool CppToolsPlugin::initialize(const QStringList &arguments, QString *error) mcpptools->addAction(command); connect(switchAction, SIGNAL(triggered()), this, SLOT(switchHeaderSource())); - // Set completion settings and keep them up to date - TextEditor::TextEditorSettings *textEditorSettings = TextEditor::TextEditorSettings::instance(); - completion->setCompletionSettings(textEditorSettings->completionSettings()); - connect(textEditorSettings, SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), - completion, SLOT(setCompletionSettings(TextEditor::CompletionSettings))); - return true; } diff --git a/src/plugins/fakevim/fakevimplugin.cpp b/src/plugins/fakevim/fakevimplugin.cpp index b34d673a4b..d65121f184 100644 --- a/src/plugins/fakevim/fakevimplugin.cpp +++ b/src/plugins/fakevim/fakevimplugin.cpp @@ -57,12 +57,16 @@ #include <texteditor/basetextdocumentlayout.h> #include <texteditor/basetexteditor.h> #include <texteditor/basetextmark.h> -#include <texteditor/completionsupport.h> #include <texteditor/texteditorconstants.h> #include <texteditor/tabsettings.h> #include <texteditor/texteditorsettings.h> #include <texteditor/indenter.h> -#include <texteditor/icompletioncollector.h> +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/completionassistprovider.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/codeassist/iassistinterface.h> +#include <texteditor/codeassist/genericproposal.h> #include <find/findplugin.h> #include <find/textfindconstants.h> @@ -581,155 +585,154 @@ void FakeVimUserCommandsPage::apply() // /////////////////////////////////////////////////////////////////////// -class WordCompletion : public ICompletionCollector +class FakeVimCompletionAssistProvider : public TextEditor::CompletionAssistProvider { - Q_OBJECT - public: - WordCompletion() + virtual bool supportsEditor(const QString &) const { - m_editable = 0; - m_editor = 0; + return false; } - virtual bool shouldRestartCompletion() + virtual TextEditor::IAssistProcessor *createProcessor() const; + + void setActive(const QString &needle, bool forward, FakeVimHandler *handler) { - //qDebug() << "SHOULD RESTART COMPLETION?"; - return false; + Q_UNUSED(forward); + m_handler = handler; + if (!m_handler) + return; + + BaseTextEditorWidget *editor = qobject_cast<BaseTextEditorWidget *>(handler->widget()); + if (!editor) + return; + + //qDebug() << "ACTIVATE: " << needle << forward; + m_needle = needle; + editor->invokeAssist(Completion, this); } - virtual ITextEditor *editor() const + void setInactive() { - //qDebug() << "NO EDITOR?"; - return m_editable; + m_needle.clear(); + m_handler = 0; } - virtual int startPosition() const + const QString &needle() const { - return m_startPosition; + return m_needle; } - virtual bool supportsEditor(ITextEditor *) const + void appendNeedle(const QChar &c) { - return true; + m_needle.append(c); } - virtual bool supportsPolicy(CompletionPolicy policy) const + FakeVimHandler *handler() const { - return policy == TextCompletion; + return m_handler; } - virtual bool triggersCompletion(ITextEditor *editable) +private: + FakeVimHandler *m_handler; + QString m_needle; +}; + +class FakeVimAssistProposalItem : public BasicProposalItem +{ +public: + FakeVimAssistProposalItem(const FakeVimCompletionAssistProvider *provider) + : m_provider(const_cast<FakeVimCompletionAssistProvider *>(provider)) + {} + + virtual bool implicitlyApplies() const { - //qDebug() << "TRIGGERS?"; - QTC_ASSERT(m_editable == editable, /**/); - return true; + return false; } - virtual int startCompletion(ITextEditor *editable) + virtual bool prematurelyApplies(const QChar &c) const { - //qDebug() << "START COMPLETION"; - QTC_ASSERT(m_editor, return -1); - QTC_ASSERT(m_editable == editable, return -1); - return m_editor->textCursor().position(); + m_provider->appendNeedle(c); + return text() == m_provider->needle(); } - void setActive(const QString &needle, bool forward, FakeVimHandler *handler) + virtual void applyContextualContent(BaseTextEditor *, int) const { - Q_UNUSED(forward); - m_handler = handler; - if (!m_handler) - return; - m_editor = qobject_cast<BaseTextEditorWidget *>(handler->widget()); - if (!m_editor) - return; - //qDebug() << "ACTIVATE: " << needle << forward; - m_needle = needle; - m_editable = m_editor->editor(); - m_startPosition = m_editor->textCursor().position() - needle.size(); - - CompletionSupport::instance()->complete(m_editable, TextCompletion, false); + QTC_ASSERT(m_provider->handler(), return); + m_provider->handler()->handleReplay(text().mid(m_provider->needle().size())); + const_cast<FakeVimCompletionAssistProvider *>(m_provider)->setInactive(); } - void setInactive() +private: + FakeVimCompletionAssistProvider *m_provider; +}; + + +class FakeVimAssistProposalModel : public BasicProposalItemListModel +{ +public: + FakeVimAssistProposalModel(const QList<BasicProposalItem *> &items) + : BasicProposalItemListModel(items) + {} + + virtual bool supportsPrefixExpansion() const { - m_needle.clear(); - m_editable = 0; - m_editor = 0; - m_handler = 0; - m_startPosition = -1; + return false; } +}; - virtual void completions(QList<CompletionItem> *completions) +class FakeVimCompletionAssistProcessor : public IAssistProcessor +{ +public: + FakeVimCompletionAssistProcessor(const TextEditor::IAssistProvider *provider) + : m_provider(static_cast<const FakeVimCompletionAssistProvider *>(provider)) + {} + + virtual TextEditor::IAssistProposal *perform(const IAssistInterface *interface) { - QTC_ASSERT(m_editor, return); - QTC_ASSERT(completions, return); - QTextCursor tc = m_editor->textCursor(); + const QString &needle = m_provider->needle(); + + const int basePosition = interface->position() - needle.size(); + + QTextCursor tc(interface->document()); + tc.setPosition(interface->position()); tc.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor); + QList<BasicProposalItem *> items; QSet<QString> seen; - QTextDocument::FindFlags flags = QTextDocument::FindCaseSensitively; while (1) { - tc = tc.document()->find(m_needle, tc.position(), flags); + tc = tc.document()->find(needle, tc.position(), flags); if (tc.isNull()) break; QTextCursor sel = tc; sel.select(QTextCursor::WordUnderCursor); QString found = sel.selectedText(); // Only add "real" completions. - if (found.startsWith(m_needle) + if (found.startsWith(needle) && !seen.contains(found) - && sel.anchor() != m_startPosition) { + && sel.anchor() != basePosition) { seen.insert(found); - CompletionItem item; - item.collector = this; - item.text = found; - completions->append(item); + BasicProposalItem *item = new FakeVimAssistProposalItem(m_provider); + item->setText(found); + items.append(item); } tc.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor); } //qDebug() << "COMPLETIONS" << completions->size(); - } - - virtual bool typedCharCompletes(const CompletionItem &item, QChar typedChar) - { - m_needle += typedChar; - //qDebug() << "COMPLETE? " << typedChar << item.text << m_needle; - return item.text == m_needle; - } - virtual void complete(const CompletionItem &item, QChar typedChar) - { - Q_UNUSED(typedChar); - //qDebug() << "COMPLETE: " << item.text; - QTC_ASSERT(m_handler, return); - m_handler->handleReplay(item.text.mid(m_needle.size())); - setInactive(); - } - - virtual bool partiallyComplete(const QList<CompletionItem> &completionItems) - { - //qDebug() << "PARTIALLY"; - Q_UNUSED(completionItems); - return false; + delete interface; + return new GenericProposal(basePosition, new FakeVimAssistProposalModel(items)); } - virtual void cleanup() {} - private: - int findStartOfName(int pos = -1) const; - bool isInComment() const; - - FakeVimHandler *m_handler; - BaseTextEditorWidget *m_editor; - ITextEditor *m_editable; - QString m_needle; - QString m_currentPrefix; - QList<CompletionItem> m_items; - int m_startPosition; + const FakeVimCompletionAssistProvider *m_provider; }; +IAssistProcessor *FakeVimCompletionAssistProvider::createProcessor() const +{ + return new FakeVimCompletionAssistProcessor(this); +} + /////////////////////////////////////////////////////////////////////// // @@ -822,7 +825,9 @@ private: UserCommandMap m_defaultUserCommandMap; Core::StatusBarWidget *m_statusBar; - WordCompletion *m_wordCompletion; + // @TODO: Delete + //WordCompletion *m_wordCompletion; + FakeVimCompletionAssistProvider *m_wordProvider; }; QVariant FakeVimUserCommandsModel::data(const QModelIndex &index, int role) const @@ -912,8 +917,10 @@ bool FakeVimPluginPrivate::initialize() m_actionManager = core()->actionManager(); QTC_ASSERT(actionManager(), return false); - m_wordCompletion = new WordCompletion; - q->addAutoReleasedObject(m_wordCompletion); + //m_wordCompletion = new WordCompletion; + //q->addAutoReleasedObject(m_wordCompletion); + m_wordProvider = new FakeVimCompletionAssistProvider; + /* // Set completion settings and keep them up to date. TextEditorSettings *textEditorSettings = TextEditorSettings::instance(); @@ -1407,16 +1414,15 @@ void FakeVimPluginPrivate::triggerCompletions() if (!handler) return; if (BaseTextEditorWidget *editor = qobject_cast<BaseTextEditorWidget *>(handler->widget())) - CompletionSupport::instance()-> - complete(editor->editor(), TextCompletion, false); - // editor->triggerCompletions(); + editor->invokeAssist(Completion, m_wordProvider); +// CompletionSupport::instance()->complete(editor->editor(), TextCompletion, false); } void FakeVimPluginPrivate::triggerSimpleCompletions(const QString &needle, bool forward) { - m_wordCompletion->setActive(needle, forward, - qobject_cast<FakeVimHandler *>(sender())); +// m_wordCompletion->setActive(needle, forward, qobject_cast<FakeVimHandler *>(sender())); + m_wordProvider->setActive(needle, forward, qobject_cast<FakeVimHandler *>(sender())); } void FakeVimPluginPrivate::setBlockSelection(bool on) diff --git a/src/plugins/glsleditor/glslcodecompletion.cpp b/src/plugins/glsleditor/glslcodecompletion.cpp deleted file mode 100644 index 5493ad7e84..0000000000 --- a/src/plugins/glsleditor/glslcodecompletion.cpp +++ /dev/null @@ -1,731 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ -#include "glslcodecompletion.h" -#include "glsleditor.h" -#include "glsleditorplugin.h" -#include <glsl/glslengine.h> -#include <glsl/glslengine.h> -#include <glsl/glsllexer.h> -#include <glsl/glslparser.h> -#include <glsl/glslsemantic.h> -#include <glsl/glslsymbols.h> -#include <glsl/glslastdump.h> -#include <cplusplus/ExpressionUnderCursor.h> -#include <texteditor/completionsettings.h> -#include <utils/faketooltip.h> -#include <QtGui/QIcon> -#include <QtGui/QPainter> -#include <QtGui/QLabel> -#include <QtGui/QToolButton> -#include <QtGui/QHBoxLayout> -#include <QtGui/QApplication> -#include <QtGui/QDesktopWidget> -#include <QtCore/QDebug> - -using namespace GLSLEditor; -using namespace GLSLEditor::Internal; - -enum CompletionOrder { - SpecialMemberOrder = -5 -}; - -static bool isIdentifierChar(QChar ch) -{ - return ch.isLetterOrNumber() || ch == QLatin1Char('_'); -} - -static bool isDelimiter(QChar ch) -{ - switch (ch.unicode()) { - case '{': - case '}': - case '[': - case ']': - case ')': - case '?': - case '!': - case ':': - case ';': - case ',': - case '+': - case '-': - case '*': - case '/': - return true; - - default: - return false; - } -} - -static bool checkStartOfIdentifier(const QString &word) -{ - if (! word.isEmpty()) { - const QChar ch = word.at(0); - if (ch.isLetter() || ch == QLatin1Char('_')) - return true; - } - - return false; -} - -namespace GLSLEditor { -namespace Internal { -class FunctionArgumentWidget : public QLabel -{ - Q_OBJECT - -public: - FunctionArgumentWidget(); - void showFunctionHint(QVector<GLSL::Function *> functionSymbols, - int startPosition); - -protected: - bool eventFilter(QObject *obj, QEvent *e); - -private slots: - void nextPage(); - void previousPage(); - -private: - void updateArgumentHighlight(); - void updateHintText(); - void placeInsideScreen(); - - GLSL::Function *currentFunction() const - { return m_items.at(m_current); } - - int m_startpos; - int m_currentarg; - int m_current; - bool m_escapePressed; - - TextEditor::ITextEditor *m_editor; - - QWidget *m_pager; - QLabel *m_numberLabel; - Utils::FakeToolTip *m_popupFrame; - QVector<GLSL::Function *> m_items; -}; - - -FunctionArgumentWidget::FunctionArgumentWidget(): - m_startpos(-1), - m_current(0), - m_escapePressed(false) -{ - QObject *editorObject = Core::EditorManager::instance()->currentEditor(); - m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject); - - m_popupFrame = new Utils::FakeToolTip(m_editor->widget()); - - QToolButton *downArrow = new QToolButton; - downArrow->setArrowType(Qt::DownArrow); - downArrow->setFixedSize(16, 16); - downArrow->setAutoRaise(true); - - QToolButton *upArrow = new QToolButton; - upArrow->setArrowType(Qt::UpArrow); - upArrow->setFixedSize(16, 16); - upArrow->setAutoRaise(true); - - setParent(m_popupFrame); - setFocusPolicy(Qt::NoFocus); - - m_pager = new QWidget; - QHBoxLayout *hbox = new QHBoxLayout(m_pager); - hbox->setMargin(0); - hbox->setSpacing(0); - hbox->addWidget(upArrow); - m_numberLabel = new QLabel; - hbox->addWidget(m_numberLabel); - hbox->addWidget(downArrow); - - QHBoxLayout *layout = new QHBoxLayout; - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(m_pager); - layout->addWidget(this); - m_popupFrame->setLayout(layout); - - connect(upArrow, SIGNAL(clicked()), SLOT(previousPage())); - connect(downArrow, SIGNAL(clicked()), SLOT(nextPage())); - - setTextFormat(Qt::RichText); - - qApp->installEventFilter(this); -} - -void FunctionArgumentWidget::showFunctionHint(QVector<GLSL::Function *> functionSymbols, - int startPosition) -{ - Q_ASSERT(!functionSymbols.isEmpty()); - - if (m_startpos == startPosition) - return; - - m_pager->setVisible(functionSymbols.size() > 1); - - m_items = functionSymbols; - m_startpos = startPosition; - m_current = 0; - m_escapePressed = false; - - // update the text - m_currentarg = -1; - updateArgumentHighlight(); - - m_popupFrame->show(); -} - -void FunctionArgumentWidget::nextPage() -{ - m_current = (m_current + 1) % m_items.size(); - updateHintText(); -} - -void FunctionArgumentWidget::previousPage() -{ - if (m_current == 0) - m_current = m_items.size() - 1; - else - --m_current; - - updateHintText(); -} - -void FunctionArgumentWidget::updateArgumentHighlight() -{ - int curpos = m_editor->position(); - if (curpos < m_startpos) { - m_popupFrame->close(); - return; - } - - const QByteArray str = m_editor->textAt(m_startpos, curpos - m_startpos).toLatin1(); - - int argnr = 0; - int parcount = 0; - GLSL::Lexer lexer(0, str.constData(), str.length()); - GLSL::Token tk; - QList<GLSL::Token> tokens; - do { - lexer.yylex(&tk); - tokens.append(tk); - } while (tk.isNot(GLSL::Parser::EOF_SYMBOL)); - for (int i = 0; i < tokens.count(); ++i) { - const GLSL::Token &tk = tokens.at(i); - if (tk.is(GLSL::Parser::T_LEFT_PAREN)) - ++parcount; - else if (tk.is(GLSL::Parser::T_RIGHT_PAREN)) - --parcount; - else if (! parcount && tk.is(GLSL::Parser::T_COMMA)) - ++argnr; - } - - if (m_currentarg != argnr) { - m_currentarg = argnr; - updateHintText(); - } - - if (parcount < 0) - m_popupFrame->close(); -} - -bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e) -{ - switch (e->type()) { - case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - if (m_items.size() > 1) { - QKeyEvent *ke = static_cast<QKeyEvent*>(e); - if (ke->key() == Qt::Key_Up) { - previousPage(); - return true; - } else if (ke->key() == Qt::Key_Down) { - nextPage(); - return true; - } - return false; - } - break; - case QEvent::KeyRelease: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_escapePressed) { - m_popupFrame->close(); - return false; - } - updateArgumentHighlight(); - break; - case QEvent::WindowDeactivate: - case QEvent::FocusOut: - if (obj != m_editor->widget()) - break; - m_popupFrame->close(); - break; - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::Wheel: { - QWidget *widget = qobject_cast<QWidget *>(obj); - if (! (widget == this || m_popupFrame->isAncestorOf(widget))) { - m_popupFrame->close(); - } - } - break; - default: - break; - } - return false; -} - -void FunctionArgumentWidget::updateHintText() -{ - setText(currentFunction()->prettyPrint(m_currentarg)); - - m_numberLabel->setText(tr("%1 of %2").arg(m_current + 1).arg(m_items.size())); - - placeInsideScreen(); -} - -void FunctionArgumentWidget::placeInsideScreen() -{ - const QDesktopWidget *desktop = QApplication::desktop(); -#ifdef Q_WS_MAC - const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_editor->widget())); -#else - const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_editor->widget())); -#endif - - m_pager->setFixedWidth(m_pager->minimumSizeHint().width()); - - setWordWrap(false); - const int maxDesiredWidth = screen.width() - 10; - const QSize minHint = m_popupFrame->minimumSizeHint(); - if (minHint.width() > maxDesiredWidth) { - setWordWrap(true); - m_popupFrame->setFixedWidth(maxDesiredWidth); - const int extra = - m_popupFrame->contentsMargins().bottom() + m_popupFrame->contentsMargins().top(); - m_popupFrame->setFixedHeight(heightForWidth(maxDesiredWidth - m_pager->width()) + extra); - } else { - m_popupFrame->setFixedSize(minHint); - } - - const QSize sz = m_popupFrame->size(); - QPoint pos = m_editor->cursorRect(m_startpos).topLeft(); - pos.setY(pos.y() - sz.height() - 1); - - if (pos.x() + sz.width() > screen.right()) - pos.setX(screen.right() - sz.width()); - - m_popupFrame->move(pos); -} - -} // Internal -} // GLSLEditor - - - -CodeCompletion::CodeCompletion(QObject *parent) - : ICompletionCollector(parent), - m_editor(0), - m_startPosition(-1), - m_restartCompletion(false), - m_keywordVariant(-1), - m_keywordIcon(":/glsleditor/images/keyword.png"), - m_varIcon(":/glsleditor/images/var.png"), - m_functionIcon(":/glsleditor/images/func.png"), - m_typeIcon(":/glsleditor/images/type.png"), - m_constIcon(":/glsleditor/images/const.png"), - m_attributeIcon(":/glsleditor/images/attribute.png"), - m_uniformIcon(":/glsleditor/images/uniform.png"), - m_varyingIcon(":/glsleditor/images/varying.png"), - m_otherIcon(":/glsleditor/images/other.png") -{ -} - -CodeCompletion::~CodeCompletion() -{ -} - -TextEditor::ITextEditor *CodeCompletion::editor() const -{ - return m_editor; -} - -int CodeCompletion::startPosition() const -{ - return m_startPosition; -} - -bool CodeCompletion::supportsEditor(TextEditor::ITextEditor *editor) const -{ - return qobject_cast<GLSLTextEditorWidget *>(editor->widget()) != 0; -} - -bool CodeCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const -{ - return policy == TextEditor::SemanticCompletion; -} - -bool CodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) -{ - const int cursorPosition = editor->position(); - const QChar ch = editor->characterAt(cursorPosition - 1); - - if (completionSettings().m_completionTrigger == TextEditor::AutomaticCompletion) { - const QChar characterUnderCursor = editor->characterAt(cursorPosition); - - if (isIdentifierChar(ch) && (characterUnderCursor.isSpace() || - characterUnderCursor.isNull() || - isDelimiter(characterUnderCursor))) { - int pos = editor->position() - 1; - for (; pos != -1; --pos) { - if (! isIdentifierChar(editor->characterAt(pos))) - break; - } - ++pos; - - const QString word = editor->textAt(pos, cursorPosition - pos); - if (word.length() > 2 && checkStartOfIdentifier(word)) { - for (int i = 0; i < word.length(); ++i) { - if (! isIdentifierChar(word.at(i))) - return false; - } - return true; - } - } - } - - if (ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char(',')) - return true; - - return false; -} - -int CodeCompletion::startCompletion(TextEditor::ITextEditor *editor) -{ - m_editor = editor; - - int pos = editor->position() - 1; - QChar ch = editor->characterAt(pos); - while (ch.isLetterOrNumber() || ch == QLatin1Char('_')) - ch = editor->characterAt(--pos); - - CPlusPlus::ExpressionUnderCursor expressionUnderCursor; - GLSLTextEditorWidget *edit = qobject_cast<GLSLTextEditorWidget *>(editor->widget()); - - QList<GLSL::Symbol *> members; - QStringList specialMembers; - - bool functionCall = (ch == QLatin1Char('(') && pos == editor->position() - 1); - - if (ch == QLatin1Char(',')) { - QTextCursor tc(edit->document()); - tc.setPosition(pos); - const int start = expressionUnderCursor.startOfFunctionCall(tc); - if (start == -1) - return -1; - - if (edit->characterAt(start) == QLatin1Char('(')) { - pos = start; - ch = QLatin1Char('('); - functionCall = true; - } - } - - if (ch == QLatin1Char('.') || functionCall) { - const bool memberCompletion = ! functionCall; - QTextCursor tc(edit->document()); - tc.setPosition(pos); - - // get the expression under cursor - const QByteArray code = expressionUnderCursor(tc).toLatin1(); - //qDebug() << endl << "expression:" << code; - - // parse the expression - GLSL::Engine engine; - GLSL::Parser parser(&engine, code, code.size(), edit->languageVariant()); - GLSL::ExpressionAST *expr = parser.parseExpression(); - -#if 0 - // dump it! - QTextStream qout(stdout, QIODevice::WriteOnly); - GLSL::ASTDump dump(qout); - dump(expr); -#endif - - if (Document::Ptr doc = edit->glslDocument()) { - GLSL::Scope *currentScope = doc->scopeAt(pos); - - GLSL::Semantic sem; - GLSL::Semantic::ExprResult exprTy = sem.expression(expr, currentScope, doc->engine()); - if (exprTy.type) { - if (memberCompletion) { - if (const GLSL::VectorType *vecTy = exprTy.type->asVectorType()) { - members = vecTy->members(); - - // Sort the most relevant swizzle orderings to the top. - specialMembers += QLatin1String("xy"); - specialMembers += QLatin1String("xyz"); - specialMembers += QLatin1String("xyzw"); - specialMembers += QLatin1String("rgb"); - specialMembers += QLatin1String("rgba"); - specialMembers += QLatin1String("st"); - specialMembers += QLatin1String("stp"); - specialMembers += QLatin1String("stpq"); - - } else if (const GLSL::Struct *structTy = exprTy.type->asStructType()) { - members = structTy->members(); - - } else { - // some other type - } - } else { // function completion - QVector<GLSL::Function *> signatures; - if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) - signatures.append(const_cast<GLSL::Function *>(funTy)); // ### get rid of the const_cast - else if (const GLSL::OverloadSet *overload = exprTy.type->asOverloadSetType()) - signatures = overload->functions(); - - if (! signatures.isEmpty()) { - // Recreate if necessary - if (!m_functionArgumentWidget) - m_functionArgumentWidget = new FunctionArgumentWidget; - - m_functionArgumentWidget->showFunctionHint(signatures, pos + 1); - return false; - } - } - } else { - // undefined - - } - - } else { - // sorry, there's no document - } - - } else { - // it's a global completion - if (Document::Ptr doc = edit->glslDocument()) { - GLSL::Scope *currentScope = doc->scopeAt(pos); - bool isGlobal = !currentScope || !currentScope->scope(); - - // add the members from the scope chain - for (; currentScope; currentScope = currentScope->scope()) - members += currentScope->members(); - - // if this is the global scope, then add some standard Qt attribute - // and uniform names for autocompleting variable declarations - // this isn't a complete list, just the most common - if (isGlobal) { - static const char * const attributeNames[] = { - "qt_Vertex", - "qt_Normal", - "qt_MultiTexCoord0", - "qt_MultiTexCoord1", - "qt_MultiTexCoord2", - 0 - }; - static const char * const uniformNames[] = { - "qt_ModelViewProjectionMatrix", - "qt_ModelViewMatrix", - "qt_ProjectionMatrix", - "qt_NormalMatrix", - "qt_Texture0", - "qt_Texture1", - "qt_Texture2", - "qt_Color", - "qt_Opacity", - 0 - }; - for (int index = 0; attributeNames[index]; ++index) { - TextEditor::CompletionItem item(this); - item.text = QString::fromLatin1(attributeNames[index]); - item.icon = m_attributeIcon; - m_completions.append(item); - } - for (int index = 0; uniformNames[index]; ++index) { - TextEditor::CompletionItem item(this); - item.text = QString::fromLatin1(uniformNames[index]); - item.icon = m_uniformIcon; - m_completions.append(item); - } - } - } - - if (m_keywordVariant != edit->languageVariant()) { - QStringList keywords = GLSL::Lexer::keywords(edit->languageVariant()); - m_keywordCompletions.clear(); - for (int index = 0; index < keywords.size(); ++index) { - TextEditor::CompletionItem item(this); - item.text = keywords.at(index); - item.icon = m_keywordIcon; - m_keywordCompletions.append(item); - } - m_keywordVariant = edit->languageVariant(); - } - - m_completions += m_keywordCompletions; - } - - foreach (GLSL::Symbol *s, members) { - TextEditor::CompletionItem item(this); - GLSL::Variable *var = s->asVariable(); - if (var) { - int storageType = var->qualifiers() & GLSL::QualifiedTypeAST::StorageMask; - if (storageType == GLSL::QualifiedTypeAST::Attribute) - item.icon = m_attributeIcon; - else if (storageType == GLSL::QualifiedTypeAST::Uniform) - item.icon = m_uniformIcon; - else if (storageType == GLSL::QualifiedTypeAST::Varying) - item.icon = m_varyingIcon; - else if (storageType == GLSL::QualifiedTypeAST::Const) - item.icon = m_constIcon; - else - item.icon = m_varIcon; - } else if (s->asArgument()) { - item.icon = m_varIcon; - } else if (s->asFunction() || s->asOverloadSet()) { - item.icon = m_functionIcon; - } else if (s->asStruct()) { - item.icon = m_typeIcon; - } else { - item.icon = m_otherIcon; - } - item.text = s->name(); - if (specialMembers.contains(item.text)) - item.order = SpecialMemberOrder; - m_completions.append(item); - } - - m_startPosition = pos + 1; - return m_startPosition; -} - -void CodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) -{ - const int length = m_editor->position() - m_startPosition; - - if (length == 0) - *completions = m_completions; - else if (length > 0) { - const QString key = m_editor->textAt(m_startPosition, length); - - filter(m_completions, completions, key); - - if (completions->size() == 1) { - if (key == completions->first().text) - completions->clear(); - } - } -} - -bool CodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) -{ - Q_UNUSED(item); - Q_UNUSED(typedChar); - return false; -} - -void CodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) -{ - Q_UNUSED(typedChar); - - QString toInsert = item.text; - - const int length = m_editor->position() - m_startPosition; - m_editor->setCursorPosition(m_startPosition); - m_editor->replace(length, toInsert); - - if (toInsert.endsWith(QLatin1Char('.')) || toInsert.endsWith(QLatin1Char('('))) - m_restartCompletion = true; -} - -bool CodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) -{ - return ICompletionCollector::partiallyComplete(completionItems); -} - -bool CodeCompletion::glslCompletionItemLessThan(const TextEditor::CompletionItem &l, const TextEditor::CompletionItem &r) -{ - if (l.order != r.order) - return l.order < r.order; - return completionItemLessThan(l, r); -} - -QList<TextEditor::CompletionItem> CodeCompletion::getCompletions() -{ - QList<TextEditor::CompletionItem> completionItems; - - completions(&completionItems); - - qStableSort(completionItems.begin(), completionItems.end(), glslCompletionItemLessThan); - - // Remove duplicates - QString lastKey; - QVariant lastData; - QList<TextEditor::CompletionItem> uniquelist; - - foreach (const TextEditor::CompletionItem &item, completionItems) { - if (item.text != lastKey || item.data.type() != lastData.type()) { - uniquelist.append(item); - lastKey = item.text; - lastData = item.data; - } - } - - return uniquelist; -} - -bool CodeCompletion::shouldRestartCompletion() -{ - return m_restartCompletion; -} - -void CodeCompletion::cleanup() -{ - m_editor = 0; - m_completions.clear(); - m_restartCompletion = false; - m_startPosition = -1; -} - -#include "glslcodecompletion.moc" diff --git a/src/plugins/glsleditor/glslcodecompletion.h b/src/plugins/glsleditor/glslcodecompletion.h deleted file mode 100644 index bb3d10a52e..0000000000 --- a/src/plugins/glsleditor/glslcodecompletion.h +++ /dev/null @@ -1,134 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ -#ifndef GLSLCODECOMPLETION_H -#define GLSLCODECOMPLETION_H - -#include <texteditor/icompletioncollector.h> -#include <QtCore/QPointer> - -namespace GLSLEditor { -namespace Internal { - -class FunctionArgumentWidget; - -class CodeCompletion: public TextEditor::ICompletionCollector -{ - Q_OBJECT - -public: - CodeCompletion(QObject *parent = 0); - virtual ~CodeCompletion(); - - /* Returns the current active ITextEditor */ - virtual TextEditor::ITextEditor *editor() const; - virtual int startPosition() const; - - /* - * Returns true if this completion collector can be used with the given editor. - */ - virtual bool supportsEditor(TextEditor::ITextEditor *editor) const; - - /* - * Returns true if this completion collector supports the given completion policy. - */ - virtual bool supportsPolicy(TextEditor::CompletionPolicy policy) const; - - /* This method should return whether the cursor is at a position which could - * trigger an autocomplete. It will be called each time a character is typed in - * the text editor. - */ - virtual bool triggersCompletion(TextEditor::ITextEditor *editor); - - // returns starting position - virtual int startCompletion(TextEditor::ITextEditor *editor); - - /* This method should add all the completions it wants to show into the list, - * based on the given cursor position. - */ - virtual void completions(QList<TextEditor::CompletionItem> *completions); - - /** - * This method should return true when the given typed character should cause - * the selected completion item to be completed. - */ - virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - - /** - * This method should complete the given completion item. - * - * \param typedChar Non-null when completion was triggered by typing a - * character. Possible values depend on typedCharCompletes() - */ - virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); - - /* This method gives the completion collector a chance to partially complete - * based on a set of items. The general use case is to complete the common - * prefix shared by all possible completion items. - * - * Returns whether the completion popup should be closed. - */ - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); - - virtual QList<TextEditor::CompletionItem> getCompletions(); - virtual bool shouldRestartCompletion(); - - /* Called when it's safe to clean up the completion items. - */ - virtual void cleanup(); - -private: - QList<TextEditor::CompletionItem> m_completions; - QList<TextEditor::CompletionItem> m_keywordCompletions; - TextEditor::ITextEditor *m_editor; - int m_startPosition; - bool m_restartCompletion; - QPointer<FunctionArgumentWidget> m_functionArgumentWidget; - - static bool glslCompletionItemLessThan(const TextEditor::CompletionItem &l, const TextEditor::CompletionItem &r); - - int m_keywordVariant; - - QIcon m_keywordIcon; - QIcon m_varIcon; - QIcon m_functionIcon; - QIcon m_typeIcon; - QIcon m_constIcon; - QIcon m_attributeIcon; - QIcon m_uniformIcon; - QIcon m_varyingIcon; - QIcon m_otherIcon; -}; - -} // namespace Internal -} // namespace GLSLEditor - -#endif // GLSLCODECOMPLETION_H diff --git a/src/plugins/glsleditor/glslcompletionassist.cpp b/src/plugins/glsleditor/glslcompletionassist.cpp new file mode 100644 index 0000000000..125dbad66f --- /dev/null +++ b/src/plugins/glsleditor/glslcompletionassist.cpp @@ -0,0 +1,478 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "glslcompletionassist.h" +#include "glsleditorconstants.h" +#include "glsleditorplugin.h" +#include "reuse.h" + +#include <glsl/glslengine.h> +#include <glsl/glslengine.h> +#include <glsl/glsllexer.h> +#include <glsl/glslparser.h> +#include <glsl/glslsemantic.h> +#include <glsl/glslsymbols.h> +#include <glsl/glslastdump.h> + +#include <coreplugin/ifile.h> +#include <texteditor/completionsettings.h> +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/functionhintproposal.h> +#include <cplusplus/ExpressionUnderCursor.h> +#include <utils/faketooltip.h> + +#include <QtGui/QIcon> +#include <QtGui/QPainter> +#include <QtGui/QLabel> +#include <QtGui/QToolButton> +#include <QtGui/QHBoxLayout> +#include <QtGui/QApplication> +#include <QtGui/QDesktopWidget> +#include <QtCore/QDebug> + +using namespace GLSLEditor; +using namespace Internal; +using namespace TextEditor; + +namespace { + +enum CompletionOrder { + SpecialMemberOrder = -5 +}; + + +bool isActivationChar(const QChar &ch) +{ + return ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char(','); +} + +bool isIdentifierChar(QChar ch) +{ + return ch.isLetterOrNumber() || ch == QLatin1Char('_'); +} + +bool isDelimiter(QChar ch) +{ + switch (ch.unicode()) { + case '{': + case '}': + case '[': + case ']': + case ')': + case '?': + case '!': + case ':': + case ';': + case ',': + case '+': + case '-': + case '*': + case '/': + return true; + + default: + return false; + } +} + +bool checkStartOfIdentifier(const QString &word) +{ + if (! word.isEmpty()) { + const QChar ch = word.at(0); + if (ch.isLetter() || ch == QLatin1Char('_')) + return true; + } + + return false; +} + +} // Anonymous + +// ---------------------------- +// GLSLCompletionAssistProvider +// ---------------------------- +bool GLSLCompletionAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(Constants::C_GLSLEDITOR_ID); +} + +IAssistProcessor *GLSLCompletionAssistProvider::createProcessor() const +{ + return new GLSLCompletionAssistProcessor; +} + +int GLSLCompletionAssistProvider::activationCharSequenceLength() const +{ + return 1; +} + +bool GLSLCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + return isActivationChar(sequence.at(0)); +} + +// ----------------------------- +// GLSLFunctionHintProposalModel +// ----------------------------- +class GLSLFunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel +{ +public: + GLSLFunctionHintProposalModel(QVector<GLSL::Function *> functionSymbols) + : m_items(functionSymbols) + , m_currentArg(-1) + {} + + virtual void reset() {} + virtual int size() const { return m_items.size(); } + virtual QString text(int index) const; + virtual int activeArgument(const QString &prefix) const; + +private: + QVector<GLSL::Function *> m_items; + mutable int m_currentArg; +}; + +QString GLSLFunctionHintProposalModel::text(int index) const +{ + return m_items.at(index)->prettyPrint(m_currentArg); +} + +int GLSLFunctionHintProposalModel::activeArgument(const QString &prefix) const +{ + const QByteArray &str = prefix.toLatin1(); + int argnr = 0; + int parcount = 0; + GLSL::Lexer lexer(0, str.constData(), str.length()); + GLSL::Token tk; + QList<GLSL::Token> tokens; + do { + lexer.yylex(&tk); + tokens.append(tk); + } while (tk.isNot(GLSL::Parser::EOF_SYMBOL)); + for (int i = 0; i < tokens.count(); ++i) { + const GLSL::Token &tk = tokens.at(i); + if (tk.is(GLSL::Parser::T_LEFT_PAREN)) + ++parcount; + else if (tk.is(GLSL::Parser::T_RIGHT_PAREN)) + --parcount; + else if (! parcount && tk.is(GLSL::Parser::T_COMMA)) + ++argnr; + } + + if (parcount < 0) + return -1; + + if (argnr != m_currentArg) + m_currentArg = argnr; + + return argnr; +} + +// ----------------------------- +// GLSLCompletionAssistProcessor +// ----------------------------- +GLSLCompletionAssistProcessor::GLSLCompletionAssistProcessor() + : m_startPosition(0) + , m_keywordIcon(":/glsleditor/images/keyword.png") + , m_varIcon(":/glsleditor/images/var.png") + , m_functionIcon(":/glsleditor/images/func.png") + , m_typeIcon(":/glsleditor/images/type.png") + , m_constIcon(":/glsleditor/images/const.png") + , m_attributeIcon(":/glsleditor/images/attribute.png") + , m_uniformIcon(":/glsleditor/images/uniform.png") + , m_varyingIcon(":/glsleditor/images/varying.png") + , m_otherIcon(":/glsleditor/images/other.png") +{} + +GLSLCompletionAssistProcessor::~GLSLCompletionAssistProcessor() +{} + +IAssistProposal *GLSLCompletionAssistProcessor::perform(const IAssistInterface *interface) +{ + m_interface.reset(static_cast<const GLSLCompletionAssistInterface *>(interface)); + + if (interface->reason() == IdleEditor && !acceptsIdleEditor()) + return 0; + + int pos = m_interface->position() - 1; + QChar ch = m_interface->characterAt(pos); + while (ch.isLetterOrNumber() || ch == QLatin1Char('_')) + ch = m_interface->characterAt(--pos); + + CPlusPlus::ExpressionUnderCursor expressionUnderCursor; + //GLSLTextEditorWidget *edit = qobject_cast<GLSLTextEditorWidget *>(editor->widget()); + + QList<GLSL::Symbol *> members; + QStringList specialMembers; + + bool functionCall = (ch == QLatin1Char('(') && pos == m_interface->position() - 1); + + if (ch == QLatin1Char(',')) { + QTextCursor tc(m_interface->document()); + tc.setPosition(pos); + const int start = expressionUnderCursor.startOfFunctionCall(tc); + if (start == -1) + return 0; + + if (m_interface->characterAt(start) == QLatin1Char('(')) { + pos = start; + ch = QLatin1Char('('); + functionCall = true; + } + } + + if (ch == QLatin1Char('.') || functionCall) { + const bool memberCompletion = ! functionCall; + QTextCursor tc(m_interface->document()); + tc.setPosition(pos); + + // get the expression under cursor + const QByteArray code = expressionUnderCursor(tc).toLatin1(); + //qDebug() << endl << "expression:" << code; + + // parse the expression + GLSL::Engine engine; + GLSL::Parser parser(&engine, code, code.size(), languageVariant(m_interface->mimeType())); + GLSL::ExpressionAST *expr = parser.parseExpression(); + +#if 0 + // dump it! + QTextStream qout(stdout, QIODevice::WriteOnly); + GLSL::ASTDump dump(qout); + dump(expr); +#endif + + if (Document::Ptr doc = m_interface->glslDocument()) { + GLSL::Scope *currentScope = doc->scopeAt(pos); + + GLSL::Semantic sem; + GLSL::Semantic::ExprResult exprTy = sem.expression(expr, currentScope, doc->engine()); + if (exprTy.type) { + if (memberCompletion) { + if (const GLSL::VectorType *vecTy = exprTy.type->asVectorType()) { + members = vecTy->members(); + + // Sort the most relevant swizzle orderings to the top. + specialMembers += QLatin1String("xy"); + specialMembers += QLatin1String("xyz"); + specialMembers += QLatin1String("xyzw"); + specialMembers += QLatin1String("rgb"); + specialMembers += QLatin1String("rgba"); + specialMembers += QLatin1String("st"); + specialMembers += QLatin1String("stp"); + specialMembers += QLatin1String("stpq"); + + } else if (const GLSL::Struct *structTy = exprTy.type->asStructType()) { + members = structTy->members(); + + } else { + // some other type + } + } else { // function completion + QVector<GLSL::Function *> signatures; + if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) + signatures.append(const_cast<GLSL::Function *>(funTy)); // ### get rid of the const_cast + else if (const GLSL::OverloadSet *overload = exprTy.type->asOverloadSetType()) + signatures = overload->functions(); + + if (! signatures.isEmpty()) { + m_startPosition = pos + 1; + return createHintProposal(signatures); + } + } + } else { + // undefined + + } + + } else { + // sorry, there's no document + } + + } else { + // it's a global completion + if (Document::Ptr doc = m_interface->glslDocument()) { + GLSL::Scope *currentScope = doc->scopeAt(pos); + bool isGlobal = !currentScope || !currentScope->scope(); + + // add the members from the scope chain + for (; currentScope; currentScope = currentScope->scope()) + members += currentScope->members(); + + // if this is the global scope, then add some standard Qt attribute + // and uniform names for autocompleting variable declarations + // this isn't a complete list, just the most common + if (isGlobal) { + static const char * const attributeNames[] = { + "qt_Vertex", + "qt_Normal", + "qt_MultiTexCoord0", + "qt_MultiTexCoord1", + "qt_MultiTexCoord2", + 0 + }; + static const char * const uniformNames[] = { + "qt_ModelViewProjectionMatrix", + "qt_ModelViewMatrix", + "qt_ProjectionMatrix", + "qt_NormalMatrix", + "qt_Texture0", + "qt_Texture1", + "qt_Texture2", + "qt_Color", + "qt_Opacity", + 0 + }; + for (int index = 0; attributeNames[index]; ++index) + addCompletion(QString::fromLatin1(attributeNames[index]), m_attributeIcon); + for (int index = 0; uniformNames[index]; ++index) + addCompletion(QString::fromLatin1(uniformNames[index]), m_uniformIcon); + } + } + + // if (m_keywordVariant != languageVariant(m_interface->mimeType())) { + QStringList keywords = GLSL::Lexer::keywords(languageVariant(m_interface->mimeType())); +// m_keywordCompletions.clear(); + for (int index = 0; index < keywords.size(); ++index) + addCompletion(keywords.at(index), m_keywordIcon); +// m_keywordVariant = languageVariant(m_interface->mimeType()); +// } + + // m_completions += m_keywordCompletions; + } + + foreach (GLSL::Symbol *s, members) { + QIcon icon; + GLSL::Variable *var = s->asVariable(); + if (var) { + int storageType = var->qualifiers() & GLSL::QualifiedTypeAST::StorageMask; + if (storageType == GLSL::QualifiedTypeAST::Attribute) + icon = m_attributeIcon; + else if (storageType == GLSL::QualifiedTypeAST::Uniform) + icon = m_uniformIcon; + else if (storageType == GLSL::QualifiedTypeAST::Varying) + icon = m_varyingIcon; + else if (storageType == GLSL::QualifiedTypeAST::Const) + icon = m_constIcon; + else + icon = m_varIcon; + } else if (s->asArgument()) { + icon = m_varIcon; + } else if (s->asFunction() || s->asOverloadSet()) { + icon = m_functionIcon; + } else if (s->asStruct()) { + icon = m_typeIcon; + } else { + icon = m_otherIcon; + } + if (specialMembers.contains(s->name())) + addCompletion(s->name(), icon, SpecialMemberOrder); + else + addCompletion(s->name(), icon); + } + + m_startPosition = pos + 1; + return createContentProposal(); +} + +IAssistProposal *GLSLCompletionAssistProcessor::createContentProposal() const +{ + IGenericProposalModel *model = new BasicProposalItemListModel(m_completions); + IAssistProposal *proposal = new GenericProposal(m_startPosition, model); + return proposal; +} + +IAssistProposal *GLSLCompletionAssistProcessor::createHintProposal( + const QVector<GLSL::Function *> &symbols) +{ + IFunctionHintProposalModel *model = new GLSLFunctionHintProposalModel(symbols); + IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model); + return proposal; +} + +bool GLSLCompletionAssistProcessor::acceptsIdleEditor() const +{ + const int cursorPosition = m_interface->position(); + const QChar ch = m_interface->characterAt(cursorPosition - 1); + + const QChar characterUnderCursor = m_interface->characterAt(cursorPosition); + + if (isIdentifierChar(ch) && (characterUnderCursor.isSpace() || + characterUnderCursor.isNull() || + isDelimiter(characterUnderCursor))) { + int pos = m_interface->position() - 1; + for (; pos != -1; --pos) { + if (! isIdentifierChar(m_interface->characterAt(pos))) + break; + } + ++pos; + + const QString word = m_interface->textAt(pos, cursorPosition - pos); + if (word.length() > 2 && checkStartOfIdentifier(word)) { + for (int i = 0; i < word.length(); ++i) { + if (! isIdentifierChar(word.at(i))) + return false; + } + return true; + } + } + + return isActivationChar(ch); +} + +void GLSLCompletionAssistProcessor::addCompletion(const QString &text, + const QIcon &icon, + int order) +{ + BasicProposalItem *item = new BasicProposalItem; + item->setText(text); + item->setIcon(icon); + item->setOrder(order); + m_completions.append(item); +} + +// ----------------------------- +// GLSLCompletionAssistInterface +// ----------------------------- +GLSLCompletionAssistInterface::GLSLCompletionAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + TextEditor::AssistReason reason, + const QString &mimeType, + const Document::Ptr &glslDoc) + : DefaultAssistInterface(document, position, file, reason) + , m_mimeType(mimeType) + , m_glslDoc(glslDoc) +{ +} diff --git a/src/plugins/glsleditor/glslcompletionassist.h b/src/plugins/glsleditor/glslcompletionassist.h new file mode 100644 index 0000000000..78daea8818 --- /dev/null +++ b/src/plugins/glsleditor/glslcompletionassist.h @@ -0,0 +1,115 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef GLSLCOMPLETIONASSIST_H +#define GLSLCOMPLETIONASSIST_H + +#include "glsleditor.h" + +#include <texteditor/codeassist/completionassistprovider.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/codeassist/defaultassistinterface.h> +#include <texteditor/codeassist/ifunctionhintproposalmodel.h> + +#include <QtCore/QScopedPointer> +#include <QtGui/QIcon> + +namespace TextEditor { +class BasicProposalItem; +} + +namespace GLSLEditor { +namespace Internal { + +class GLSLCompletionAssistInterface; + +class GLSLCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + virtual bool supportsEditor(const QString &editorId) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; + + virtual int activationCharSequenceLength() const; + virtual bool isActivationCharSequence(const QString &sequence) const; +}; + +class GLSLCompletionAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + GLSLCompletionAssistProcessor(); + virtual ~GLSLCompletionAssistProcessor(); + + virtual TextEditor::IAssistProposal *perform(const TextEditor::IAssistInterface *interface); + +private: + TextEditor::IAssistProposal *createContentProposal() const; + TextEditor::IAssistProposal *createHintProposal(const QVector<GLSL::Function *> &symbols); + bool acceptsIdleEditor() const; + void addCompletion(const QString &text, const QIcon &icon, int order = 0); + + int m_startPosition; + QScopedPointer<const GLSLCompletionAssistInterface> m_interface; + QList<TextEditor::BasicProposalItem *> m_completions; + + QIcon m_keywordIcon; + QIcon m_varIcon; + QIcon m_functionIcon; + QIcon m_typeIcon; + QIcon m_constIcon; + QIcon m_attributeIcon; + QIcon m_uniformIcon; + QIcon m_varyingIcon; + QIcon m_otherIcon; +}; + +class GLSLCompletionAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + GLSLCompletionAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + TextEditor::AssistReason reason, + const QString &mimeType, + const Document::Ptr &glslDoc); + + const QString &mimeType() const { return m_mimeType; } + const Document::Ptr &glslDocument() const { return m_glslDoc; } + +private: + QString m_mimeType; + Document::Ptr m_glslDoc; +}; + +} // Internal +} // GLSLEditor + +#endif // GLSLCOMPLETIONASSIST_H diff --git a/src/plugins/glsleditor/glsleditor.cpp b/src/plugins/glsleditor/glsleditor.cpp index 83c84d13e9..18fa55228f 100644 --- a/src/plugins/glsleditor/glsleditor.cpp +++ b/src/plugins/glsleditor/glsleditor.cpp @@ -37,6 +37,7 @@ #include "glslhighlighter.h" #include "glslautocompleter.h" #include "glslindenter.h" +#include "glslcompletionassist.h" #include <glsl/glsllexer.h> #include <glsl/glslparser.h> @@ -412,3 +413,17 @@ Document::Ptr GLSLTextEditorWidget::glslDocument() const { return m_glslDocument; } + +TextEditor::IAssistInterface *GLSLTextEditorWidget::createAssistInterface( + TextEditor::AssistKind kind, + TextEditor::AssistReason reason) const +{ + if (kind == TextEditor::Completion) + return new GLSLCompletionAssistInterface(document(), + position(), + editor()->file(), + reason, + mimeType(), + glslDocument()); + return BaseTextEditorWidget::createAssistInterface(kind, reason); +} diff --git a/src/plugins/glsleditor/glsleditor.h b/src/plugins/glsleditor/glsleditor.h index 8c5712d9be..ba228f4c22 100644 --- a/src/plugins/glsleditor/glsleditor.h +++ b/src/plugins/glsleditor/glsleditor.h @@ -104,6 +104,9 @@ public: Document::Ptr glslDocument() const; + TextEditor::IAssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, + TextEditor::AssistReason reason) const; + public slots: virtual void setFontSettings(const TextEditor::FontSettings &); diff --git a/src/plugins/glsleditor/glsleditor.pro b/src/plugins/glsleditor/glsleditor.pro index be0cdf3b43..7a5385842a 100644 --- a/src/plugins/glsleditor/glsleditor.pro +++ b/src/plugins/glsleditor/glsleditor.pro @@ -17,10 +17,11 @@ glsleditorfactory.h \ glsleditorplugin.h \ glslfilewizard.h \ glslhighlighter.h \ -glslcodecompletion.h \ glslautocompleter.h \ glslindenter.h \ -glslhoverhandler.h +glslhoverhandler.h \ + glslcompletionassist.h \ + reuse.h SOURCES += \ glsleditor.cpp \ @@ -30,10 +31,11 @@ glsleditorfactory.cpp \ glsleditorplugin.cpp \ glslfilewizard.cpp \ glslhighlighter.cpp \ -glslcodecompletion.cpp \ glslautocompleter.cpp \ glslindenter.cpp \ -glslhoverhandler.cpp +glslhoverhandler.cpp \ + glslcompletionassist.cpp \ + reuse.cpp OTHER_FILES += GLSLEditor.mimetypes.xml RESOURCES += glsleditor.qrc diff --git a/src/plugins/glsleditor/glsleditorplugin.cpp b/src/plugins/glsleditor/glsleditorplugin.cpp index 7e372bfea1..319982888a 100644 --- a/src/plugins/glsleditor/glsleditorplugin.cpp +++ b/src/plugins/glsleditor/glsleditorplugin.cpp @@ -34,9 +34,9 @@ #include "glsleditor.h" #include "glsleditorconstants.h" #include "glsleditorfactory.h" -#include "glslcodecompletion.h" #include "glslfilewizard.h" #include "glslhoverhandler.h" +#include "glslcompletionassist.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -55,7 +55,6 @@ #include <texteditor/texteditorsettings.h> #include <texteditor/textfilewizard.h> #include <texteditor/texteditoractionhandler.h> -#include <texteditor/completionsupport.h> #include <utils/qtcassert.h> #include <glsl/glslengine.h> @@ -132,8 +131,7 @@ bool GLSLEditorPlugin::initialize(const QStringList & /*arguments*/, QString *er m_editor = new GLSLEditorFactory(this); addObject(m_editor); - CodeCompletion *completion = new CodeCompletion(this); - addAutoReleasedObject(completion); + addAutoReleasedObject(new GLSLCompletionAssistProvider); m_actionHandler = new TextEditor::TextEditorActionHandler(GLSLEditor::Constants::C_GLSLEDITOR_ID, TextEditor::TextEditorActionHandler::Format @@ -164,12 +162,6 @@ bool GLSLEditorPlugin::initialize(const QStringList & /*arguments*/, QString *er cmd = am->command(TextEditor::Constants::UN_COMMENT_SELECTION); contextMenu->addAction(cmd); - // Set completion settings and keep them up to date - TextEditor::TextEditorSettings *textEditorSettings = TextEditor::TextEditorSettings::instance(); - completion->setCompletionSettings(textEditorSettings->completionSettings()); - connect(textEditorSettings, SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), - completion, SLOT(setCompletionSettings(TextEditor::CompletionSettings))); - error_message->clear(); Core::FileIconProvider *iconProvider = Core::FileIconProvider::instance(); diff --git a/src/plugins/glsleditor/reuse.cpp b/src/plugins/glsleditor/reuse.cpp new file mode 100644 index 0000000000..aeb8932513 --- /dev/null +++ b/src/plugins/glsleditor/reuse.cpp @@ -0,0 +1,83 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "reuse.h" + +#include <QtCore/QLatin1String> + +#include <glsl/glsllexer.h> + +using namespace GLSL; + +namespace GLSLEditor { +namespace Internal { + +int languageVariant(const QString &mimeType) +{ + int variant = 0; + bool isVertex = false; + bool isFragment = false; + bool isDesktop = false; + if (mimeType.isEmpty()) { + // ### Before file has been opened, so don't know the mime type. + isVertex = true; + isFragment = true; + } else if (mimeType == QLatin1String("text/x-glsl") || + mimeType == QLatin1String("application/x-glsl")) { + isVertex = true; + isFragment = true; + isDesktop = true; + } else if (mimeType == QLatin1String("text/x-glsl-vert")) { + isVertex = true; + isDesktop = true; + } else if (mimeType == QLatin1String("text/x-glsl-frag")) { + isFragment = true; + isDesktop = true; + } else if (mimeType == QLatin1String("text/x-glsl-es-vert")) { + isVertex = true; + } else if (mimeType == QLatin1String("text/x-glsl-es-frag")) { + isFragment = true; + } + if (isDesktop) + variant |= Lexer::Variant_GLSL_120; + else + variant |= Lexer::Variant_GLSL_ES_100; + if (isVertex) + variant |= Lexer::Variant_VertexShader; + if (isFragment) + variant |= Lexer::Variant_FragmentShader; + return variant; +} + +} // Internal +} // GLSLEditor + diff --git a/src/plugins/glsleditor/reuse.h b/src/plugins/glsleditor/reuse.h new file mode 100644 index 0000000000..5fb12112e6 --- /dev/null +++ b/src/plugins/glsleditor/reuse.h @@ -0,0 +1,46 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef REUSE_H +#define REUSE_H + +#include <QtCore/QtGlobal> + +namespace GLSLEditor { +namespace Internal { + +int languageVariant(const QString &mimeType); + +} // Internal +} // GLSLEditor + +#endif // REUSE_H diff --git a/src/plugins/qmljseditor/qmljscodecompletion.cpp b/src/plugins/qmljseditor/qmljscodecompletion.cpp deleted file mode 100644 index 9ad60f4f51..0000000000 --- a/src/plugins/qmljseditor/qmljscodecompletion.cpp +++ /dev/null @@ -1,1146 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "qmljscodecompletion.h" -#include "qmlexpressionundercursor.h" -#include "qmljseditor.h" -#include "qmljseditorconstants.h" - -#include <qmljs/qmljsmodelmanagerinterface.h> -#include <qmljs/parser/qmljsast_p.h> -#include <qmljs/qmljsinterpreter.h> -#include <qmljs/qmljslookupcontext.h> -#include <qmljs/qmljsscanner.h> -#include <qmljs/qmljsbind.h> -#include <qmljs/qmljscompletioncontextfinder.h> -#include <qmljs/qmljsscopebuilder.h> - -#include <texteditor/basetexteditor.h> -#include <texteditor/completionsettings.h> - -#include <coreplugin/icore.h> -#include <coreplugin/editormanager/editormanager.h> - -#include <utils/faketooltip.h> -#include <utils/qtcassert.h> - -#include <QtCore/QFile> -#include <QtCore/QFileInfo> -#include <QtCore/QDir> -#include <QtCore/QDebug> -#include <QtCore/QDirIterator> - -#include <QtGui/QApplication> -#include <QtGui/QDesktopWidget> -#include <QtGui/QHBoxLayout> -#include <QtGui/QLabel> -#include <QtGui/QPainter> -#include <QtGui/QStyle> -#include <QtGui/QTextBlock> -#include <QtGui/QToolButton> - -using namespace QmlJSEditor; -using namespace QmlJSEditor::Internal; -using namespace QmlJS; - -namespace { - -enum CompletionOrder { - EnumValueOrder = -5, - SnippetOrder = -15, - PropertyOrder = -10, - SymbolOrder = -20, - KeywordOrder = -25, - TypeOrder = -30 -}; - -// Temporary workaround until we have proper icons for QML completion items -static QIcon iconForColor(const QColor &color) -{ - QPixmap pix(6, 6); - - int pixSize = 20; - QBrush br(color); - - QPixmap pm(2 * pixSize, 2 * pixSize); - QPainter pmp(&pm); - pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); - pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); - pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); - pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); - pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, color); - br = QBrush(pm); - - QPainter p(&pix); - int corr = 1; - QRect r = pix.rect().adjusted(corr, corr, -corr, -corr); - p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); - p.fillRect(r, br); - - p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr, - r.width() / 2, r.height() / 2, - QColor(color.rgb())); - p.drawRect(pix.rect().adjusted(0, 0, -1, -1)); - - return pix; -} - -static bool checkStartOfIdentifier(const QString &word) -{ - if (word.isEmpty()) - return false; - - const QChar ch = word.at(0); - - switch (ch.unicode()) { - case '_': case '$': - return true; - - default: - return ch.isLetter(); - } -} - -static bool isIdentifierChar(QChar ch) -{ - switch (ch.unicode()) { - case '_': case '$': - return true; - - default: - return ch.isLetterOrNumber(); - } -} - -class SearchPropertyDefinitions: protected AST::Visitor -{ - QList<AST::UiPublicMember *> _properties; - -public: - QList<AST::UiPublicMember *> operator()(Document::Ptr doc) - { - _properties.clear(); - if (doc && doc->qmlProgram()) - doc->qmlProgram()->accept(this); - return _properties; - } - - -protected: - using AST::Visitor::visit; - - virtual bool visit(AST::UiPublicMember *member) - { - if (member->propertyToken.isValid()) { - _properties.append(member); - } - - return true; - } -}; - -class EnumerateProperties: private Interpreter::MemberProcessor -{ - QSet<const Interpreter::ObjectValue *> _processed; - QHash<QString, const Interpreter::Value *> _properties; - bool _globalCompletion; - bool _enumerateGeneratedSlots; - const Interpreter::Context *_context; - const Interpreter::ObjectValue *_currentObject; - -public: - EnumerateProperties(const Interpreter::Context *context) - : _globalCompletion(false), - _enumerateGeneratedSlots(false), - _context(context), - _currentObject(0) - { - } - - void setGlobalCompletion(bool globalCompletion) - { - _globalCompletion = globalCompletion; - } - - void setEnumerateGeneratedSlots(bool enumerate) - { - _enumerateGeneratedSlots = enumerate; - } - - QHash<QString, const Interpreter::Value *> operator ()(const Interpreter::Value *value) - { - _processed.clear(); - _properties.clear(); - _currentObject = Interpreter::value_cast<const Interpreter::ObjectValue *>(value); - - enumerateProperties(value); - - return _properties; - } - - QHash<QString, const Interpreter::Value *> operator ()() - { - _processed.clear(); - _properties.clear(); - _currentObject = 0; - - foreach (const Interpreter::ObjectValue *scope, _context->scopeChain().all()) - enumerateProperties(scope); - - return _properties; - } - -private: - void insertProperty(const QString &name, const Interpreter::Value *value) - { - _properties.insert(name, value); - } - - virtual bool processProperty(const QString &name, const Interpreter::Value *value) - { - insertProperty(name, value); - return true; - } - - virtual bool processEnumerator(const QString &name, const Interpreter::Value *value) - { - if (! _globalCompletion) - insertProperty(name, value); - return true; - } - - virtual bool processSignal(const QString &, const Interpreter::Value *) - { - return true; - } - - virtual bool processSlot(const QString &name, const Interpreter::Value *value) - { - insertProperty(name, value); - return true; - } - - virtual bool processGeneratedSlot(const QString &name, const Interpreter::Value *value) - { - if (_enumerateGeneratedSlots || (_currentObject && _currentObject->className().endsWith(QLatin1String("Keys")))) { - // ### FIXME: add support for attached properties. - insertProperty(name, value); - } - return true; - } - - void enumerateProperties(const Interpreter::Value *value) - { - if (! value) - return; - else if (const Interpreter::ObjectValue *object = value->asObjectValue()) { - enumerateProperties(object); - } - } - - void enumerateProperties(const Interpreter::ObjectValue *object) - { - if (! object || _processed.contains(object)) - return; - - _processed.insert(object); - enumerateProperties(object->prototype(_context)); - - object->processMembers(this); - } -}; - -} // end of anonymous namespace - -namespace QmlJSEditor { -namespace Internal { - -class FunctionArgumentWidget : public QLabel -{ -public: - FunctionArgumentWidget(); - void showFunctionHint(const QString &functionName, - const QStringList &signature, - int startPosition); - -protected: - bool eventFilter(QObject *obj, QEvent *e); - -private: - void updateArgumentHighlight(); - void updateHintText(); - - QString m_functionName; - QStringList m_signature; - int m_minimumArgumentCount; - int m_startpos; - int m_currentarg; - int m_current; - bool m_escapePressed; - - TextEditor::ITextEditor *m_editor; - - QWidget *m_pager; - QLabel *m_numberLabel; - Utils::FakeToolTip *m_popupFrame; -}; - - -FunctionArgumentWidget::FunctionArgumentWidget(): - m_minimumArgumentCount(0), - m_startpos(-1), - m_current(0), - m_escapePressed(false) -{ - QObject *editorObject = Core::EditorManager::instance()->currentEditor(); - m_editor = qobject_cast<TextEditor::ITextEditor *>(editorObject); - - m_popupFrame = new Utils::FakeToolTip(m_editor->widget()); - - setParent(m_popupFrame); - setFocusPolicy(Qt::NoFocus); - - m_pager = new QWidget; - QHBoxLayout *hbox = new QHBoxLayout(m_pager); - hbox->setMargin(0); - hbox->setSpacing(0); - m_numberLabel = new QLabel; - hbox->addWidget(m_numberLabel); - - QHBoxLayout *layout = new QHBoxLayout; - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(m_pager); - layout->addWidget(this); - m_popupFrame->setLayout(layout); - - setTextFormat(Qt::RichText); - - qApp->installEventFilter(this); -} - -void FunctionArgumentWidget::showFunctionHint(const QString &functionName, const QStringList &signature, int startPosition) -{ - if (m_startpos == startPosition) - return; - - m_functionName = functionName; - m_signature = signature; - m_minimumArgumentCount = signature.size(); - m_startpos = startPosition; - m_current = 0; - m_escapePressed = false; - - // update the text - m_currentarg = -1; - updateArgumentHighlight(); - - m_popupFrame->show(); -} - -void FunctionArgumentWidget::updateArgumentHighlight() -{ - int curpos = m_editor->position(); - if (curpos < m_startpos) { - m_popupFrame->close(); - return; - } - - updateHintText(); - - QString str = m_editor->textAt(m_startpos, curpos - m_startpos); - int argnr = 0; - int parcount = 0; - Scanner tokenize; - const QList<Token> tokens = tokenize(str); - for (int i = 0; i < tokens.count(); ++i) { - const Token &tk = tokens.at(i); - if (tk.is(Token::LeftParenthesis)) - ++parcount; - else if (tk.is(Token::RightParenthesis)) - --parcount; - else if (! parcount && tk.is(Token::Colon)) - ++argnr; - } - - if (m_currentarg != argnr) { - // m_currentarg = argnr; - updateHintText(); - } - - if (parcount < 0) - m_popupFrame->close(); -} - -bool FunctionArgumentWidget::eventFilter(QObject *obj, QEvent *e) -{ - switch (e->type()) { - case QEvent::ShortcutOverride: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyRelease: - if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_escapePressed) { - m_popupFrame->close(); - return false; - } - updateArgumentHighlight(); - break; - case QEvent::WindowDeactivate: - case QEvent::FocusOut: - if (obj != m_editor->widget()) - break; - m_popupFrame->close(); - break; - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseButtonDblClick: - case QEvent::Wheel: { - QWidget *widget = qobject_cast<QWidget *>(obj); - if (! (widget == this || m_popupFrame->isAncestorOf(widget))) { - m_popupFrame->close(); - } - } - break; - default: - break; - } - return false; -} - -void FunctionArgumentWidget::updateHintText() -{ - QString prettyMethod; - prettyMethod += QString::fromLatin1("function "); - prettyMethod += m_functionName; - prettyMethod += QLatin1Char('('); - for (int i = 0; i < m_minimumArgumentCount; ++i) { - if (i != 0) - prettyMethod += QLatin1String(", "); - - QString arg = m_signature.at(i); - if (arg.isEmpty()) { - arg = QLatin1String("arg"); - arg += QString::number(i + 1); - } - - prettyMethod += arg; - } - prettyMethod += QLatin1Char(')'); - - m_numberLabel->setText(prettyMethod); - - m_popupFrame->setFixedWidth(m_popupFrame->minimumSizeHint().width()); - - const QDesktopWidget *desktop = QApplication::desktop(); -#ifdef Q_WS_MAC - const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_editor->widget())); -#else - const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_editor->widget())); -#endif - - const QSize sz = m_popupFrame->sizeHint(); - QPoint pos = m_editor->cursorRect(m_startpos).topLeft(); - pos.setY(pos.y() - sz.height() - 1); - - if (pos.x() + sz.width() > screen.right()) - pos.setX(screen.right() - sz.width()); - - m_popupFrame->move(pos); -} - -} } // namespace QmlJSEditor::Internal - -CodeCompletion::CodeCompletion(ModelManagerInterface *modelManager, QObject *parent) - : TextEditor::ICompletionCollector(parent), - m_modelManager(modelManager), - m_editor(0), - m_startPosition(0), - m_restartCompletion(false), - m_snippetProvider(Constants::QML_SNIPPETS_GROUP_ID, iconForColor(Qt::red), SnippetOrder) -{ - Q_ASSERT(modelManager); -} - -CodeCompletion::~CodeCompletion() -{ } - -TextEditor::ITextEditor *CodeCompletion::editor() const -{ return m_editor; } - -int CodeCompletion::startPosition() const -{ return m_startPosition; } - -bool CodeCompletion::shouldRestartCompletion() -{ return m_restartCompletion; } - -bool CodeCompletion::supportsEditor(TextEditor::ITextEditor *editor) const -{ - if (qobject_cast<QmlJSTextEditorWidget *>(editor->widget())) - return true; - - return false; -} - -bool CodeCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const -{ - return policy == TextEditor::SemanticCompletion; -} - -bool CodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) -{ - if (maybeTriggersCompletion(editor)) { - // check the token under cursor - - if (QmlJSTextEditorWidget *ed = qobject_cast<QmlJSTextEditorWidget *>(editor->widget())) { - - QTextCursor tc = ed->textCursor(); - QTextBlock block = tc.block(); - const int column = tc.positionInBlock(); - const QChar ch = block.text().at(column - 1); - const int blockState = qMax(0, block.previous().userState()) & 0xff; - const QString blockText = block.text(); - - Scanner scanner; - const QList<Token> tokens = scanner(blockText, blockState); - foreach (const Token &tk, tokens) { - if (column >= tk.begin() && column <= tk.end()) { - if (ch == QLatin1Char('/') && tk.is(Token::String)) - return true; // path completion inside string literals - if (tk.is(Token::Comment) || tk.is(Token::String)) - return false; - break; - } - } - if (ch == QLatin1Char('/')) - return false; - } - return true; - } - - return false; -} - -bool CodeCompletion::maybeTriggersCompletion(TextEditor::ITextEditor *editor) -{ - const int cursorPosition = editor->position(); - const QChar ch = editor->characterAt(cursorPosition - 1); - - if (ch == QLatin1Char('(') || ch == QLatin1Char('.') || ch == QLatin1Char('/')) - return true; - if (completionSettings().m_completionTrigger != TextEditor::AutomaticCompletion) - return false; - - const QChar characterUnderCursor = editor->characterAt(cursorPosition); - - if (isIdentifierChar(ch) && (characterUnderCursor.isSpace() || - characterUnderCursor.isNull() || - isDelimiter(characterUnderCursor))) { - int pos = editor->position() - 1; - for (; pos != -1; --pos) { - if (! isIdentifierChar(editor->characterAt(pos))) - break; - } - ++pos; - - const QString word = editor->textAt(pos, cursorPosition - pos); - if (word.length() > 2 && checkStartOfIdentifier(word)) { - for (int i = 0; i < word.length(); ++i) { - if (! isIdentifierChar(word.at(i))) - return false; - } - return true; - } - } - - return false; -} - -bool CodeCompletion::isDelimiter(QChar ch) const -{ - switch (ch.unicode()) { - case '{': - case '}': - case '[': - case ']': - case ')': - case '?': - case '!': - case ':': - case ';': - case ',': - case '+': - case '-': - case '*': - case '/': - return true; - - default: - return false; - } -} - -static bool isLiteral(AST::Node *ast) -{ - if (AST::cast<AST::StringLiteral *>(ast)) - return true; - else if (AST::cast<AST::NumericLiteral *>(ast)) - return true; - else - return false; -} - -bool CodeCompletion::completeUrl(const QString &relativeBasePath, const QString &urlString) -{ - const QUrl url(urlString); - QString fileName = url.toLocalFile(); - if (fileName.isEmpty()) - return false; - - return completeFileName(relativeBasePath, fileName); -} - -class FileNameCompletion -{ -public: - bool isDirectory; -}; -Q_DECLARE_METATYPE(FileNameCompletion) - -bool CodeCompletion::completeFileName(const QString &relativeBasePath, const QString &fileName, - const QStringList &patterns) -{ - const QFileInfo fileInfo(fileName); - QString directoryPrefix; - if (fileInfo.isRelative()) { - directoryPrefix = relativeBasePath; - directoryPrefix += QDir::separator(); - directoryPrefix += fileInfo.path(); - } else { - directoryPrefix = fileInfo.path(); - } - if (!QFileInfo(directoryPrefix).exists()) - return false; - - QDirIterator dirIterator(directoryPrefix, patterns, QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); - while (dirIterator.hasNext()) { - dirIterator.next(); - const QString fileName = dirIterator.fileName(); - - TextEditor::CompletionItem item(this); - item.text += fileName; - FileNameCompletion extraData; - extraData.isDirectory = dirIterator.fileInfo().isDir(); - if (extraData.isDirectory) - item.text += QLatin1Char('/'); - item.data = QVariant::fromValue(extraData); - // ### Icon for file completions - item.icon = iconForColor(Qt::darkBlue); - m_completions.append(item); - } - - return !m_completions.isEmpty(); -} - -void CodeCompletion::addCompletions(const QHash<QString, const Interpreter::Value *> &newCompletions, - const QIcon &icon, int order) -{ - QHashIterator<QString, const Interpreter::Value *> it(newCompletions); - while (it.hasNext()) { - it.next(); - - TextEditor::CompletionItem item(this); - item.text = it.key(); - item.icon = icon; - item.order = order; - m_completions.append(item); - } -} - -void CodeCompletion::addCompletions(const QStringList &newCompletions, - const QIcon &icon, int order) -{ - foreach (const QString &text, newCompletions) { - TextEditor::CompletionItem item(this); - item.text = text; - item.icon = icon; - item.order = order; - m_completions.append(item); - } -} - -void CodeCompletion::addCompletionsPropertyLhs( - const QHash<QString, const Interpreter::Value *> &newCompletions, - const QIcon &icon, int order, bool afterOn) -{ - QHashIterator<QString, const Interpreter::Value *> it(newCompletions); - while (it.hasNext()) { - it.next(); - - TextEditor::CompletionItem item(this); - item.text = it.key(); - - QLatin1String postfix(": "); - if (afterOn) - postfix = QLatin1String(" {"); - if (const Interpreter::QmlObjectValue *qmlValue = dynamic_cast<const Interpreter::QmlObjectValue *>(it.value())) { - // to distinguish "anchors." from "gradient:" we check if the right hand side - // type is instantiatable or is the prototype of an instantiatable object - if (qmlValue->hasChildInPackage()) - item.text.append(postfix); - else - item.text.append(QLatin1Char('.')); - } else { - item.text.append(postfix); - } - item.icon = icon; - item.order = order; - m_completions.append(item); - } -} - -static const Interpreter::Value *getPropertyValue( - const Interpreter::ObjectValue *object, - const QStringList &propertyNames, - const Interpreter::Context *context) -{ - if (propertyNames.isEmpty() || !object) - return 0; - - const Interpreter::Value *value = object; - foreach (const QString &name, propertyNames) { - if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { - value = objectValue->property(name, context); - if (!value) - return 0; - } else { - return 0; - } - } - return value; -} - -int CodeCompletion::startCompletion(TextEditor::ITextEditor *editor) -{ - m_restartCompletion = false; - - m_editor = editor; - - QmlJSTextEditorWidget *edit = qobject_cast<QmlJSTextEditorWidget *>(m_editor->widget()); - if (! edit) - return -1; - - m_startPosition = editor->position(); - const QString fileName = editor->file()->fileName(); - - while (editor->characterAt(m_startPosition - 1).isLetterOrNumber() || - editor->characterAt(m_startPosition - 1) == QLatin1Char('_')) - --m_startPosition; - - m_completions.clear(); - - const SemanticInfo semanticInfo = edit->semanticInfo(); - - if (! semanticInfo.isValid()) - return -1; - - const Document::Ptr document = semanticInfo.document; - const QFileInfo currentFileInfo(fileName); - - bool isQmlFile = false; - if (currentFileInfo.suffix() == QLatin1String("qml")) - isQmlFile = true; - - const QIcon symbolIcon = iconForColor(Qt::darkCyan); - const QIcon keywordIcon = iconForColor(Qt::darkYellow); - - const QList<AST::Node *> path = semanticInfo.astPath(editor->position()); - LookupContext::Ptr lookupContext = semanticInfo.lookupContext(path); - const Interpreter::Context *context = lookupContext->context(); - - // Search for the operator that triggered the completion. - QChar completionOperator; - if (m_startPosition > 0) - completionOperator = editor->characterAt(m_startPosition - 1); - - QTextCursor startPositionCursor(edit->document()); - startPositionCursor.setPosition(m_startPosition); - CompletionContextFinder contextFinder(startPositionCursor); - - const Interpreter::ObjectValue *qmlScopeType = 0; - if (contextFinder.isInQmlContext()) { - // ### this should use semanticInfo.declaringMember instead, but that may also return functions - for (int i = path.size() - 1; i >= 0; --i) { - AST::Node *node = path[i]; - if (AST::cast<AST::UiObjectDefinition *>(node) || AST::cast<AST::UiObjectBinding *>(node)) { - qmlScopeType = document->bind()->findQmlObject(node); - if (qmlScopeType) - break; - } - } - // fallback to getting the base type object - if (!qmlScopeType) - qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName()); - } - - if (contextFinder.isInStringLiteral()) { - // get the text of the literal up to the cursor position - QTextCursor tc = edit->textCursor(); - QmlExpressionUnderCursor expressionUnderCursor; - expressionUnderCursor(tc); - QString literalText = expressionUnderCursor.text(); - QTC_ASSERT(!literalText.isEmpty() && ( - literalText.at(0) == QLatin1Char('"') - || literalText.at(0) == QLatin1Char('\'')), return -1); - literalText = literalText.mid(1); - - if (contextFinder.isInImport()) { - QStringList patterns; - patterns << QLatin1String("*.qml") << QLatin1String("*.js"); - if (completeFileName(document->path(), literalText, patterns)) - return m_startPosition; - return -1; - } - - const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context); - if (!value) { - // do nothing - } else if (value->asUrlValue()) { - if (completeUrl(document->path(), literalText)) - return m_startPosition; - } - - // ### enum completion? - - // completion gets triggered for / in string literals, if we don't - // return here, this will mean the snippet completion pops up for - // each / in a string literal that is not triggering file completion - return -1; - } else if (completionOperator.isSpace() || completionOperator.isNull() || isDelimiter(completionOperator) || - (completionOperator == QLatin1Char('(') && m_startPosition != editor->position())) { - - bool doGlobalCompletion = true; - bool doQmlKeywordCompletion = true; - bool doJsKeywordCompletion = true; - bool doQmlTypeCompletion = false; - - if (contextFinder.isInLhsOfBinding() && qmlScopeType) { - doGlobalCompletion = false; - doJsKeywordCompletion = false; - doQmlTypeCompletion = true; - - EnumerateProperties enumerateProperties(context); - enumerateProperties.setGlobalCompletion(true); - enumerateProperties.setEnumerateGeneratedSlots(true); - - // id: is special - TextEditor::CompletionItem idPropertyCompletion(this); - idPropertyCompletion.text = QLatin1String("id: "); - idPropertyCompletion.icon = symbolIcon; - idPropertyCompletion.order = PropertyOrder; - m_completions.append(idPropertyCompletion); - - addCompletionsPropertyLhs(enumerateProperties(qmlScopeType), symbolIcon, PropertyOrder, contextFinder.isAfterOnInLhsOfBinding()); - - if (ScopeBuilder::isPropertyChangesObject(context, qmlScopeType) - && context->scopeChain().qmlScopeObjects.size() == 2) { - addCompletions(enumerateProperties(context->scopeChain().qmlScopeObjects.first()), symbolIcon, SymbolOrder); - } - } - - if (contextFinder.isInRhsOfBinding() && qmlScopeType) { - doQmlKeywordCompletion = false; - - // complete enum values for enum properties - const Interpreter::Value *value = getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context); - if (const Interpreter::QmlEnumValue *enumValue = dynamic_cast<const Interpreter::QmlEnumValue *>(value)) { - foreach (const QString &key, enumValue->keys()) { - TextEditor::CompletionItem item(this); - item.text = key; - item.data = QString("\"%1\"").arg(key); - item.icon = symbolIcon; - item.order = EnumValueOrder; - m_completions.append(item); - } - } - } - - if (!contextFinder.isInImport() && !contextFinder.isInQmlContext()) - doQmlTypeCompletion = true; - - if (doQmlTypeCompletion) { - if (const Interpreter::ObjectValue *qmlTypes = context->scopeChain().qmlTypes) { - EnumerateProperties enumerateProperties(context); - addCompletions(enumerateProperties(qmlTypes), symbolIcon, TypeOrder); - } - } - - if (doGlobalCompletion) { - // It's a global completion. - EnumerateProperties enumerateProperties(context); - enumerateProperties.setGlobalCompletion(true); - addCompletions(enumerateProperties(), symbolIcon, SymbolOrder); - } - - if (doJsKeywordCompletion) { - // add js keywords - addCompletions(Scanner::keywords(), keywordIcon, KeywordOrder); - } - - // add qml extra words - if (doQmlKeywordCompletion && isQmlFile) { - static QStringList qmlWords; - static QStringList qmlWordsAlsoInJs; - - if (qmlWords.isEmpty()) { - qmlWords << QLatin1String("property") - //<< QLatin1String("readonly") - << QLatin1String("signal") - << QLatin1String("import"); - } - if (qmlWordsAlsoInJs.isEmpty()) { - qmlWordsAlsoInJs << QLatin1String("default") - << QLatin1String("function"); - } - - addCompletions(qmlWords, keywordIcon, KeywordOrder); - if (!doJsKeywordCompletion) - addCompletions(qmlWordsAlsoInJs, keywordIcon, KeywordOrder); - } - } - - else if (completionOperator == QLatin1Char('.') || completionOperator == QLatin1Char('(')) { - // Look at the expression under cursor. - QTextCursor tc = edit->textCursor(); - tc.setPosition(m_startPosition - 1); - - QmlExpressionUnderCursor expressionUnderCursor; - QmlJS::AST::ExpressionNode *expression = expressionUnderCursor(tc); - - if (expression != 0 && ! isLiteral(expression)) { - // Evaluate the expression under cursor. - Interpreter::Engine *interp = lookupContext->engine(); - const Interpreter::Value *value = interp->convertToObject(lookupContext->evaluate(expression)); - //qDebug() << "type:" << interp.typeId(value); - - if (value && completionOperator == QLatin1Char('.')) { // member completion - EnumerateProperties enumerateProperties(context); - if (contextFinder.isInLhsOfBinding() && qmlScopeType) { - enumerateProperties.setEnumerateGeneratedSlots(true); - addCompletionsPropertyLhs(enumerateProperties(value), symbolIcon, PropertyOrder, contextFinder.isAfterOnInLhsOfBinding()); - } else - addCompletions(enumerateProperties(value), symbolIcon, SymbolOrder); - } else if (value && completionOperator == QLatin1Char('(') && m_startPosition == editor->position()) { - // function completion - if (const Interpreter::FunctionValue *f = value->asFunctionValue()) { - QString functionName = expressionUnderCursor.text(); - int indexOfDot = functionName.lastIndexOf(QLatin1Char('.')); - if (indexOfDot != -1) - functionName = functionName.mid(indexOfDot + 1); - - // Recreate if necessary - if (!m_functionArgumentWidget) - m_functionArgumentWidget = new QmlJSEditor::Internal::FunctionArgumentWidget; - - QStringList signature; - for (int i = 0; i < f->argumentCount(); ++i) - signature.append(f->argumentName(i)); - - m_functionArgumentWidget->showFunctionHint(functionName.trimmed(), - signature, - m_startPosition); - } - - return -1; // We always return -1 when completing function prototypes. - } - } - - if (! m_completions.isEmpty()) - return m_startPosition; - - return -1; - } - - if (isQmlFile && (completionOperator.isNull() || completionOperator.isSpace() || isDelimiter(completionOperator))) { - m_completions.append(m_snippetProvider.getSnippets(this)); - } - - if (! m_completions.isEmpty()) - return m_startPosition; - - return -1; -} - -void CodeCompletion::completions(QList<TextEditor::CompletionItem> *completions) -{ - const int length = m_editor->position() - m_startPosition; - - if (length == 0) - *completions = m_completions; - else if (length > 0) { - const QString key = m_editor->textAt(m_startPosition, length); - - filter(m_completions, completions, key); - - if (completions->size() == 1) { - if (key == completions->first().text) - completions->clear(); - } - } -} - -bool CodeCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) -{ - if (item.data.canConvert<QString>()) // snippet - return false; - - return (item.text.endsWith(QLatin1String(": ")) && typedChar == QLatin1Char(':')) - || (item.text.endsWith(QLatin1Char('.')) && typedChar == QLatin1Char('.')); -} - -void CodeCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) -{ - Q_UNUSED(typedChar) // Currently always included in the completion item when used - - QString toInsert = item.text; - - if (QmlJSTextEditorWidget *edit = qobject_cast<QmlJSTextEditorWidget *>(m_editor->widget())) { - if (item.data.canConvert<QString>()) { - QTextCursor tc = edit->textCursor(); - tc.setPosition(m_startPosition, QTextCursor::KeepAnchor); - toInsert = item.data.toString(); - edit->insertCodeSnippet(tc, toInsert); - return; - } - } - - QString replacableChars; - if (toInsert.endsWith(QLatin1String(": "))) - replacableChars = QLatin1String(": "); - else if (toInsert.endsWith(QLatin1Char('.'))) - replacableChars = QLatin1String("."); - - int replacedLength = 0; - - // Avoid inserting characters that are already there - for (int i = 0; i < replacableChars.length(); ++i) { - const QChar a = replacableChars.at(i); - const QChar b = m_editor->characterAt(m_editor->position() + i); - if (a == b) - ++replacedLength; - else - break; - } - - const int length = m_editor->position() - m_startPosition + replacedLength; - m_editor->setCursorPosition(m_startPosition); - m_editor->replace(length, toInsert); - - if (toInsert.endsWith(QLatin1Char('.'))) - m_restartCompletion = true; - if (item.data.canConvert<FileNameCompletion>() && item.data.value<FileNameCompletion>().isDirectory) - m_restartCompletion = true; -} - -bool CodeCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) -{ - if (completionItems.count() == 1) { - const TextEditor::CompletionItem item = completionItems.first(); - - if (!item.data.canConvert<QString>()) { - complete(item, QChar()); - return true; - } - } - - return TextEditor::ICompletionCollector::partiallyComplete(completionItems); -} - -void CodeCompletion::cleanup() -{ - m_editor = 0; - m_startPosition = 0; - m_completions.clear(); -} - -static bool qmlCompletionItemLessThan(const TextEditor::CompletionItem &l, const TextEditor::CompletionItem &r) -{ - if (l.order != r.order) - return l.order > r.order; - else if (l.text.isEmpty()) - return true; - else if (r.text.isEmpty()) - return false; - else if (l.data.isValid() != r.data.isValid()) - return l.data.isValid(); - else if (l.text.at(0).isUpper() && r.text.at(0).isLower()) - return false; - else if (l.text.at(0).isLower() && r.text.at(0).isUpper()) - return true; - - return l.text < r.text; -} - -void CodeCompletion::sortCompletion(QList<TextEditor::CompletionItem> &completionItems) -{ - qStableSort(completionItems.begin(), completionItems.end(), qmlCompletionItemLessThan); -} - -QList<TextEditor::CompletionItem> CodeCompletion::getCompletions() -{ - QList<TextEditor::CompletionItem> completionItems; - - completions(&completionItems); - - sortCompletion(completionItems); - - // Remove duplicates - QString lastKey; - QVariant lastData; - QList<TextEditor::CompletionItem> uniquelist; - - foreach (const TextEditor::CompletionItem &item, completionItems) { - if (item.text != lastKey || item.data.type() != lastData.type()) { - uniquelist.append(item); - lastKey = item.text; - lastData = item.data; - } - } - - return uniquelist; -} diff --git a/src/plugins/qmljseditor/qmljscodecompletion.h b/src/plugins/qmljseditor/qmljscodecompletion.h deleted file mode 100644 index ba3083448c..0000000000 --- a/src/plugins/qmljseditor/qmljscodecompletion.h +++ /dev/null @@ -1,114 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#ifndef QMLJSCODECOMPLETION_H -#define QMLJSCODECOMPLETION_H - -#include <qmljs/qmljsdocument.h> -#include <texteditor/icompletioncollector.h> -#include <texteditor/snippets/snippetcollector.h> -#include <QtCore/QDateTime> -#include <QtCore/QPointer> - -namespace TextEditor { -class ITextEditor; -} - -namespace QmlJS { - class ModelManagerInterface; - - namespace Interpreter { - class Value; - } -} - -namespace QmlJSEditor { - -namespace Internal { - -class FunctionArgumentWidget; - -class CodeCompletion: public TextEditor::ICompletionCollector -{ - Q_OBJECT - -public: - explicit CodeCompletion(QmlJS::ModelManagerInterface *modelManager, QObject *parent = 0); - virtual ~CodeCompletion(); - - virtual TextEditor::ITextEditor *editor() const; - virtual int startPosition() const; - virtual bool shouldRestartCompletion(); - virtual bool supportsEditor(TextEditor::ITextEditor *editor) const; - virtual bool supportsPolicy(TextEditor::CompletionPolicy policy) const; - virtual bool triggersCompletion(TextEditor::ITextEditor *editor); - virtual int startCompletion(TextEditor::ITextEditor *editor); - virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); - virtual QList<TextEditor::CompletionItem> getCompletions(); - virtual void sortCompletion(QList<TextEditor::CompletionItem> &completionItems); - - virtual void cleanup(); - -private: - - bool maybeTriggersCompletion(TextEditor::ITextEditor *editor); - bool isDelimiter(QChar ch) const; - - bool completeUrl(const QString &relativeBasePath, const QString &urlString); - bool completeFileName(const QString &relativeBasePath, const QString &fileName, - const QStringList &patterns = QStringList()); - - void addCompletions(const QHash<QString, const QmlJS::Interpreter::Value *> &newCompletions, - const QIcon &icon, int relevance); - void addCompletions(const QStringList &newCompletions, - const QIcon &icon, int relevance); - void addCompletionsPropertyLhs( - const QHash<QString, const QmlJS::Interpreter::Value *> &newCompletions, - const QIcon &icon, int relevance, bool afterOn); - - QmlJS::ModelManagerInterface *m_modelManager; - TextEditor::ITextEditor *m_editor; - int m_startPosition; - bool m_restartCompletion; - TextEditor::SnippetCollector m_snippetProvider; - QList<TextEditor::CompletionItem> m_completions; - QPointer<FunctionArgumentWidget> m_functionArgumentWidget; -}; - - -} // namespace Internal -} // namespace QmlJSEditor - -#endif // QMLJSCODECOMPLETION_H diff --git a/src/plugins/qmljseditor/qmljscompletionassist.cpp b/src/plugins/qmljseditor/qmljscompletionassist.cpp new file mode 100644 index 0000000000..6de4e82549 --- /dev/null +++ b/src/plugins/qmljseditor/qmljscompletionassist.cpp @@ -0,0 +1,854 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmljscompletionassist.h" +#include "qmljseditorconstants.h" +#include "qmljsreuse.h" +#include "qmlexpressionundercursor.h" + +#include <coreplugin/ifile.h> + +#include <texteditor/codeassist/iassistinterface.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/functionhintproposal.h> +#include <texteditor/codeassist/ifunctionhintproposalmodel.h> + +#include <utils/qtcassert.h> + +#include <qmljs/qmljsmodelmanagerinterface.h> +#include <qmljs/parser/qmljsast_p.h> +#include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljslookupcontext.h> +#include <qmljs/qmljsscanner.h> +#include <qmljs/qmljsbind.h> +#include <qmljs/qmljscompletioncontextfinder.h> +#include <qmljs/qmljsscopebuilder.h> + +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QtAlgorithms> +#include <QtCore/QDirIterator> +#include <QtCore/QStringList> +#include <QtGui/QIcon> + +using namespace QmlJS; +using namespace QmlJSEditor; +using namespace Internal; +using namespace TextEditor; + +namespace { + +enum CompletionOrder { + EnumValueOrder = -5, + SnippetOrder = -15, + PropertyOrder = -10, + SymbolOrder = -20, + KeywordOrder = -25, + TypeOrder = -30 +}; + +class EnumerateProperties: private Interpreter::MemberProcessor +{ + QSet<const Interpreter::ObjectValue *> _processed; + QHash<QString, const Interpreter::Value *> _properties; + bool _globalCompletion; + bool _enumerateGeneratedSlots; + const Interpreter::Context *_context; + const Interpreter::ObjectValue *_currentObject; + +public: + EnumerateProperties(const Interpreter::Context *context) + : _globalCompletion(false), + _enumerateGeneratedSlots(false), + _context(context), + _currentObject(0) + { + } + + void setGlobalCompletion(bool globalCompletion) + { + _globalCompletion = globalCompletion; + } + + void setEnumerateGeneratedSlots(bool enumerate) + { + _enumerateGeneratedSlots = enumerate; + } + + QHash<QString, const Interpreter::Value *> operator ()(const Interpreter::Value *value) + { + _processed.clear(); + _properties.clear(); + _currentObject = Interpreter::value_cast<const Interpreter::ObjectValue *>(value); + + enumerateProperties(value); + + return _properties; + } + + QHash<QString, const Interpreter::Value *> operator ()() + { + _processed.clear(); + _properties.clear(); + _currentObject = 0; + + foreach (const Interpreter::ObjectValue *scope, _context->scopeChain().all()) + enumerateProperties(scope); + + return _properties; + } + +private: + void insertProperty(const QString &name, const Interpreter::Value *value) + { + _properties.insert(name, value); + } + + virtual bool processProperty(const QString &name, const Interpreter::Value *value) + { + insertProperty(name, value); + return true; + } + + virtual bool processEnumerator(const QString &name, const Interpreter::Value *value) + { + if (! _globalCompletion) + insertProperty(name, value); + return true; + } + + virtual bool processSignal(const QString &, const Interpreter::Value *) + { + return true; + } + + virtual bool processSlot(const QString &name, const Interpreter::Value *value) + { + insertProperty(name, value); + return true; + } + + virtual bool processGeneratedSlot(const QString &name, const Interpreter::Value *value) + { + if (_enumerateGeneratedSlots || (_currentObject && _currentObject->className().endsWith(QLatin1String("Keys")))) { + // ### FIXME: add support for attached properties. + insertProperty(name, value); + } + return true; + } + + void enumerateProperties(const Interpreter::Value *value) + { + if (! value) + return; + else if (const Interpreter::ObjectValue *object = value->asObjectValue()) { + enumerateProperties(object); + } + } + + void enumerateProperties(const Interpreter::ObjectValue *object) + { + if (! object || _processed.contains(object)) + return; + + _processed.insert(object); + enumerateProperties(object->prototype(_context)); + + object->processMembers(this); + } +}; + +const Interpreter::Value *getPropertyValue(const Interpreter::ObjectValue *object, + const QStringList &propertyNames, + const Interpreter::Context *context) +{ + if (propertyNames.isEmpty() || !object) + return 0; + + const Interpreter::Value *value = object; + foreach (const QString &name, propertyNames) { + if (const Interpreter::ObjectValue *objectValue = value->asObjectValue()) { + value = objectValue->property(name, context); + if (!value) + return 0; + } else { + return 0; + } + } + return value; +} + +bool isLiteral(AST::Node *ast) +{ + if (AST::cast<AST::StringLiteral *>(ast)) + return true; + else if (AST::cast<AST::NumericLiteral *>(ast)) + return true; + else + return false; +} + +} // Anonymous + +// ----------------------- +// QmlJSAssistProposalItem +// ----------------------- +bool QmlJSAssistProposalItem::prematurelyApplies(const QChar &c) const +{ + if (data().canConvert<QString>()) // snippet + return false; + + return (text().endsWith(QLatin1String(": ")) && c == QLatin1Char(':')) + || (text().endsWith(QLatin1Char('.')) && c == QLatin1Char('.')); +} + +void QmlJSAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const +{ + const int currentPosition = editor->position(); + editor->setCursorPosition(basePosition); + editor->remove(currentPosition - basePosition); + + QString replaceable; + const QString &content = text(); + if (content.endsWith(QLatin1String(": "))) + replaceable = QLatin1String(": "); + else if (content.endsWith(QLatin1Char('.'))) + replaceable = QLatin1String("."); + int replacedLength = 0; + for (int i = 0; i < replaceable.length(); ++i) { + const QChar a = replaceable.at(i); + const QChar b = editor->characterAt(editor->position() + i); + if (a == b) + ++replacedLength; + else + break; + } + const int length = editor->position() - basePosition + replacedLength; + editor->replace(length, content); +} + +// ------------------------- +// FunctionHintProposalModel +// ------------------------- +class FunctionHintProposalModel : public TextEditor::IFunctionHintProposalModel +{ +public: + FunctionHintProposalModel(const QString &functionName, const QStringList &signature) + : m_functionName(functionName) + , m_signature(signature) + , m_minimumArgumentCount(signature.size()) + {} + + virtual void reset() {} + virtual int size() const { return 1; } + virtual QString text(int index) const; + virtual int activeArgument(const QString &prefix) const; + +private: + QString m_functionName; + QStringList m_signature; + int m_minimumArgumentCount; +}; + +QString FunctionHintProposalModel::text(int index) const +{ + Q_UNUSED(index) + + QString prettyMethod; + prettyMethod += QString::fromLatin1("function "); + prettyMethod += m_functionName; + prettyMethod += QLatin1Char('('); + for (int i = 0; i < m_minimumArgumentCount; ++i) { + if (i != 0) + prettyMethod += QLatin1String(", "); + + QString arg = m_signature.at(i); + if (arg.isEmpty()) { + arg = QLatin1String("arg"); + arg += QString::number(i + 1); + } + + prettyMethod += arg; + } + prettyMethod += QLatin1Char(')'); + return prettyMethod; +} + +int FunctionHintProposalModel::activeArgument(const QString &prefix) const +{ + int argnr = 0; + int parcount = 0; + Scanner tokenize; + const QList<Token> tokens = tokenize(prefix); + for (int i = 0; i < tokens.count(); ++i) { + const Token &tk = tokens.at(i); + if (tk.is(Token::LeftParenthesis)) + ++parcount; + else if (tk.is(Token::RightParenthesis)) + --parcount; + else if (! parcount && tk.is(Token::Colon)) + ++argnr; + } + + if (parcount < 0) + return -1; + + return argnr; +} + +// ----------------------------- +// QmlJSCompletionAssistProvider +// ----------------------------- +bool QmlJSCompletionAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(Constants::C_QMLJSEDITOR_ID); +} + +int QmlJSCompletionAssistProvider::activationCharSequenceLength() const +{ + return 1; +} + +bool QmlJSCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + return isActivationChar(sequence.at(0)); +} + +bool QmlJSCompletionAssistProvider::isContinuationChar(const QChar &c) const +{ + return isIdentifierChar(c, false); +} + +IAssistProcessor *QmlJSCompletionAssistProvider::createProcessor() const +{ + return new QmlJSCompletionAssistProcessor; +} + +// ------------------------------ +// QmlJSCompletionAssistProcessor +// ------------------------------ +QmlJSCompletionAssistProcessor::QmlJSCompletionAssistProcessor() + : m_startPosition(0) + , m_snippetCollector(Constants::QML_SNIPPETS_GROUP_ID, iconForColor(Qt::red), SnippetOrder) +{} + +QmlJSCompletionAssistProcessor::~QmlJSCompletionAssistProcessor() +{} + +IAssistProposal *QmlJSCompletionAssistProcessor::createContentProposal() const +{ + IGenericProposalModel *model = new QmlJSAssistProposalModel(m_completions); + IAssistProposal *proposal = new GenericProposal(m_startPosition, model); + return proposal; +} + +IAssistProposal *QmlJSCompletionAssistProcessor::createHintProposal(const QString &functionName, + const QStringList &signature) const +{ + IFunctionHintProposalModel *model = new FunctionHintProposalModel(functionName, signature); + IAssistProposal *proposal = new FunctionHintProposal(m_startPosition, model); + return proposal; +} + +IAssistProposal *QmlJSCompletionAssistProcessor::perform(const IAssistInterface *assistInterface) +{ + m_interface.reset(static_cast<const QmlJSCompletionAssistInterface *>(assistInterface)); + + if (assistInterface->reason() == IdleEditor && !acceptsIdleEditor()) + return 0; + + const QString &fileName = m_interface->file()->fileName(); + + m_startPosition = assistInterface->position(); + while (isIdentifierChar(m_interface->document()->characterAt(m_startPosition - 1), false, false)) + --m_startPosition; + + m_completions.clear(); + + const QmlJSCompletionAssistInterface *qmlInterface = + static_cast<const QmlJSCompletionAssistInterface *>(assistInterface); + const SemanticInfo &semanticInfo = qmlInterface->semanticInfo(); + if (!semanticInfo.isValid()) + return 0; + + const Document::Ptr document = semanticInfo.document; + const QFileInfo currentFileInfo(fileName); + + bool isQmlFile = false; + if (currentFileInfo.suffix() == QLatin1String("qml")) + isQmlFile = true; + + const QList<AST::Node *> path = semanticInfo.astPath(m_interface->position()); + LookupContext::Ptr lookupContext = semanticInfo.lookupContext(path); + const Interpreter::Context *context = lookupContext->context(); + + // Search for the operator that triggered the completion. + QChar completionOperator; + if (m_startPosition > 0) + completionOperator = m_interface->document()->characterAt(m_startPosition - 1); + + QTextCursor startPositionCursor(qmlInterface->document()); + startPositionCursor.setPosition(m_startPosition); + CompletionContextFinder contextFinder(startPositionCursor); + + const Interpreter::ObjectValue *qmlScopeType = 0; + if (contextFinder.isInQmlContext()) { + // ### this should use semanticInfo.declaringMember instead, but that may also return functions + for (int i = path.size() - 1; i >= 0; --i) { + AST::Node *node = path[i]; + if (AST::cast<AST::UiObjectDefinition *>(node) || AST::cast<AST::UiObjectBinding *>(node)) { + qmlScopeType = document->bind()->findQmlObject(node); + if (qmlScopeType) + break; + } + } + // fallback to getting the base type object + if (!qmlScopeType) + qmlScopeType = context->lookupType(document.data(), contextFinder.qmlObjectTypeName()); + } + + if (contextFinder.isInStringLiteral()) { + // get the text of the literal up to the cursor position + //QTextCursor tc = textWidget->textCursor(); + QTextCursor tc(qmlInterface->document()); + tc.setPosition(qmlInterface->position()); + QmlExpressionUnderCursor expressionUnderCursor; + expressionUnderCursor(tc); + QString literalText = expressionUnderCursor.text(); + QTC_ASSERT(!literalText.isEmpty() && ( + literalText.at(0) == QLatin1Char('"') + || literalText.at(0) == QLatin1Char('\'')), return 0); + literalText = literalText.mid(1); + + if (contextFinder.isInImport()) { + QStringList patterns; + patterns << QLatin1String("*.qml") << QLatin1String("*.js"); + if (completeFileName(document->path(), literalText, patterns)) + return createContentProposal(); + return 0; + } + + const Interpreter::Value *value = + getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context); + if (!value) { + // do nothing + } else if (value->asUrlValue()) { + if (completeUrl(document->path(), literalText)) + return createContentProposal(); + } + + // ### enum completion? + + // completion gets triggered for / in string literals, if we don't + // return here, this will mean the snippet completion pops up for + // each / in a string literal that is not triggering file completion + return 0; + } else if (completionOperator.isSpace() + || completionOperator.isNull() + || isDelimiterChar(completionOperator) + || (completionOperator == QLatin1Char('(') + && m_startPosition != m_interface->position())) { + + bool doGlobalCompletion = true; + bool doQmlKeywordCompletion = true; + bool doJsKeywordCompletion = true; + bool doQmlTypeCompletion = false; + + if (contextFinder.isInLhsOfBinding() && qmlScopeType) { + doGlobalCompletion = false; + doJsKeywordCompletion = false; + doQmlTypeCompletion = true; + + EnumerateProperties enumerateProperties(context); + enumerateProperties.setGlobalCompletion(true); + enumerateProperties.setEnumerateGeneratedSlots(true); + + // id: is special + BasicProposalItem *idProposalItem = new QmlJSAssistProposalItem; + idProposalItem->setText(QLatin1String("id: ")); + idProposalItem->setIcon(m_interface->symbolIcon()); + idProposalItem->setOrder(PropertyOrder); + m_completions.append(idProposalItem); + + addCompletionsPropertyLhs(enumerateProperties(qmlScopeType), + m_interface->symbolIcon(), + PropertyOrder, + contextFinder.isAfterOnInLhsOfBinding()); + + if (ScopeBuilder::isPropertyChangesObject(context, qmlScopeType) + && context->scopeChain().qmlScopeObjects.size() == 2) { + addCompletions(enumerateProperties(context->scopeChain().qmlScopeObjects.first()), + m_interface->symbolIcon(), + SymbolOrder); + } + } + + if (contextFinder.isInRhsOfBinding() && qmlScopeType) { + doQmlKeywordCompletion = false; + + // complete enum values for enum properties + const Interpreter::Value *value = + getPropertyValue(qmlScopeType, contextFinder.bindingPropertyName(), context); + if (const Interpreter::QmlEnumValue *enumValue = + dynamic_cast<const Interpreter::QmlEnumValue *>(value)) { + foreach (const QString &key, enumValue->keys()) + addCompletion(key, m_interface->symbolIcon(), + EnumValueOrder, QString("\"%1\"").arg(key)); + } + } + + if (!contextFinder.isInImport() && !contextFinder.isInQmlContext()) + doQmlTypeCompletion = true; + + if (doQmlTypeCompletion) { + if (const Interpreter::ObjectValue *qmlTypes = context->scopeChain().qmlTypes) { + EnumerateProperties enumerateProperties(context); + addCompletions(enumerateProperties(qmlTypes), m_interface->symbolIcon(), TypeOrder); + } + } + + if (doGlobalCompletion) { + // It's a global completion. + EnumerateProperties enumerateProperties(context); + enumerateProperties.setGlobalCompletion(true); + addCompletions(enumerateProperties(), m_interface->symbolIcon(), SymbolOrder); + } + + if (doJsKeywordCompletion) { + // add js keywords + addCompletions(Scanner::keywords(), m_interface->keywordIcon(), KeywordOrder); + } + + // add qml extra words + if (doQmlKeywordCompletion && isQmlFile) { + static QStringList qmlWords; + static QStringList qmlWordsAlsoInJs; + + if (qmlWords.isEmpty()) { + qmlWords << QLatin1String("property") + //<< QLatin1String("readonly") + << QLatin1String("signal") + << QLatin1String("import"); + } + if (qmlWordsAlsoInJs.isEmpty()) + qmlWordsAlsoInJs << QLatin1String("default") << QLatin1String("function"); + + addCompletions(qmlWords, m_interface->keywordIcon(), KeywordOrder); + if (!doJsKeywordCompletion) + addCompletions(qmlWordsAlsoInJs, m_interface->keywordIcon(), KeywordOrder); + } + } + + else if (completionOperator == QLatin1Char('.') || completionOperator == QLatin1Char('(')) { + // Look at the expression under cursor. + //QTextCursor tc = textWidget->textCursor(); + QTextCursor tc(qmlInterface->document()); + tc.setPosition(m_startPosition - 1); + + QmlExpressionUnderCursor expressionUnderCursor; + QmlJS::AST::ExpressionNode *expression = expressionUnderCursor(tc); + + if (expression != 0 && ! isLiteral(expression)) { + // Evaluate the expression under cursor. + Interpreter::Engine *interp = lookupContext->engine(); + const Interpreter::Value *value = + interp->convertToObject(lookupContext->evaluate(expression)); + //qDebug() << "type:" << interp.typeId(value); + + if (value && completionOperator == QLatin1Char('.')) { // member completion + EnumerateProperties enumerateProperties(context); + if (contextFinder.isInLhsOfBinding() && qmlScopeType) { + enumerateProperties.setEnumerateGeneratedSlots(true); + addCompletionsPropertyLhs(enumerateProperties(value), + m_interface->symbolIcon(), + PropertyOrder, + contextFinder.isAfterOnInLhsOfBinding()); + } else + addCompletions(enumerateProperties(value), m_interface->symbolIcon(), SymbolOrder); + } else if (value + && completionOperator == QLatin1Char('(') + && m_startPosition == m_interface->position()) { + // function completion + if (const Interpreter::FunctionValue *f = value->asFunctionValue()) { + QString functionName = expressionUnderCursor.text(); + int indexOfDot = functionName.lastIndexOf(QLatin1Char('.')); + if (indexOfDot != -1) + functionName = functionName.mid(indexOfDot + 1); + + QStringList signature; + for (int i = 0; i < f->argumentCount(); ++i) + signature.append(f->argumentName(i)); + + return createHintProposal(functionName.trimmed(), signature); + } + } + } + + if (! m_completions.isEmpty()) + return createContentProposal(); + return 0; + } + + if (isQmlFile + && (completionOperator.isNull() + || completionOperator.isSpace() + || isDelimiterChar(completionOperator))) { + m_completions.append(m_snippetCollector.collect()); + } + + if (! m_completions.isEmpty()) + return createContentProposal(); + return 0; +} + +bool QmlJSCompletionAssistProcessor::acceptsIdleEditor() const +{ + const int cursorPos = m_interface->position(); + + bool maybeAccept = false; + const QChar &charBeforeCursor = m_interface->document()->characterAt(cursorPos - 1); + if (isActivationChar(charBeforeCursor)) { + maybeAccept = true; + } else { + const QChar &charUnderCursor = m_interface->document()->characterAt(cursorPos); + if (isIdentifierChar(charBeforeCursor) + && ((charUnderCursor.isSpace() + || charUnderCursor.isNull() + || isDelimiterChar(charUnderCursor)) + || isIdentifierChar(charUnderCursor))) { + + int startPos = cursorPos - 1; + for (; startPos != -1; --startPos) { + if (!isIdentifierChar(m_interface->document()->characterAt(startPos))) + break; + } + ++startPos; + + const QString &word = m_interface->textAt(startPos, cursorPos - startPos); + if (word.length() > 2 && isIdentifierChar(word.at(0), true)) { + for (int i = 1; i < word.length(); ++i) { + if (!isIdentifierChar(word.at(i))) + return false; + } + maybeAccept = true; + } + } + } + + if (maybeAccept) { + QTextCursor tc(m_interface->document()); + tc.setPosition(m_interface->position()); + const QTextBlock &block = tc.block(); + const QString &blockText = block.text(); + const int blockState = qMax(0, block.previous().userState()) & 0xff; + + Scanner scanner; + const QList<Token> tokens = scanner(blockText, blockState); + const int column = block.position() - m_interface->position(); + foreach (const Token &tk, tokens) { + if (column >= tk.begin() && column <= tk.end()) { + if (charBeforeCursor == QLatin1Char('/') && tk.is(Token::String)) + return true; // path completion inside string literals + if (tk.is(Token::Comment) || tk.is(Token::String)) + return false; + break; + } + } + if (charBeforeCursor != QLatin1Char('/')) + return true; + } + + return false; +} + +bool QmlJSCompletionAssistProcessor::completeFileName(const QString &relativeBasePath, + const QString &fileName, + const QStringList &patterns) +{ + const QFileInfo fileInfo(fileName); + QString directoryPrefix; + if (fileInfo.isRelative()) { + directoryPrefix = relativeBasePath; + directoryPrefix += QDir::separator(); + directoryPrefix += fileInfo.path(); + } else { + directoryPrefix = fileInfo.path(); + } + if (!QFileInfo(directoryPrefix).exists()) + return false; + + QDirIterator dirIterator(directoryPrefix, + patterns, + QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + while (dirIterator.hasNext()) { + dirIterator.next(); + const QString fileName = dirIterator.fileName(); + + BasicProposalItem *item = new QmlJSAssistProposalItem; + item->setText(fileName); + item->setIcon(m_interface->fileNameIcon()); + m_completions.append(item); + } + + return !m_completions.isEmpty(); +} + +bool QmlJSCompletionAssistProcessor::completeUrl(const QString &relativeBasePath, const QString &urlString) +{ + const QUrl url(urlString); + QString fileName = url.toLocalFile(); + if (fileName.isEmpty()) + return false; + + return completeFileName(relativeBasePath, fileName); +} + +void QmlJSCompletionAssistProcessor::addCompletionsPropertyLhs(const QHash<QString, + const QmlJS::Interpreter::Value *> &newCompletions, + const QIcon &icon, + int order, + bool afterOn) +{ + QHashIterator<QString, const Interpreter::Value *> it(newCompletions); + while (it.hasNext()) { + it.next(); + + QString itemText = it.key(); + QLatin1String postfix(": "); + if (afterOn) + postfix = QLatin1String(" {"); + if (const Interpreter::QmlObjectValue *qmlValue = + dynamic_cast<const Interpreter::QmlObjectValue *>(it.value())) { + // to distinguish "anchors." from "gradient:" we check if the right hand side + // type is instantiatable or is the prototype of an instantiatable object + if (qmlValue->hasChildInPackage()) + itemText.append(postfix); + else + itemText.append(QLatin1Char('.')); + } else { + itemText.append(postfix); + } + + addCompletion(itemText, icon, order); + } +} + +void QmlJSCompletionAssistProcessor::addCompletion(const QString &text, + const QIcon &icon, + int order, + const QVariant &data) +{ + if (text.isEmpty()) + return; + + BasicProposalItem *item = new QmlJSAssistProposalItem; + item->setText(text); + item->setIcon(icon); + item->setOrder(order); + item->setData(data); + m_completions.append(item); +} + +void QmlJSCompletionAssistProcessor::addCompletions(const QHash<QString, + const QmlJS::Interpreter::Value *> &newCompletions, + const QIcon &icon, + int order) +{ + QHashIterator<QString, const Interpreter::Value *> it(newCompletions); + while (it.hasNext()) { + it.next(); + addCompletion(it.key(), icon, order); + } +} + +void QmlJSCompletionAssistProcessor::addCompletions(const QStringList &newCompletions, + const QIcon &icon, + int order) +{ + foreach (const QString &text, newCompletions) + addCompletion(text, icon, order); +} + +// ------------------------------ +// QmlJSCompletionAssistInterface +// ------------------------------ +QmlJSCompletionAssistInterface::QmlJSCompletionAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + TextEditor::AssistReason reason, + const SemanticInfo &info) + : DefaultAssistInterface(document, position, file, reason) + , m_semanticInfo(info) + , m_darkBlueIcon(iconForColor(Qt::darkBlue)) + , m_darkYellowIcon(iconForColor(Qt::darkYellow)) + , m_darkCyanIcon(iconForColor(Qt::darkCyan)) +{} + +const SemanticInfo &QmlJSCompletionAssistInterface::semanticInfo() const +{ + return m_semanticInfo; +} + +namespace { + +struct QmlJSLessThan +{ + bool operator() (const BasicProposalItem *a, const BasicProposalItem *b) + { + if (a->order() != b->order()) + return a->order() > b->order(); + else if (a->text().isEmpty()) + return true; + else if (b->text().isEmpty()) + return false; + else if (a->data().isValid() != b->data().isValid()) + return a->data().isValid(); + else if (a->text().at(0).isUpper() && b->text().at(0).isLower()) + return false; + else if (a->text().at(0).isLower() && b->text().at(0).isUpper()) + return true; + return a->text() < b->text(); + } +}; + +} // Anonymous + +// ------------------------- +// QmlJSAssistProposalModel +// ------------------------- +void QmlJSAssistProposalModel::sort() +{ + qSort(currentItems().first, currentItems().second, QmlJSLessThan()); +} diff --git a/src/plugins/qmljseditor/qmljscompletionassist.h b/src/plugins/qmljseditor/qmljscompletionassist.h new file mode 100644 index 0000000000..3826f0507c --- /dev/null +++ b/src/plugins/qmljseditor/qmljscompletionassist.h @@ -0,0 +1,158 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLJSCOMPLETIONASSIST_H +#define QMLJSCOMPLETIONASSIST_H + +#include "qmljseditor.h" + +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/completionassistprovider.h> +#include <texteditor/codeassist/iassistprocessor.h> +#include <texteditor/snippets/snippetassistcollector.h> +#include <texteditor/codeassist/defaultassistinterface.h> + +#include <QtCore/QStringList> +#include <QtCore/QScopedPointer> +#include <QtCore/QVariant> +#include <QtGui/QIcon> + +namespace QmlJS { +namespace Interpreter { +class Value; +} +} + +namespace QmlJSEditor { +namespace Internal { + +class QmlJSCompletionAssistInterface; + +class QmlJSAssistProposalItem : public TextEditor::BasicProposalItem +{ +public: + virtual bool prematurelyApplies(const QChar &c) const; + virtual void applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const; +}; + + +class QmlJSAssistProposalModel : public TextEditor::BasicProposalItemListModel +{ +public: + QmlJSAssistProposalModel(const QList<TextEditor::BasicProposalItem *> &items) + : TextEditor::BasicProposalItemListModel(items) + {} + + virtual void sort(); +}; + + +class QmlJSCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + virtual bool supportsEditor(const QString &editorId) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; + + virtual int activationCharSequenceLength() const; + virtual bool isActivationCharSequence(const QString &sequence) const; + virtual bool isContinuationChar(const QChar &c) const; +}; + + +class QmlJSCompletionAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + QmlJSCompletionAssistProcessor(); + virtual ~QmlJSCompletionAssistProcessor(); + + virtual TextEditor::IAssistProposal *perform(const TextEditor::IAssistInterface *interface); + +private: + TextEditor::IAssistProposal *createContentProposal() const; + TextEditor::IAssistProposal *createHintProposal(const QString &functionName, + const QStringList &signature) const; + + bool acceptsIdleEditor() const; + + bool completeUrl(const QString &relativeBasePath, const QString &urlString); + bool completeFileName(const QString &relativeBasePath, + const QString &fileName, + const QStringList &patterns = QStringList()); + + void addCompletion(const QString &text, + const QIcon &icon, + int order, + const QVariant &data = QVariant()); + void addCompletions(const QHash<QString, const QmlJS::Interpreter::Value *> &newCompletions, + const QIcon &icon, + int order); + void addCompletions(const QStringList &newCompletions, const QIcon &icon, int order); + void addCompletionsPropertyLhs(const QHash<QString, + const QmlJS::Interpreter::Value *> &newCompletions, + const QIcon &icon, + int order, + bool afterOn); + + int m_startPosition; + QScopedPointer<const QmlJSCompletionAssistInterface> m_interface; + QList<TextEditor::BasicProposalItem *> m_completions; + TextEditor::SnippetAssistCollector m_snippetCollector; + const TextEditor::IAssistProvider *m_provider; +}; + + +class QmlJSCompletionAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + QmlJSCompletionAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + TextEditor::AssistReason reason, + const SemanticInfo &info); + const SemanticInfo &semanticInfo() const; + const QIcon &fileNameIcon() const { return m_darkBlueIcon; } + const QIcon &keywordIcon() const { return m_darkYellowIcon; } + const QIcon &symbolIcon() const { return m_darkCyanIcon; } + +private: + SemanticInfo m_semanticInfo; + QIcon m_darkBlueIcon; + QIcon m_darkYellowIcon; + QIcon m_darkCyanIcon; +}; + +} // Internal +} // QmlJSEditor + +#endif // QMLJSCOMPLETIONASSIST_H diff --git a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp index 65824f2329..a85e4b0e0f 100644 --- a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp +++ b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.cpp @@ -32,6 +32,7 @@ #include "qmljscomponentfromobjectdef.h" #include "qmljscomponentnamedialog.h" +#include "qmljsquickfixassist.h" #include <coreplugin/ifile.h> @@ -93,8 +94,9 @@ class Operation: public QmlJSQuickFixOperation QString m_idName, m_componentName; public: - Operation(const QmlJSQuickFixState &state, UiObjectDefinition *objDef) - : QmlJSQuickFixOperation(state, 0) + Operation(const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface, + UiObjectDefinition *objDef) + : QmlJSQuickFixOperation(interface, 0) , m_objDef(objDef) { Q_ASSERT(m_objDef != 0); @@ -117,7 +119,7 @@ public: QString componentName = m_componentName; QString path = QFileInfo(fileName()).path(); if (componentName.isEmpty()) { - ComponentNameDialog::go(&componentName, &path, state().editor()); + ComponentNameDialog::go(&componentName, &path, assistInterface()->widget()); } if (componentName.isEmpty() || path.isEmpty()) @@ -157,19 +159,21 @@ public: } // end of anonymous namespace -QList<QmlJSQuickFixOperation::Ptr> ComponentFromObjectDef::match(const QmlJSQuickFixState &state) + +QList<QmlJSQuickFixOperation::Ptr> ComponentFromObjectDef::match( + const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface) { - const int pos = state.currentFile().cursor().position(); + const int pos = interface->currentFile().cursor().position(); - QList<Node *> path = state.semanticInfo().astPath(pos); + QList<Node *> path = interface->semanticInfo().astPath(pos); for (int i = path.size() - 1; i >= 0; --i) { Node *node = path.at(i); if (UiObjectDefinition *objDef = cast<UiObjectDefinition *>(node)) { - if (!state.currentFile().isCursorOn(objDef->qualifiedTypeNameId)) + if (!interface->currentFile().isCursorOn(objDef->qualifiedTypeNameId)) return noResult(); // check that the node is not the root node if (i > 0 && !cast<UiProgram*>(path.at(i - 1))) { - return singleResult(new Operation(state, objDef)); + return singleResult(new Operation(interface, objDef)); } } } diff --git a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h index ccab5b05e2..0467621246 100644 --- a/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h +++ b/src/plugins/qmljseditor/qmljscomponentfromobjectdef.h @@ -41,7 +41,8 @@ namespace Internal { class ComponentFromObjectDef: public QmlJSQuickFixFactory { public: - virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state); + virtual QList<QmlJSQuickFixOperation::Ptr> match( + const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface); }; } // namespace Internal diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 41f9646955..017ae2b80c 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -35,12 +35,13 @@ #include "qmljseditorconstants.h" #include "qmljshighlighter.h" #include "qmljseditorplugin.h" -#include "qmljsquickfix.h" #include "qmloutlinemodel.h" #include "qmljsfindreferences.h" #include "qmljssemantichighlighter.h" #include "qmljsindenter.h" #include "qmljsautocompleter.h" +#include "qmljscompletionassist.h" +#include "qmljsquickfixassist.h" #include <qmljs/qmljsbind.h> #include <qmljs/qmljsevaluate.h> @@ -70,6 +71,8 @@ #include <texteditor/syntaxhighlighter.h> #include <texteditor/refactoroverlay.h> #include <texteditor/tooltip/tooltip.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> #include <qmldesigner/qmldesignerconstants.h> #include <projectexplorer/projectexplorerconstants.h> #include <utils/changeset.h> @@ -78,6 +81,7 @@ #include <QtCore/QFileInfo> #include <QtCore/QSignalMapper> #include <QtCore/QTimer> +#include <QtCore/QScopedPointer> #include <QtGui/QMenu> #include <QtGui/QComboBox> @@ -1347,20 +1351,29 @@ void QmlJSTextEditorWidget::contextMenuEvent(QContextMenuEvent *e) connect(a, SIGNAL(triggered()), this, SLOT(renameIdUnderCursor())); } - // Add other refactoring actions: - QmlJSQuickFixCollector *quickFixCollector = QmlJSEditorPlugin::instance()->quickFixCollector(); QSignalMapper mapper; connect(&mapper, SIGNAL(mapped(int)), this, SLOT(performQuickFix(int))); - if (! isOutdated()) { - if (quickFixCollector->startCompletion(editor()) != -1) { - m_quickFixes = quickFixCollector->quickFixes(); - - for (int index = 0; index < m_quickFixes.size(); ++index) { - TextEditor::QuickFixOperation::Ptr op = m_quickFixes.at(index); - QAction *action = refactoringMenu->addAction(op->description()); - mapper.setMapping(action, index); - connect(action, SIGNAL(triggered()), &mapper, SLOT(map())); + TextEditor::IAssistInterface *interface = + createAssistInterface(TextEditor::QuickFix, TextEditor::ExplicitlyInvoked); + if (interface) { + QScopedPointer<TextEditor::IAssistProcessor> processor( + QmlJSEditorPlugin::instance()->quickFixAssistProvider()->createProcessor()); + QScopedPointer<TextEditor::IAssistProposal> proposal(processor->perform(interface)); + if (!proposal.isNull()) { + TextEditor::BasicProposalItemListModel *model = + static_cast<TextEditor::BasicProposalItemListModel *>(proposal->model()); + for (int index = 0; index < model->size(); ++index) { + TextEditor::BasicProposalItem *item = + static_cast<TextEditor::BasicProposalItem *>(model->proposalItem(index)); + TextEditor::QuickFixOperation::Ptr op = + item->data().value<TextEditor::QuickFixOperation::Ptr>(); + m_quickFixes.append(op); + QAction *action = refactoringMenu->addAction(op->description()); + mapper.setMapping(action, index); + connect(action, SIGNAL(triggered()), &mapper, SLOT(map())); + } + delete model; } } } @@ -1380,7 +1393,6 @@ void QmlJSTextEditorWidget::contextMenuEvent(QContextMenuEvent *e) menu->exec(e->globalPos()); menu->deleteLater(); - quickFixCollector->cleanup(); m_quickFixes.clear(); } @@ -1578,3 +1590,19 @@ SemanticHighlighterSource QmlJSTextEditorWidget::currentSource(bool force) source.force = force; return source; } + +TextEditor::IAssistInterface *QmlJSTextEditorWidget::createAssistInterface( + TextEditor::AssistKind assistKind, + TextEditor::AssistReason reason) const +{ + if (assistKind == TextEditor::Completion) { + return new QmlJSCompletionAssistInterface(document(), + position(), + editor()->file(), + reason, + m_semanticInfo); + } else if (assistKind == TextEditor::QuickFix) { + return new QmlJSQuickFixAssistInterface(const_cast<QmlJSTextEditorWidget *>(this), reason); + } + return 0; +} diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index 6eb3bc15db..dabe5bf295 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -160,6 +160,9 @@ public: static QVector<QString> highlighterFormatCategories(); + TextEditor::IAssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, + TextEditor::AssistReason reason) const; + public slots: void forceSemanticRehighlight(); void followSymbolUnderCursor(); diff --git a/src/plugins/qmljseditor/qmljseditor.pro b/src/plugins/qmljseditor/qmljseditor.pro index 6d08513810..b52dcfc679 100644 --- a/src/plugins/qmljseditor/qmljseditor.pro +++ b/src/plugins/qmljseditor/qmljseditor.pro @@ -8,7 +8,6 @@ DEFINES += \ QT_CREATOR HEADERS += \ - qmljscodecompletion.h \ qmljseditor.h \ qmljseditor_global.h \ qmljseditoractionhandler.h \ @@ -20,7 +19,6 @@ HEADERS += \ qmljshighlighter.h \ qmljshoverhandler.h \ qmljspreviewrunner.h \ - qmljsquickfix.h \ qmljscomponentfromobjectdef.h \ qmljsoutline.h \ qmloutlinemodel.h \ @@ -35,10 +33,13 @@ HEADERS += \ qmljsindenter.h \ qmljsautocompleter.h \ jsfilewizard.h \ - qmljssnippetprovider.h + qmljssnippetprovider.h \ + qmljsreuse.h \ + qmljsquickfixassist.h \ + qmljscompletionassist.h \ + qmljsquickfix.h SOURCES += \ - qmljscodecompletion.cpp \ qmljseditor.cpp \ qmljseditoractionhandler.cpp \ qmljseditorfactory.cpp \ @@ -48,7 +49,6 @@ SOURCES += \ qmljshighlighter.cpp \ qmljshoverhandler.cpp \ qmljspreviewrunner.cpp \ - qmljsquickfix.cpp \ qmljscomponentfromobjectdef.cpp \ qmljsoutline.cpp \ qmloutlinemodel.cpp \ @@ -64,7 +64,11 @@ SOURCES += \ qmljsindenter.cpp \ qmljsautocompleter.cpp \ jsfilewizard.cpp \ - qmljssnippetprovider.cpp + qmljssnippetprovider.cpp \ + qmljsreuse.cpp \ + qmljsquickfixassist.cpp \ + qmljscompletionassist.cpp \ + qmljsquickfix.cpp RESOURCES += qmljseditor.qrc OTHER_FILES += QmlJSEditor.mimetypes.xml diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index 3aebceee46..e18c8e6e4e 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -35,17 +35,17 @@ #include "qmljseditor.h" #include "qmljseditorconstants.h" #include "qmljseditorfactory.h" -#include "qmljscodecompletion.h" #include "qmljshoverhandler.h" #include "qmlfilewizard.h" #include "jsfilewizard.h" #include "qmljsoutline.h" #include "qmljspreviewrunner.h" -#include "qmljsquickfix.h" #include "qmljssnippetprovider.h" #include "qmltaskmanager.h" #include "quicktoolbar.h" #include "quicktoolbarsettingspage.h" +#include "qmljscompletionassist.h" +#include "qmljsquickfixassist.h" #include <qmljs/qmljsicons.h> #include <qmljs/qmljsmodelmanagerinterface.h> @@ -69,7 +69,6 @@ #include <texteditor/texteditorsettings.h> #include <texteditor/textfilewizard.h> #include <texteditor/texteditoractionhandler.h> -#include <texteditor/completionsupport.h> #include <utils/qtcassert.h> #include <QtCore/QtPlugin> @@ -89,21 +88,18 @@ enum { QUICKFIX_INTERVAL = 20 }; +void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); + QmlJSEditorPlugin *QmlJSEditorPlugin::m_instance = 0; QmlJSEditorPlugin::QmlJSEditorPlugin() : m_modelManager(0), m_wizard(0), m_editor(0), - m_actionHandler(0) + m_actionHandler(0), + m_quickFixAssistProvider(0) { m_instance = this; - - m_quickFixCollector = 0; - m_quickFixTimer = new QTimer(this); - m_quickFixTimer->setInterval(20); - m_quickFixTimer->setSingleShot(true); - connect(m_quickFixTimer, SIGNAL(timeout()), this, SLOT(quickFixNow())); } QmlJSEditorPlugin::~QmlJSEditorPlugin() @@ -211,25 +207,18 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e cmd = am->command(TextEditor::Constants::UN_COMMENT_SELECTION); contextMenu->addAction(cmd); - CodeCompletion *completion = new CodeCompletion(m_modelManager); - addAutoReleasedObject(completion); + m_quickFixAssistProvider = new QmlJSQuickFixAssistProvider; + addAutoReleasedObject(m_quickFixAssistProvider); + addAutoReleasedObject(new QmlJSCompletionAssistProvider); addAutoReleasedObject(new HoverHandler); - // Set completion settings and keep them up to date - TextEditor::TextEditorSettings *textEditorSettings = TextEditor::TextEditorSettings::instance(); - completion->setCompletionSettings(textEditorSettings->completionSettings()); - connect(textEditorSettings, SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), - completion, SLOT(setCompletionSettings(TextEditor::CompletionSettings))); - error_message->clear(); Core::FileIconProvider *iconProvider = Core::FileIconProvider::instance(); iconProvider->registerIconOverlayForSuffix(QIcon(QLatin1String(":/qmljseditor/images/qmlfile.png")), "qml"); - m_quickFixCollector = new QmlJSQuickFixCollector; - addAutoReleasedObject(m_quickFixCollector); - QmlJSQuickFixCollector::registerQuickFixes(this); + registerQuickFixes(this); addAutoReleasedObject(new QmlJSOutlineWidgetFactory); @@ -313,35 +302,9 @@ Core::Command *QmlJSEditorPlugin::addToolAction(QAction *a, Core::ActionManager return command; } -QmlJSQuickFixCollector *QmlJSEditorPlugin::quickFixCollector() const -{ return m_quickFixCollector; } - -void QmlJSEditorPlugin::quickFix(TextEditor::ITextEditor *editable) +QmlJSQuickFixAssistProvider *QmlJSEditorPlugin::quickFixAssistProvider() const { - m_currentTextEditable = editable; - quickFixNow(); -} - -void QmlJSEditorPlugin::quickFixNow() -{ - if (! m_currentTextEditable) - return; - - Core::EditorManager *em = Core::EditorManager::instance(); - QmlJSTextEditorWidget *currentEditor = qobject_cast<QmlJSTextEditorWidget*>(em->currentEditor()->widget()); - - if (QmlJSTextEditorWidget *editor = qobject_cast<QmlJSTextEditorWidget*>(m_currentTextEditable->widget())) { - if (currentEditor == editor) { - if (editor->isOutdated()) { - // qDebug() << "TODO: outdated document" << editor->editorRevision() << editor->semanticInfo().revision(); - // ### FIXME: m_quickFixTimer->start(QUICKFIX_INTERVAL); - m_quickFixTimer->stop(); - } else { - TextEditor::CompletionSupport::instance() - ->complete(m_currentTextEditable, TextEditor::QuickFixCompletion, true); - } - } - } + return m_quickFixAssistProvider; } void QmlJSEditorPlugin::currentEditorChanged(Core::IEditor *editor) diff --git a/src/plugins/qmljseditor/qmljseditorplugin.h b/src/plugins/qmljseditor/qmljseditorplugin.h index 5451ab3155..18f43d2333 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.h +++ b/src/plugins/qmljseditor/qmljseditorplugin.h @@ -68,7 +68,7 @@ namespace Internal { class QmlJSEditorFactory; class QmlJSPreviewRunner; -class QmlJSQuickFixCollector; +class QmlJSQuickFixAssistProvider; class QmlTaskManager; class QmlJSEditorPlugin : public ExtensionSystem::IPlugin @@ -87,7 +87,7 @@ public: static QmlJSEditorPlugin *instance() { return m_instance; } - QmlJSQuickFixCollector *quickFixCollector() const; + QmlJSQuickFixAssistProvider *quickFixAssistProvider() const; void initializeEditor(QmlJSEditor::QmlJSTextEditorWidget *editor); @@ -97,8 +97,6 @@ public Q_SLOTS: void showContextPane(); private Q_SLOTS: - void quickFix(TextEditor::ITextEditor *editable); - void quickFixNow(); void currentEditorChanged(Core::IEditor *editor); private: @@ -115,9 +113,8 @@ private: QmlJSEditorFactory *m_editor; TextEditor::TextEditorActionHandler *m_actionHandler; - QmlJSQuickFixCollector *m_quickFixCollector; + QmlJSQuickFixAssistProvider *m_quickFixAssistProvider; - QTimer *m_quickFixTimer; QPointer<TextEditor::ITextEditor> m_currentTextEditable; QmlTaskManager *m_qmlTaskManager; }; diff --git a/src/plugins/qmljseditor/qmljshoverhandler.h b/src/plugins/qmljseditor/qmljshoverhandler.h index 1146ae3cb4..b0a411fa55 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.h +++ b/src/plugins/qmljseditor/qmljshoverhandler.h @@ -56,8 +56,6 @@ class QmlJSTextEditorWidget; namespace Internal { -class SemanticInfo; - class HoverHandler : public TextEditor::BaseHoverHandler { Q_OBJECT diff --git a/src/plugins/qmljseditor/qmljsquickfix.cpp b/src/plugins/qmljseditor/qmljsquickfix.cpp index 3e8115d2f1..f00a0eddd1 100644 --- a/src/plugins/qmljseditor/qmljsquickfix.cpp +++ b/src/plugins/qmljseditor/qmljsquickfix.cpp @@ -34,6 +34,7 @@ #include "qmljscomponentfromobjectdef.h" #include "qmljseditor.h" #include "qmljs/parser/qmljsast_p.h" +#include "qmljsquickfixassist.h" #include <extensionsystem/iplugin.h> #include <extensionsystem/pluginmanager.h> @@ -50,34 +51,11 @@ using namespace QmlJSTools; using namespace TextEditor; using TextEditor::RefactoringChanges; -QmlJSQuickFixState::QmlJSQuickFixState(TextEditor::BaseTextEditorWidget *editor) - : QuickFixState(editor) -{ -} - -SemanticInfo QmlJSQuickFixState::semanticInfo() const -{ - return _semanticInfo; -} - -Snapshot QmlJSQuickFixState::snapshot() const -{ - return _semanticInfo.snapshot; -} - -Document::Ptr QmlJSQuickFixState::document() const -{ - return _semanticInfo.document; -} - -const QmlJSRefactoringFile QmlJSQuickFixState::currentFile() const -{ - return QmlJSRefactoringFile(editor(), document()); -} - -QmlJSQuickFixOperation::QmlJSQuickFixOperation(const QmlJSQuickFixState &state, int priority) +QmlJSQuickFixOperation::QmlJSQuickFixOperation( + const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface, + int priority) : QuickFixOperation(priority) - , _state(state) + , m_interface(interface) { } @@ -88,20 +66,21 @@ QmlJSQuickFixOperation::~QmlJSQuickFixOperation() void QmlJSQuickFixOperation::perform() { QmlJSRefactoringChanges refactoring(ExtensionSystem::PluginManager::instance()->getObject<QmlJS::ModelManagerInterface>(), - _state.snapshot()); + //_state.snapshot()); + m_interface->semanticInfo().snapshot); QmlJSRefactoringFile current = refactoring.file(fileName()); performChanges(¤t, &refactoring); } -const QmlJSQuickFixState &QmlJSQuickFixOperation::state() const +const QmlJSQuickFixAssistInterface *QmlJSQuickFixOperation::assistInterface() const { - return _state; + return m_interface.data(); } QString QmlJSQuickFixOperation::fileName() const { - return state().document()->fileName(); + return m_interface->semanticInfo().document->fileName(); } QmlJSQuickFixFactory::QmlJSQuickFixFactory() @@ -112,12 +91,10 @@ QmlJSQuickFixFactory::~QmlJSQuickFixFactory() { } -QList<QuickFixOperation::Ptr> QmlJSQuickFixFactory::matchingOperations(QuickFixState *state) +QList<QuickFixOperation::Ptr> QmlJSQuickFixFactory::matchingOperations( + const QSharedPointer<const TextEditor::IAssistInterface> &interface) { - if (QmlJSQuickFixState *qmljsState = static_cast<QmlJSQuickFixState *>(state)) - return match(*qmljsState); - else - return QList<TextEditor::QuickFixOperation::Ptr>(); + return match(interface.staticCast<const QmlJSQuickFixAssistInterface>()); } QList<QmlJSQuickFixOperation::Ptr> QmlJSQuickFixFactory::noResult() @@ -131,49 +108,3 @@ QList<QmlJSQuickFixOperation::Ptr> QmlJSQuickFixFactory::singleResult(QmlJSQuick result.append(QmlJSQuickFixOperation::Ptr(operation)); return result; } - -QmlJSQuickFixCollector::QmlJSQuickFixCollector() -{ -} - -QmlJSQuickFixCollector::~QmlJSQuickFixCollector() -{ -} - -bool QmlJSQuickFixCollector::supportsEditor(TextEditor::ITextEditor *editable) const -{ - return qobject_cast<QmlJSTextEditorWidget *>(editable->widget()) != 0; -} - -bool QmlJSQuickFixCollector::supportsPolicy(TextEditor::CompletionPolicy policy) const -{ - return policy == TextEditor::QuickFixCompletion; -} - -TextEditor::QuickFixState *QmlJSQuickFixCollector::initializeCompletion(TextEditor::BaseTextEditorWidget *editor) -{ - if (QmlJSTextEditorWidget *qmljsEditor = qobject_cast<QmlJSTextEditorWidget *>(editor)) { - const SemanticInfo info = qmljsEditor->semanticInfo(); - - if (! info.isValid() || qmljsEditor->isOutdated()) { - // outdated - qWarning() << "TODO: outdated semantic info, force a reparse."; - return 0; - } - - QmlJSQuickFixState *state = new QmlJSQuickFixState(editor); - state->_semanticInfo = info; - return state; - } - - return 0; -} - -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; -} diff --git a/src/plugins/qmljseditor/qmljsquickfix.h b/src/plugins/qmljseditor/qmljsquickfix.h index ca5dd5ce51..7529814829 100644 --- a/src/plugins/qmljseditor/qmljsquickfix.h +++ b/src/plugins/qmljseditor/qmljsquickfix.h @@ -40,6 +40,8 @@ #include <qmljs/qmljsdocument.h> #include <qmljstools/qmljsrefactoringchanges.h> +#include <QtCore/QSharedPointer> + namespace ExtensionSystem { class IPlugin; } @@ -51,40 +53,12 @@ namespace QmlJS { namespace QmlJSEditor { namespace Internal { -class QmlJSQuickFixCollector; +class QmlJSQuickFixAssistInterface; } // namespace Internal -/*! - Specialized QuickFixState for QML/JavaScript quick-fixes. - - This specialized state for QML/JavaScript quick-fixes also holds the - QmlJSEditor::Internal::SemanticInfo for the document in the editor. - */ -class QmlJSQuickFixState: public TextEditor::QuickFixState -{ - friend class Internal::QmlJSQuickFixCollector; - -public: - /// Creates a new state for the given editor. - QmlJSQuickFixState(TextEditor::BaseTextEditorWidget *editor); - - SemanticInfo semanticInfo() const; - - /// \returns the snapshot holding the document of the editor. - QmlJS::Snapshot snapshot() const; - - /// \returns the document of the editor - QmlJS::Document::Ptr document() const; - - const QmlJSTools::QmlJSRefactoringFile currentFile() const; - -private: - SemanticInfo _semanticInfo; -}; /*! - A quick-fix operation for the QML/JavaScript editor, which works on a - QmlJSQuickFixState . + A quick-fix operation for the QML/JavaScript editor. */ class QmlJSQuickFixOperation: public TextEditor::QuickFixOperation { @@ -94,13 +68,12 @@ public: /*! Creates a new QmlJSQuickFixOperation. - This operation will copy the complete state, in order to be able to perform - its changes later on. - - \param state The state for which this operation was created. + \param interface The interface on which the operation is performed. \param priority The priority for this operation. */ - explicit QmlJSQuickFixOperation(const QmlJSQuickFixState &state, int priority = -1); + explicit QmlJSQuickFixOperation( + const QSharedPointer<const Internal::QmlJSQuickFixAssistInterface> &interface, + int priority = -1); virtual ~QmlJSQuickFixOperation(); virtual void perform(); @@ -111,14 +84,13 @@ protected: virtual void performChanges(QmlJSTools::QmlJSRefactoringFile *currentFile, QmlJSTools::QmlJSRefactoringChanges *refactoring) = 0; - /// \returns A const-reference to the state of the operation. - const QmlJSQuickFixState &state() const; + const Internal::QmlJSQuickFixAssistInterface *assistInterface() const; /// \returns The name of the file for for which this operation is invoked. QString fileName() const; private: - QmlJSQuickFixState _state; + QSharedPointer<const Internal::QmlJSQuickFixAssistInterface> m_interface; }; class QmlJSQuickFixFactory: public TextEditor::QuickFixFactory @@ -129,39 +101,20 @@ public: QmlJSQuickFixFactory(); virtual ~QmlJSQuickFixFactory(); - virtual QList<TextEditor::QuickFixOperation::Ptr> matchingOperations(TextEditor::QuickFixState *state); + virtual QList<TextEditor::QuickFixOperation::Ptr> + matchingOperations(const QSharedPointer<const TextEditor::IAssistInterface> &interface); /*! Implement this method to match and create the appropriate QmlJSQuickFixOperation objects. */ - virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state) = 0; + virtual QList<QmlJSQuickFixOperation::Ptr> match( + const QSharedPointer<const Internal::QmlJSQuickFixAssistInterface> &interface) = 0; static QList<QmlJSQuickFixOperation::Ptr> noResult(); static QList<QmlJSQuickFixOperation::Ptr> singleResult(QmlJSQuickFixOperation *operation); }; -namespace Internal { - -class QmlJSQuickFixCollector: public TextEditor::QuickFixCollector -{ - Q_OBJECT - -public: - QmlJSQuickFixCollector(); - virtual ~QmlJSQuickFixCollector(); - - virtual bool supportsEditor(TextEditor::ITextEditor *editor) const; - virtual bool supportsPolicy(TextEditor::CompletionPolicy policy) const; - virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::BaseTextEditorWidget *editor); - - virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; - - /// Registers all quick-fixes in this plug-in as auto-released objects. - static void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); -}; - -} // namespace Internal } // namespace QmlJSEditor #endif // QMLJSQUICKFIX_H diff --git a/src/plugins/qmljseditor/qmljsquickfixassist.cpp b/src/plugins/qmljseditor/qmljsquickfixassist.cpp new file mode 100644 index 0000000000..99a60eaa68 --- /dev/null +++ b/src/plugins/qmljseditor/qmljsquickfixassist.cpp @@ -0,0 +1,115 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmljsquickfixassist.h" +#include "qmljseditorconstants.h" + +//temp +#include "qmljsquickfix.h" + +#include <extensionsystem/pluginmanager.h> + +using namespace QmlJSEditor; +using namespace Internal; +using namespace QmlJSTools; +using namespace TextEditor; + +// ----------------------- +// QuickFixAssistInterface +// ----------------------- +QmlJSQuickFixAssistInterface::QmlJSQuickFixAssistInterface(QmlJSTextEditorWidget *editor, + TextEditor::AssistReason reason) + : DefaultAssistInterface(editor->document(), editor->position(), editor->file(), reason) + , m_editor(editor) + , m_semanticInfo(editor->semanticInfo()) +{} + +QmlJSQuickFixAssistInterface::~QmlJSQuickFixAssistInterface() +{} + +const SemanticInfo &QmlJSQuickFixAssistInterface::semanticInfo() const +{ + return m_semanticInfo; +} + +const QmlJSTools::QmlJSRefactoringFile QmlJSQuickFixAssistInterface::currentFile() const +{ + return QmlJSRefactoringFile(m_editor, m_semanticInfo.document); +} + +QWidget *QmlJSQuickFixAssistInterface::widget() const +{ + return m_editor; +} + +// ---------------------- +// QmlJSQuickFixProcessor +// ---------------------- +QmlJSQuickFixProcessor::QmlJSQuickFixProcessor(const TextEditor::IAssistProvider *provider) + : m_provider(provider) +{} + +QmlJSQuickFixProcessor::~QmlJSQuickFixProcessor() +{} + +const IAssistProvider *QmlJSQuickFixProcessor::provider() const +{ + return m_provider; +} + +// --------------------------- +// QmlJSQuickFixAssistProvider +// --------------------------- +QmlJSQuickFixAssistProvider::QmlJSQuickFixAssistProvider() +{} + +QmlJSQuickFixAssistProvider::~QmlJSQuickFixAssistProvider() +{} + +bool QmlJSQuickFixAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(Constants::C_QMLJSEDITOR_ID); +} + +IAssistProcessor *QmlJSQuickFixAssistProvider::createProcessor() const +{ + return new QmlJSQuickFixProcessor(this); +} + +QList<QuickFixFactory *> QmlJSQuickFixAssistProvider::quickFixFactories() const +{ + QList<TextEditor::QuickFixFactory *> results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (QmlJSQuickFixFactory *f, pm->getObjects<QmlJSEditor::QmlJSQuickFixFactory>()) + results.append(f); + return results; +} diff --git a/src/plugins/qmljseditor/qmljsquickfixassist.h b/src/plugins/qmljseditor/qmljsquickfixassist.h new file mode 100644 index 0000000000..3427cb1b41 --- /dev/null +++ b/src/plugins/qmljseditor/qmljsquickfixassist.h @@ -0,0 +1,91 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLJSQUICKFIXASSIST_H +#define QMLJSQUICKFIXASSIST_H + +#include "qmljseditor.h" + +#include <qmljstools/qmljsrefactoringchanges.h> + +#include <texteditor/codeassist/defaultassistinterface.h> +#include <texteditor/codeassist/quickfixassistprovider.h> +#include <texteditor/codeassist/quickfixassistprocessor.h> + +namespace QmlJSEditor { +namespace Internal { + +class QmlJSQuickFixAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + QmlJSQuickFixAssistInterface(QmlJSTextEditorWidget *editor, TextEditor::AssistReason reason); + virtual ~QmlJSQuickFixAssistInterface(); + + const SemanticInfo &semanticInfo() const; + const QmlJSTools::QmlJSRefactoringFile currentFile() const; + QWidget *widget() const; + +private: + QmlJSTextEditorWidget *m_editor; + SemanticInfo m_semanticInfo; +}; + + +class QmlJSQuickFixProcessor : public TextEditor::QuickFixAssistProcessor +{ +public: + QmlJSQuickFixProcessor(const TextEditor::IAssistProvider *provider); + virtual ~QmlJSQuickFixProcessor(); + + virtual const TextEditor::IAssistProvider *provider() const; + +private: + const TextEditor::IAssistProvider *m_provider; +}; + + +class QmlJSQuickFixAssistProvider : public TextEditor::QuickFixAssistProvider +{ +public: + QmlJSQuickFixAssistProvider(); + virtual ~QmlJSQuickFixAssistProvider(); + + virtual bool supportsEditor(const QString &editorId) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; + + virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; +}; + +} // Internal +} // QmlJSEditor + +#endif // QMLJSQUICKFIXASSIST_H diff --git a/src/plugins/qmljseditor/qmljsquickfixes.cpp b/src/plugins/qmljseditor/qmljsquickfixes.cpp index 9650617e8b..54c6820c3c 100644 --- a/src/plugins/qmljseditor/qmljsquickfixes.cpp +++ b/src/plugins/qmljseditor/qmljsquickfixes.cpp @@ -33,6 +33,7 @@ #include "qmljsquickfix.h" #include "qmljscomponentfromobjectdef.h" #include "qmljseditor.h" +#include "qmljsquickfixassist.h" #include <extensionsystem/iplugin.h> #include <extensionsystem/pluginmanager.h> @@ -64,13 +65,14 @@ namespace { class SplitInitializerOp: public QmlJSQuickFixFactory { public: - virtual QList<QmlJSQuickFixOperation::Ptr> match(const QmlJSQuickFixState &state) + virtual QList<QmlJSQuickFixOperation::Ptr> match( + const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface) { UiObjectInitializer *objectInitializer = 0; - const int pos = state.currentFile().cursor().position(); + const int pos = interface->currentFile().cursor().position(); - if (QmlJS::AST::Node *member = state.semanticInfo().declaringMember(pos)) { + if (QmlJS::AST::Node *member = interface->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; @@ -82,7 +84,7 @@ public: } if (objectInitializer) - return singleResult(new Operation(state, objectInitializer)); + return singleResult(new Operation(interface, objectInitializer)); else return noResult(); } @@ -93,8 +95,9 @@ private: UiObjectInitializer *_objectInitializer; public: - Operation(const QmlJSQuickFixState &state, UiObjectInitializer *objectInitializer) - : QmlJSQuickFixOperation(state, 0) + Operation(const QSharedPointer<const QmlJSQuickFixAssistInterface> &interface, + UiObjectInitializer *objectInitializer) + : QmlJSQuickFixOperation(interface, 0) , _objectInitializer(objectInitializer) { setDescription(QApplication::translate("QmlJSEditor::QuickFix", @@ -129,7 +132,7 @@ private: } // end of anonymous namespace -void QmlJSQuickFixCollector::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) +void registerQuickFixes(ExtensionSystem::IPlugin *plugIn) { plugIn->addAutoReleasedObject(new SplitInitializerOp); plugIn->addAutoReleasedObject(new ComponentFromObjectDef); diff --git a/src/plugins/qmljseditor/qmljsreuse.cpp b/src/plugins/qmljseditor/qmljsreuse.cpp new file mode 100644 index 0000000000..a60b0f4f56 --- /dev/null +++ b/src/plugins/qmljseditor/qmljsreuse.cpp @@ -0,0 +1,122 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "qmljsreuse.h" + +#include <QtCore/QChar> +#include <QtGui/QPainter> + +namespace QmlJSEditor { +namespace Internal { + +bool isIdentifierChar(const QChar &c, bool atStart, bool acceptDollar) +{ + switch (c.unicode()) { + case '_': + return true; + case '$': + if (acceptDollar) + return true; + return false; + + default: + if (atStart) + return c.isLetter(); + else + return c.isLetterOrNumber(); + } +} + +bool isDelimiterChar(const QChar &c) +{ + switch (c.unicode()) { + case '{': + case '}': + case '[': + case ']': + case ')': + case '?': + case '!': + case ':': + case ';': + case ',': + case '+': + case '-': + case '*': + case '/': + return true; + + default: + return false; + } +} + +bool isActivationChar(const QChar &c) +{ + if (c == QLatin1Char('(') || c == QLatin1Char('.') || c == QLatin1Char('/')) + return true; + return false; +} + +QIcon iconForColor(const QColor &color) +{ + QPixmap pix(6, 6); + + int pixSize = 20; + QBrush br(color); + + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, color); + br = QBrush(pm); + + QPainter p(&pix); + int corr = 1; + QRect r = pix.rect().adjusted(corr, corr, -corr, -corr); + p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); + p.fillRect(r, br); + + p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr, + r.width() / 2, r.height() / 2, + QColor(color.rgb())); + p.drawRect(pix.rect().adjusted(0, 0, -1, -1)); + + return pix; +} + + +} // Internal +} // QmlJSEditor diff --git a/src/plugins/qmljseditor/qmljsreuse.h b/src/plugins/qmljseditor/qmljsreuse.h new file mode 100644 index 0000000000..0b5cf1f9e8 --- /dev/null +++ b/src/plugins/qmljseditor/qmljsreuse.h @@ -0,0 +1,55 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QMLJSREUSE_H +#define QMLJSREUSE_H + +#include <QtCore/QtGlobal> +#include <QtGui/QIcon> + +QT_BEGIN_NAMESPACE +class QChar; +QT_END_NAMESPACE + +namespace QmlJSEditor { +namespace Internal { + +bool isIdentifierChar(const QChar &c, bool atStart = false, bool acceptDollar = true); +bool isDelimiterChar(const QChar &c); +bool isActivationChar(const QChar &c); + +QIcon iconForColor(const QColor &color); + +} // Internal +} // QmlJSEditor + +#endif // QMLJSREUSE_H diff --git a/src/plugins/qt4projectmanager/profilecompletion.cpp b/src/plugins/qt4projectmanager/profilecompletion.cpp deleted file mode 100644 index c8015c78a2..0000000000 --- a/src/plugins/qt4projectmanager/profilecompletion.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "profilecompletion.h" -#include "profileeditor.h" -#include "profilekeywords.h" -#include <texteditor/itexteditor.h> -#include <texteditor/completionsettings.h> -#include <cplusplus/Icons.h> -#include <QtCore/QDebug> - -using namespace Qt4ProjectManager::Internal; - -ProFileCompletion::ProFileCompletion(QObject *parent) : - TextEditor::ICompletionCollector(parent), - m_editor(0), - m_startPosition(-1), - m_variableIcon(CPlusPlus::Icons().iconForType(CPlusPlus::Icons::VarPublicIconType)), - m_functionIcon(CPlusPlus::Icons().iconForType(CPlusPlus::Icons::FuncPublicIconType)) -{ -} - -ProFileCompletion::~ProFileCompletion() -{ - -} - -QList<TextEditor::CompletionItem> ProFileCompletion::getCompletions() -{ - QList<TextEditor::CompletionItem> completionItems; - completions(&completionItems); - - return completionItems; -} - -bool ProFileCompletion::shouldRestartCompletion() -{ - return false; -} - -TextEditor::ITextEditor *ProFileCompletion::editor() const -{ - return m_editor; -} - -int ProFileCompletion::startPosition() const -{ - return m_startPosition; -} - -bool ProFileCompletion::supportsEditor(TextEditor::ITextEditor *editor) const -{ - return qobject_cast<ProFileEditor *>(editor) != 0; -} - -bool ProFileCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const -{ - return policy == TextEditor::SemanticCompletion; -} - -bool ProFileCompletion::triggersCompletion(TextEditor::ITextEditor *editor) -{ - m_editor = editor; - const int pos = editor->position(); - - if (completionSettings().m_completionTrigger == TextEditor::AutomaticCompletion) { - QChar characterUnderCursor = editor->characterAt(pos); - if (!characterUnderCursor.isLetterOrNumber()) { - m_startPosition = findStartOfName(); - if (pos - m_startPosition >= 3 && !isInComment()) - return true; - } - } - return false; -} - -int ProFileCompletion::findStartOfName(int pos) const -{ - if (pos == -1) - pos = m_editor->position(); - QChar chr; - - // Skip to the start of a name - do { - chr = m_editor->characterAt(--pos); - } while (chr.isLetterOrNumber() || chr == QLatin1Char('_')); - - return pos + 1; -} - -bool ProFileCompletion::isInComment() const -{ - const int beginOfLinePosition = m_editor->position(TextEditor::ITextEditor::StartOfLine); - const QString lineBeginning = m_editor->textAt(beginOfLinePosition, - m_startPosition - beginOfLinePosition); - if (lineBeginning.contains(QLatin1Char('#'))) - return true; - return false; -} - -int ProFileCompletion::startCompletion(TextEditor::ITextEditor *editor) -{ - m_editor = editor; - m_startPosition = findStartOfName(); - - return m_startPosition; -} - -void ProFileCompletion::completions(QList<TextEditor::CompletionItem> *completions) -{ - const int length = m_editor->position() - m_startPosition; - if (length < 0) - return; - - if (isInComment()) - return; - - const QString key = m_editor->textAt(m_startPosition, length); - - QList<TextEditor::CompletionItem> items; - QStringList keywords = ProFileKeywords::variables() - + ProFileKeywords::functions(); -// qSort(keywords); - for (int i = 0; i < keywords.count(); i++) { - TextEditor::CompletionItem item(this); - item.text = keywords[i]; - item.data = QVariant::fromValue(item.text); - item.icon = ProFileKeywords::isFunction(item.text) - ? m_functionIcon : m_variableIcon; - items.append(item); - } - - filter(items, completions, key); -} - -bool ProFileCompletion::typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar) -{ - // only '(' in case of a function - if (typedChar == QLatin1Char('(') && ProFileKeywords::isFunction(item.text)) - return true; - return false; -} - -void ProFileCompletion::complete(const TextEditor::CompletionItem &item, QChar typedChar) -{ - Q_UNUSED(typedChar) - - int replaceLength = m_editor->position() - m_startPosition; - if (replaceLength < 0) - return; - - QString toInsert = item.text; - int cursorOffset = 0; - if (ProFileKeywords::isFunction(toInsert) - && completionSettings().m_autoInsertBrackets) { - if (completionSettings().m_spaceAfterFunctionName) { - if (m_editor->textAt(m_editor->position(), 2) == QLatin1String(" (")) { - cursorOffset = 2; - } else if (m_editor->characterAt(m_editor->position()) == QLatin1Char('(') - || m_editor->characterAt(m_editor->position()) == QLatin1Char(' ')) { - replaceLength += 1; - toInsert += QLatin1String(" ("); - } else { - toInsert += QLatin1String(" ()"); - cursorOffset = -1; - } - } else { - if (m_editor->characterAt(m_editor->position()) == QLatin1Char('(')) { - cursorOffset = 1; - } else { - toInsert += QLatin1String("()"); - cursorOffset = -1; - } - } - } - - m_editor->setCursorPosition(m_startPosition); - m_editor->replace(replaceLength, toInsert); - if (cursorOffset) - m_editor->setCursorPosition(m_editor->position() + cursorOffset); -} - -bool ProFileCompletion::partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems) -{ - if (completionItems.count() == 1) { - complete(completionItems.first(), QChar()); - return true; - } - - return TextEditor::ICompletionCollector::partiallyComplete(completionItems); -} - -void ProFileCompletion::cleanup() -{ -} diff --git a/src/plugins/qt4projectmanager/profilecompletion.h b/src/plugins/qt4projectmanager/profilecompletion.h deleted file mode 100644 index 47f6646b0d..0000000000 --- a/src/plugins/qt4projectmanager/profilecompletion.h +++ /dev/null @@ -1,78 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#ifndef PROFILECOMPLETION_H -#define PROFILECOMPLETION_H - -#include <texteditor/icompletioncollector.h> - -namespace Qt4ProjectManager { - -namespace Internal { - -class ProFileCompletion : public TextEditor::ICompletionCollector -{ - Q_OBJECT -public: - ProFileCompletion(QObject *parent = 0); - - virtual ~ProFileCompletion(); - - virtual QList<TextEditor::CompletionItem> getCompletions(); - virtual bool shouldRestartCompletion(); - - virtual TextEditor::ITextEditor *editor() const; - virtual int startPosition() const; - - virtual bool supportsEditor(TextEditor::ITextEditor *editor) const; - virtual bool supportsPolicy(TextEditor::CompletionPolicy policy) const; - virtual bool triggersCompletion(TextEditor::ITextEditor *editor); - virtual int startCompletion(TextEditor::ITextEditor *editor); - virtual void completions(QList<TextEditor::CompletionItem> *completions); - virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); - virtual void cleanup(); -private: - int findStartOfName(int pos = -1) const; - bool isInComment() const; - - TextEditor::ITextEditor *m_editor; - int m_startPosition; - const QIcon m_variableIcon; - const QIcon m_functionIcon; -}; - -} // namespace Internal -} // namespace Qt4ProjectManager - -#endif // PROFILECOMPLETION_H diff --git a/src/plugins/qt4projectmanager/profilecompletionassist.cpp b/src/plugins/qt4projectmanager/profilecompletionassist.cpp new file mode 100644 index 0000000000..2d6b25fb01 --- /dev/null +++ b/src/plugins/qt4projectmanager/profilecompletionassist.cpp @@ -0,0 +1,231 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "profilecompletionassist.h" +#include "qt4projectmanagerconstants.h" +#include "profilekeywords.h" + +#include <texteditor/codeassist/iassistinterface.h> +#include <texteditor/codeassist/genericproposal.h> +#include <texteditor/completionsettings.h> +#include <texteditor/texteditorsettings.h> +#include <texteditor/basetexteditor.h> + +#include <cplusplus/Icons.h> + +#include <QtGui/QTextCursor> + +using namespace Qt4ProjectManager::Internal; +using namespace TextEditor; + +// ------------------------- +// ProFileAssistProposalItem +// ------------------------- +ProFileAssistProposalItem::ProFileAssistProposalItem() +{} + +ProFileAssistProposalItem::~ProFileAssistProposalItem() +{} + +bool ProFileAssistProposalItem::prematurelyApplies(const QChar &c) const +{ + // only '(' in case of a function + if (c == QLatin1Char('(') && ProFileKeywords::isFunction(text())) + return true; + return false; +} + +void ProFileAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const +{ + const CompletionSettings &settings = TextEditorSettings::instance()->completionSettings(); + + int replaceLength = editor->position() - basePosition; + QString toInsert = text(); + int cursorOffset = 0; + if (ProFileKeywords::isFunction(toInsert) && settings.m_autoInsertBrackets) { + if (settings.m_spaceAfterFunctionName) { + if (editor->textAt(editor->position(), 2) == QLatin1String(" (")) { + cursorOffset = 2; + } else if (editor->characterAt(editor->position()) == QLatin1Char('(') + || editor->characterAt(editor->position()) == QLatin1Char(' ')) { + replaceLength += 1; + toInsert += QLatin1String(" ("); + } else { + toInsert += QLatin1String(" ()"); + cursorOffset = -1; + } + } else { + if (editor->characterAt(editor->position()) == QLatin1Char('(')) { + cursorOffset = 1; + } else { + toInsert += QLatin1String("()"); + cursorOffset = -1; + } + } + } + + editor->setCursorPosition(basePosition); + editor->replace(replaceLength, toInsert); + if (cursorOffset) + editor->setCursorPosition(editor->position() + cursorOffset); +} + +// ------------------------------- +// ProFileCompletionAssistProvider +// ------------------------------- +ProFileCompletionAssistProvider::ProFileCompletionAssistProvider() +{} + +ProFileCompletionAssistProvider::~ProFileCompletionAssistProvider() +{} + +bool ProFileCompletionAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(Qt4ProjectManager::Constants::PROFILE_EDITOR_ID); +} + +bool ProFileCompletionAssistProvider::isAsynchronous() const +{ + return false; +} + +int ProFileCompletionAssistProvider::activationCharSequenceLength() const +{ + return 0; +} + +bool ProFileCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + Q_UNUSED(sequence); + return false; +} + +bool ProFileCompletionAssistProvider::isContinuationChar(const QChar &c) const +{ + return c.isLetterOrNumber() || c == QLatin1Char('_'); +} + +IAssistProcessor *ProFileCompletionAssistProvider::createProcessor() const +{ + return new ProFileCompletionAssistProcessor; +} + +// -------------------------------- +// ProFileCompletionAssistProcessor +// -------------------------------- +ProFileCompletionAssistProcessor::ProFileCompletionAssistProcessor() + : m_startPosition(-1) + , m_variableIcon(CPlusPlus::Icons().iconForType(CPlusPlus::Icons::VarPublicIconType)) + , m_functionIcon(CPlusPlus::Icons().iconForType(CPlusPlus::Icons::FuncPublicIconType)) +{} + +ProFileCompletionAssistProcessor::~ProFileCompletionAssistProcessor() +{} + +IAssistProposal *ProFileCompletionAssistProcessor::perform(const IAssistInterface *interface) +{ + m_interface.reset(interface); + + if (isInComment()) + return 0; + + if (interface->reason() == IdleEditor && !acceptsIdleEditor()) + return 0; + + if (m_startPosition == -1) + m_startPosition = findStartOfName(); + + QList<TextEditor::BasicProposalItem *> items; + QStringList keywords = ProFileKeywords::variables() + ProFileKeywords::functions(); + for (int i = 0; i < keywords.count(); i++) { + BasicProposalItem *item = new ProFileAssistProposalItem; + item->setText(keywords[i]); + item->setIcon(ProFileKeywords::isFunction(item->text()) ? m_functionIcon : m_variableIcon); + items.append(item); + } + + return new GenericProposal(m_startPosition, new ProFileAssistProposalModel(items)); +} + +bool ProFileCompletionAssistProcessor::acceptsIdleEditor() +{ + const int pos = m_interface->position(); + QChar characterUnderCursor = m_interface->characterAt(pos); + if (!characterUnderCursor.isLetterOrNumber()) { + m_startPosition = findStartOfName(); + if (pos - m_startPosition >= 3 && !isInComment()) + return true; + } + return false; +} + +int ProFileCompletionAssistProcessor::findStartOfName(int pos) const +{ + if (pos == -1) + pos = m_interface->position(); + QChar chr; + + // Skip to the start of a name + do { + chr = m_interface->characterAt(--pos); + } while (chr.isLetterOrNumber() || chr == QLatin1Char('_')); + + return pos + 1; +} + +bool ProFileCompletionAssistProcessor::isInComment() const +{ + QTextCursor tc(m_interface->document()); + tc.setPosition(m_interface->position()); + tc.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor); + const QString &lineBeginning = tc.selectedText(); + if (lineBeginning.contains(QLatin1Char('#'))) + return true; + return false; +} + +// -------------------------- +// ProFileAssistProposalModel +// -------------------------- +ProFileAssistProposalModel::ProFileAssistProposalModel( + const QList<BasicProposalItem *> &items) + : BasicProposalItemListModel(items) +{} + +ProFileAssistProposalModel::~ProFileAssistProposalModel() +{} + +bool ProFileAssistProposalModel::isSortable() const +{ + return false; +} diff --git a/src/plugins/qt4projectmanager/profilecompletionassist.h b/src/plugins/qt4projectmanager/profilecompletionassist.h new file mode 100644 index 0000000000..0569a57281 --- /dev/null +++ b/src/plugins/qt4projectmanager/profilecompletionassist.h @@ -0,0 +1,106 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef PROFILECOMPLETIONASSIST_H +#define PROFILECOMPLETIONASSIST_H + +#include <texteditor/codeassist/basicproposalitem.h> +#include <texteditor/codeassist/basicproposalitemlistmodel.h> +#include <texteditor/codeassist/completionassistprovider.h> +#include <texteditor/codeassist/iassistprocessor.h> + +#include <QtGui/QIcon> + +namespace Qt4ProjectManager { +namespace Internal { + +class ProFileAssistProposalItem : public TextEditor::BasicProposalItem +{ +public: + ProFileAssistProposalItem(); + virtual ~ProFileAssistProposalItem(); + + virtual bool prematurelyApplies(const QChar &c) const; + virtual void applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const; +}; + + +class ProFileCompletionAssistProvider : public TextEditor::CompletionAssistProvider +{ +public: + ProFileCompletionAssistProvider(); + virtual ~ProFileCompletionAssistProvider(); + + virtual bool supportsEditor(const QString &editorId) const; + virtual TextEditor::IAssistProcessor *createProcessor() const; + + virtual bool isAsynchronous() const; + virtual int activationCharSequenceLength() const; + virtual bool isActivationCharSequence(const QString &sequence) const; + virtual bool isContinuationChar(const QChar &c) const; +}; + + +class ProFileCompletionAssistProcessor : public TextEditor::IAssistProcessor +{ +public: + ProFileCompletionAssistProcessor(); + virtual ~ProFileCompletionAssistProcessor(); + + virtual TextEditor::IAssistProposal *perform(const TextEditor::IAssistInterface *interface); + +private: + bool acceptsIdleEditor(); + int findStartOfName(int pos = -1) const; + bool isInComment() const; + + int m_startPosition; + QScopedPointer<const TextEditor::IAssistInterface> m_interface; + const QIcon m_variableIcon; + const QIcon m_functionIcon; +}; + + +class ProFileAssistProposalModel : public TextEditor::BasicProposalItemListModel +{ +public: + ProFileAssistProposalModel(const QList<TextEditor::BasicProposalItem *> &items); + virtual ~ProFileAssistProposalModel(); + + virtual bool isSortable() const; +}; + +} // Internal +} // Qt4ProjectManager + +#endif // PROFILECOMPLETIONASSIST_H diff --git a/src/plugins/qt4projectmanager/profileeditorfactory.cpp b/src/plugins/qt4projectmanager/profileeditorfactory.cpp index 00052b1d8e..95fc5aa40a 100644 --- a/src/plugins/qt4projectmanager/profileeditorfactory.cpp +++ b/src/plugins/qt4projectmanager/profileeditorfactory.cpp @@ -40,7 +40,6 @@ #include <coreplugin/editormanager/editormanager.h> #include <texteditor/texteditoractionhandler.h> #include <texteditor/texteditorsettings.h> -#include <texteditor/completionsupport.h> #include <QtCore/QFileInfo> #include <QtGui/QAction> diff --git a/src/plugins/qt4projectmanager/qt4projectmanager.pro b/src/plugins/qt4projectmanager/qt4projectmanager.pro index 436f4500e1..86da78e4bb 100644 --- a/src/plugins/qt4projectmanager/qt4projectmanager.pro +++ b/src/plugins/qt4projectmanager/qt4projectmanager.pro @@ -69,7 +69,6 @@ HEADERS += \ qmldumptool.h \ qmlobservertool.h \ qmldebugginglibrary.h \ - profilecompletion.h \ profilekeywords.h \ debugginghelperbuildtask.h \ qt4targetsetupwidget.h \ @@ -78,7 +77,8 @@ HEADERS += \ qtversionfactory.h \ winceqtversionfactory.h \ baseqtversion.h \ - winceqtversion.h + winceqtversion.h \ + profilecompletionassist.h SOURCES += qt4projectmanagerplugin.cpp \ qtparser.cpp \ @@ -142,13 +142,14 @@ SOURCES += qt4projectmanagerplugin.cpp \ qmldumptool.cpp \ qmlobservertool.cpp \ qmldebugginglibrary.cpp \ - profilecompletion.cpp \ profilekeywords.cpp \ debugginghelperbuildtask.cpp \ qtversionfactory.cpp \ winceqtversionfactory.cpp \ baseqtversion.cpp \ - winceqtversion.cpp + winceqtversion.cpp \ + profilecompletionassist.cpp + FORMS += makestep.ui \ qmakestep.ui \ qt4projectconfigwidget.ui \ diff --git a/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp b/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp index feb220c2d7..c75d409c97 100644 --- a/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp +++ b/src/plugins/qt4projectmanager/qt4projectmanagerplugin.cpp @@ -54,7 +54,7 @@ #include "qtoptionspage.h" #include "externaleditors.h" #include "gettingstartedwelcomepage.h" -#include "profilecompletion.h" +#include "profilecompletionassist.h" #include "qt-maemo/maemomanager.h" #include "qt-s60/s60manager.h" @@ -180,13 +180,7 @@ bool Qt4ProjectManagerPlugin::initialize(const QStringList &arguments, QString * addAutoReleasedObject(new SimulatorQtVersionFactory); addAutoReleasedObject(new WinCeQtVersionFactory); - ProFileCompletion *completion = new ProFileCompletion; - addAutoReleasedObject(completion); - // Set completion settings and keep them up to date - TextEditor::TextEditorSettings *textEditorSettings = TextEditor::TextEditorSettings::instance(); - completion->setCompletionSettings(textEditorSettings->completionSettings()); - connect(textEditorSettings, SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), - completion, SLOT(setCompletionSettings(TextEditor::CompletionSettings))); + addAutoReleasedObject(new ProFileCompletionAssistProvider); // TODO reenable //m_embeddedPropertiesPage = new EmbeddedPropertiesPage; diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp index f4dc040e2a..211c7a8d98 100644 --- a/src/plugins/texteditor/basetexteditor.cpp +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -38,7 +38,6 @@ #include "behaviorsettings.h" #include "codecselector.h" #include "completionsettings.h" -#include "completionsupport.h" #include "tabsettings.h" #include "texteditorconstants.h" #include "texteditorplugin.h" @@ -48,6 +47,9 @@ #include "indenter.h" #include "autocompleter.h" #include "snippet.h" +#include "codeassistant.h" +#include "defaultassistinterface.h" +#include "convenience.h" #include <aggregation/aggregate.h> #include <coreplugin/actionmanager/actionmanager.h> @@ -236,11 +238,6 @@ BaseTextEditorWidget::BaseTextEditorWidget(QWidget *parent) d->m_highlightBlocksTimer->setSingleShot(true); connect(d->m_highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(_q_highlightBlocks())); - d->m_requestAutoCompletionTimer = new QTimer(this); - d->m_requestAutoCompletionTimer->setSingleShot(true); - d->m_requestAutoCompletionTimer->setInterval(500); - connect(d->m_requestAutoCompletionTimer, SIGNAL(timeout()), this, SLOT(_q_requestAutoCompletion())); - d->m_animator = 0; d->m_searchResultFormat.setBackground(QColor(0xffef0b)); @@ -490,7 +487,8 @@ ITextMarkable *BaseTextEditorWidget::markableInterface() const BaseTextEditor *BaseTextEditorWidget::editor() const { if (!d->m_editor) { - d->m_editor = const_cast<BaseTextEditorWidget*>(this)->createEditor(); + d->m_editor = const_cast<BaseTextEditorWidget *>(this)->createEditor(); + d->m_codeAssistant->configure(d->m_editor); connect(this, SIGNAL(textChanged()), d->m_editor, SIGNAL(contentsChanged())); connect(this, SIGNAL(changed()), @@ -668,6 +666,9 @@ void BaseTextEditorWidget::editorContentsChange(int position, int charsRemoved, if (doc->isRedoAvailable()) emit editor()->contentsChangedBecauseOfUndo(); + + if (charsAdded != 0 && characterAt(position + charsAdded - 1).isPrint()) + d->m_assistRelevantContentAdded = true; } void BaseTextEditorWidget::slotSelectionChanged() @@ -1535,9 +1536,7 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) if (!ro && (e == QKeySequence::InsertParagraphSeparator - || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator)) - ) { - + || (!d->m_lineSeparatorsAllowed && e == QKeySequence::InsertLineSeparator))) { if (d->m_snippetOverlay->isVisible()) { e->accept(); d->m_snippetOverlay->hide(); @@ -1548,7 +1547,6 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) return; } - QTextCursor cursor = textCursor(); if (d->m_inBlockSelectionMode) cursor.clearSelection(); @@ -1702,7 +1700,8 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) case Qt::Key_Right: case Qt::Key_Left: #ifndef Q_WS_MAC - if ((e->modifiers() & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { + if ((e->modifiers() + & (Qt::AltModifier | Qt::ShiftModifier)) == (Qt::AltModifier | Qt::ShiftModifier)) { int diff_row = 0; int diff_col = 0; if (e->key() == Qt::Key_Up) @@ -1833,39 +1832,8 @@ void BaseTextEditorWidget::keyPressEvent(QKeyEvent *e) if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled) d->m_parenthesesMatchingTimer->start(50); - - if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint()) { - maybeRequestAutoCompletion(e->text().at(0)); - } - -} - -void BaseTextEditorWidget::maybeRequestAutoCompletion(const QChar &ch) -{ - if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) { - if (CompletionSupport::instance()->isActive()) - d->m_requestAutoCompletionTimer->stop(); - else { - d->m_requestAutoCompletionRevision = document()->revision(); - d->m_requestAutoCompletionPosition = position(); - d->m_requestAutoCompletionTimer->start(); - } - } else { - d->m_requestAutoCompletionTimer->stop(); - CompletionSupport::instance()->complete(editor(), SemanticCompletion, false); - } -} - -void BaseTextEditorWidget::_q_requestAutoCompletion() -{ - d->m_requestAutoCompletionTimer->stop(); - - if (CompletionSupport::instance()->isActive()) - return; - - if (d->m_requestAutoCompletionRevision == document()->revision() - && d->m_requestAutoCompletionPosition == position()) - CompletionSupport::instance()->complete(editor(), SemanticCompletion, false); + if (!ro && d->m_contentsChanged && !e->text().isEmpty() && e->text().at(0).isPrint()) + d->m_codeAssistant->process(); } void BaseTextEditorWidget::insertCodeSnippet(const QTextCursor &cursor_arg, const QString &snippet) @@ -2025,14 +1993,7 @@ int BaseTextEditorWidget::position(ITextEditor::PositionOperation posOp, int at) void BaseTextEditorWidget::convertPosition(int pos, int *line, int *column) const { - QTextBlock block = document()->findBlock(pos); - if (!block.isValid()) { - (*line) = -1; - (*column) = -1; - } else { - (*line) = block.blockNumber() + 1; - (*column) = pos - block.position(); - } + Convenience::convertPosition(document(), pos, line, column); } QChar BaseTextEditorWidget::characterAt(int pos) const @@ -2087,6 +2048,7 @@ BaseTextDocument *BaseTextEditorWidget::baseTextDocument() const return d->m_document; } + void BaseTextEditorWidget::setBaseTextDocument(BaseTextDocument *doc) { if (doc) { @@ -2403,9 +2365,8 @@ BaseTextEditorPrivate::BaseTextEditorPrivate() m_findScopeVerticalBlockSelectionFirstColumn(-1), m_findScopeVerticalBlockSelectionLastColumn(-1), m_highlightBlocksTimer(0), - m_requestAutoCompletionRevision(0), - m_requestAutoCompletionPosition(0), - m_requestAutoCompletionTimer(0), + m_codeAssistant(new CodeAssistant), + m_assistRelevantContentAdded(false), m_cursorBlockNumber(-1), m_autoCompleter(new AutoCompleter), m_indenter(new Indenter) @@ -3787,6 +3748,7 @@ void BaseTextEditorWidget::extraAreaPaintEvent(QPaintEvent *e) const QString &number = QString::number(blockNumber + 1); bool selected = ( (selStart < block.position() + block.length() + && selEnd > block.position()) || (selStart == selEnd && selStart == block.position()) ); @@ -3961,6 +3923,7 @@ void BaseTextEditorWidget::slotCursorPositionChanged() } else if (d->m_contentsChanged) { saveCurrentCursorPositionForNavigation(); } + updateHighlights(); } @@ -5650,8 +5613,8 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) if (text.isEmpty()) return; - if (CompletionSupport::instance()->isActive()) - setFocus(); + if (d->m_codeAssistant->hasContext()) + d->m_codeAssistant->destroyContext(); QStringList lines = text.split(QLatin1Char('\n')); QTextCursor cursor = textCursor(); @@ -5696,8 +5659,8 @@ void BaseTextEditorWidget::insertFromMimeData(const QMimeData *source) if (text.isEmpty()) return; - if (CompletionSupport::instance()->isActive()) - setFocus(); + if (d->m_codeAssistant->hasContext()) + d->m_codeAssistant->destroyContext(); if (d->m_snippetOverlay->isVisible() && (text.contains(QLatin1Char('\n')) || text.contains(QLatin1Char('\t')))) { @@ -5882,18 +5845,7 @@ QString BaseTextEditor::selectedText() const QString BaseTextEditor::textAt(int pos, int length) const { - QTextCursor c = e->textCursor(); - - if (pos < 0) - pos = 0; - c.movePosition(QTextCursor::End); - if (pos + length > c.position()) - length = c.position() - pos; - - c.setPosition(pos); - c.setPosition(pos + length, QTextCursor::KeepAnchor); - - return c.selectedText(); + return Convenience::textAt(e->textCursor(), pos, length); } void BaseTextEditor::remove(int length) @@ -6166,3 +6118,15 @@ void BaseTextEditorWidget::inSnippetMode(bool *active) { *active = d->m_snippetOverlay->isVisible(); } + +void BaseTextEditorWidget::invokeAssist(AssistKind kind, IAssistProvider *provider) +{ + d->m_codeAssistant->invoke(kind, provider); +} + +IAssistInterface *BaseTextEditorWidget::createAssistInterface(AssistKind kind, + AssistReason reason) const +{ + Q_UNUSED(kind); + return new DefaultAssistInterface(document(), position(), d->m_document, reason); +} diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index 083cbceae3..dd8e141a04 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -34,7 +34,7 @@ #define BASETEXTEDITOR_H #include "itexteditor.h" -#include "icompletioncollector.h" +#include "codeassist/assistenums.h" #include <find/ifindsupport.h> @@ -56,6 +56,9 @@ namespace TextEditor { class TabSettings; class RefactorOverlay; struct RefactorMarker; +class IAssistMonitorInterface; +class IAssistInterface; +class IAssistProvider; namespace Internal { class BaseTextEditorPrivate; @@ -235,6 +238,11 @@ public: QPoint toolTipPosition(const QTextCursor &c) const; + void invokeAssist(AssistKind assistKind, IAssistProvider *provider = 0); + + virtual IAssistInterface *createAssistInterface(AssistKind assistKind, + AssistReason assistReason) const; + public slots: void setDisplayName(const QString &title); @@ -492,7 +500,6 @@ signals: void requestBlockUpdate(const QTextBlock &); private: - void maybeRequestAutoCompletion(const QChar &ch); void indentOrUnindent(bool doIndent); void handleHomeKey(bool anchor); void handleBackspaceKey(); @@ -531,9 +538,6 @@ private: void transformSelection(Internal::TransformationMethod method); private slots: - // auto completion - void _q_requestAutoCompletion(); - void handleBlockSelection(int diff_row, int diff_col); // parentheses matcher diff --git a/src/plugins/texteditor/basetexteditor_p.h b/src/plugins/texteditor/basetexteditor_p.h index d5d05061c4..189e587424 100644 --- a/src/plugins/texteditor/basetexteditor_p.h +++ b/src/plugins/texteditor/basetexteditor_p.h @@ -54,6 +54,7 @@ namespace TextEditor { class BaseTextDocument; class TextEditorActionHandler; +class CodeAssistant; namespace Internal { @@ -288,9 +289,8 @@ public: BaseTextEditorPrivateHighlightBlocks m_highlightBlocksInfo; QTimer *m_highlightBlocksTimer; - int m_requestAutoCompletionRevision; - int m_requestAutoCompletionPosition; - QTimer *m_requestAutoCompletionTimer; + QScopedPointer<CodeAssistant> m_codeAssistant; + bool m_assistRelevantContentAdded; QPointer<BaseTextEditorAnimator> m_animator; int m_cursorBlockNumber; diff --git a/src/plugins/texteditor/codeassist/assistenums.h b/src/plugins/texteditor/codeassist/assistenums.h new file mode 100644 index 0000000000..5bfd760eea --- /dev/null +++ b/src/plugins/texteditor/codeassist/assistenums.h @@ -0,0 +1,53 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef ASSISTENUMS_H +#define ASSISTENUMS_H + +namespace TextEditor { + +enum AssistKind +{ + Completion, + QuickFix +}; + +enum AssistReason +{ + IdleEditor, + ActivationCharacter, + ExplicitlyInvoked +}; + +} // TextEditor + +#endif // ASSISTENUMS_H diff --git a/src/plugins/texteditor/codeassist/basicproposalitem.cpp b/src/plugins/texteditor/codeassist/basicproposalitem.cpp new file mode 100644 index 0000000000..f1d0045f19 --- /dev/null +++ b/src/plugins/texteditor/codeassist/basicproposalitem.cpp @@ -0,0 +1,142 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "basicproposalitem.h" + +#include <texteditor/basetexteditor.h> +#include <texteditor/quickfix.h> + +#include <QtGui/QTextCursor> + +using namespace TextEditor; + +BasicProposalItem::BasicProposalItem() + : m_order(0) +{} + +BasicProposalItem::~BasicProposalItem() +{} + +void BasicProposalItem::setIcon(const QIcon &icon) +{ + m_icon = icon; +} + +const QIcon &BasicProposalItem::icon() const +{ + return m_icon; +} + +void BasicProposalItem::setText(const QString &text) +{ + m_text = text; +} + +const QString &BasicProposalItem::text() const +{ + return m_text; +} + +void BasicProposalItem::setDetail(const QString &detail) +{ + m_detail = detail; +} + +const QString &BasicProposalItem::detail() const +{ + return m_detail; +} + +void BasicProposalItem::setData(const QVariant &var) +{ + m_data = var; +} + +const QVariant &BasicProposalItem::data() const +{ + return m_data; +} + +int BasicProposalItem::order() const +{ + return m_order; +} + +void BasicProposalItem::setOrder(int order) +{ + m_order = order; +} + +bool BasicProposalItem::implicitlyApplies() const +{ + return !data().canConvert<QString>() && !data().canConvert<QuickFixOperation::Ptr>(); +} + +bool BasicProposalItem::prematurelyApplies(const QChar &c) const +{ + Q_UNUSED(c); + return false; +} + +void BasicProposalItem::apply(BaseTextEditor *editor, int basePosition) const +{ + if (data().canConvert<QString>()) + applySnippet(editor, basePosition); + else if (data().canConvert<QuickFixOperation::Ptr>()) + applyQuickFix(editor, basePosition); + else + applyContextualContent(editor, basePosition); +} + +void BasicProposalItem::applyContextualContent(BaseTextEditor *editor, int basePosition) const +{ + const int currentPosition = editor->position(); + editor->setCursorPosition(basePosition); + editor->replace(currentPosition - basePosition, text()); +} + +void BasicProposalItem::applySnippet(BaseTextEditor *editor, int basePosition) const +{ + BaseTextEditorWidget *editorWidget = static_cast<BaseTextEditorWidget *>(editor->widget()); + QTextCursor tc = editorWidget->textCursor(); + tc.setPosition(basePosition, QTextCursor::KeepAnchor); + editorWidget->insertCodeSnippet(tc, data().toString()); +} + +void BasicProposalItem::applyQuickFix(BaseTextEditor *editor, int basePosition) const +{ + Q_UNUSED(editor) + Q_UNUSED(basePosition) + + QuickFixOperation::Ptr op = data().value<QuickFixOperation::Ptr>(); + op->perform(); +} diff --git a/src/plugins/texteditor/codeassist/basicproposalitem.h b/src/plugins/texteditor/codeassist/basicproposalitem.h new file mode 100644 index 0000000000..4478fb8fb2 --- /dev/null +++ b/src/plugins/texteditor/codeassist/basicproposalitem.h @@ -0,0 +1,83 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef BASICPROPOSALITEM_H +#define BASICPROPOSALITEM_H + +#include "iassistproposalitem.h" + +#include <texteditor/texteditor_global.h> + +#include <QtCore/QVariant> +#include <QtGui/QIcon> + +namespace TextEditor { + +class TEXTEDITOR_EXPORT BasicProposalItem : public IAssistProposalItem +{ +public: + BasicProposalItem(); + virtual ~BasicProposalItem(); + + void setIcon(const QIcon &icon); + const QIcon &icon() const; + + void setText(const QString &text); + const QString &text() const; + + void setDetail(const QString &detail); + const QString &detail() const; + + void setData(const QVariant &var); + const QVariant &data() const; + + int order() const; + void setOrder(int order); + + virtual bool implicitlyApplies() const; + virtual bool prematurelyApplies(const QChar &c) const; + virtual void apply(BaseTextEditor *editor, int basePosition) const; + virtual void applyContextualContent(BaseTextEditor *editor, int basePosition) const; + virtual void applySnippet(BaseTextEditor *editor, int basePosition) const; + virtual void applyQuickFix(BaseTextEditor *editor, int basePosition) const; + +private: + QIcon m_icon; + QString m_text; + QString m_detail; + QVariant m_data; + int m_order; +}; + +} // TextEditor + +#endif // BASICPROPOSALITEM_H diff --git a/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.cpp b/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.cpp new file mode 100644 index 0000000000..ae34da69f6 --- /dev/null +++ b/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.cpp @@ -0,0 +1,267 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "basicproposalitemlistmodel.h" +#include "basicproposalitem.h" +#include "texteditorsettings.h" +#include "completionsettings.h" + +#include <QtCore/QDebug> +#include <QtCore/QRegExp> +#include <QtCore/QtAlgorithms> +#include <QtCore/QHash> + +#include <algorithm> + +using namespace TextEditor; + +uint qHash(const BasicProposalItem &item) +{ + return qHash(item.text()); +} + +namespace { + +const int kMaxSort = 1000; +const int kMaxPrefixFilter = 100; + +struct ContentLessThan +{ + bool operator()(const BasicProposalItem *a, const BasicProposalItem *b) + { + // The order is case-insensitive in principle, but case-sensitive when this + // would otherwise mean equality + const QString &lowera = a->text().toLower(); + const QString &lowerb = b->text().toLower(); + if (lowera == lowerb) + return lessThan(a->text(), b->text()); + else + return lessThan(lowera, lowerb); + } + + bool lessThan(const QString &a, const QString &b) + { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), CharLessThan()); + } + + struct CharLessThan + { + bool operator()(const QChar &a, const QChar &b) + { + if (a == QLatin1Char('_')) + return false; + else if (b == QLatin1Char('_')) + return true; + else + return a < b; + } + }; +}; + +} // Anonymous + +BasicProposalItemListModel::BasicProposalItemListModel() +{} + +BasicProposalItemListModel::BasicProposalItemListModel(const QList<BasicProposalItem *> &items) + : m_originalItems(items) + , m_currentItems(items) +{ + mapPersistentIds(); +} + +BasicProposalItemListModel::~BasicProposalItemListModel() +{ + qDeleteAll(m_originalItems); +} + +void BasicProposalItemListModel::loadContent(const QList<BasicProposalItem *> &items) +{ + m_originalItems = items; + m_currentItems = items; + mapPersistentIds(); +} + +void BasicProposalItemListModel::mapPersistentIds() +{ + for (int i = 0; i < m_originalItems.size(); ++i) + m_idByText.insert(m_originalItems.at(i)->text(), i); +} + +void BasicProposalItemListModel::reset() +{ + m_currentItems = m_originalItems; +} + +int BasicProposalItemListModel::size() const +{ + return m_currentItems.size(); +} + +QString BasicProposalItemListModel::text(int index) const +{ + return m_currentItems.at(index)->text(); +} + +QIcon BasicProposalItemListModel::icon(int index) const +{ + return m_currentItems.at(index)->icon(); +} + +QString BasicProposalItemListModel::detail(int index) const +{ + return m_currentItems.at(index)->detail(); +} + +void BasicProposalItemListModel::removeDuplicates() +{ + QHash<QString, QVariant> unique; + QList<BasicProposalItem *>::iterator it = m_originalItems.begin(); + while (it != m_originalItems.end()) { + const BasicProposalItem *item = *it; + if (unique.contains(item->text()) + && unique.value(item->text(), QVariant()) == item->data()) { + it = m_originalItems.erase(it); + } else { + unique.insert(item->text(), item->data()); + ++it; + } + } +} + +void BasicProposalItemListModel::filter(const QString &prefix) +{ + if (prefix.isEmpty()) + return; + + /* + * This code builds a regular expression in order to more intelligently match + * camel-case style. This means upper-case characters will be rewritten as follows: + * + * A => [a-z0-9_]*A (for any but the first capital letter) + * + * Meaning it allows any sequence of lower-case characters to preceed an + * upper-case character. So for example gAC matches getActionController. + * + * It also implements the first-letter-only case sensitivity. + */ + const TextEditor::CaseSensitivity caseSensitivity = + TextEditorSettings::instance()->completionSettings().m_caseSensitivity; + + QString keyRegExp; + keyRegExp += QLatin1Char('^'); + bool first = true; + const QLatin1String wordContinuation("[a-z0-9_]*"); + foreach (const QChar &c, prefix) { + if (caseSensitivity == TextEditor::CaseInsensitive || + (caseSensitivity == TextEditor::FirstLetterCaseSensitive && !first)) { + + keyRegExp += QLatin1String("(?:"); + if (c.isUpper() && !first) + keyRegExp += wordContinuation; + keyRegExp += QRegExp::escape(c.toUpper()); + keyRegExp += QLatin1Char('|'); + keyRegExp += QRegExp::escape(c.toLower()); + keyRegExp += QLatin1Char(')'); + } else { + if (c.isUpper() && !first) + keyRegExp += wordContinuation; + keyRegExp += QRegExp::escape(c); + } + + first = false; + } + const QRegExp regExp(keyRegExp); + + m_currentItems.clear(); + for (QList<BasicProposalItem *>::const_iterator it = m_originalItems.begin(); + it != m_originalItems.end(); + ++it) { + BasicProposalItem *item = *it; + if (regExp.indexIn(item->text()) == 0) + m_currentItems.append(item); + } +} + +bool BasicProposalItemListModel::isSortable() const +{ + if (m_currentItems.size() < kMaxSort) + return true; + return false; +} + +void BasicProposalItemListModel::sort() +{ + qStableSort(m_currentItems.begin(), m_currentItems.end(), ContentLessThan()); +} + +int BasicProposalItemListModel::persistentId(int index) const +{ + return m_idByText.value(m_currentItems.at(index)->text()); +} + +bool BasicProposalItemListModel::supportsPrefixExpansion() const +{ + return true; +} + +QString BasicProposalItemListModel::proposalPrefix() const +{ + if (m_currentItems.size() >= kMaxPrefixFilter) + return QString(); + + // Compute common prefix + QString firstKey = m_currentItems.first()->text(); + QString lastKey = m_currentItems.last()->text(); + const int length = qMin(firstKey.length(), lastKey.length()); + firstKey.truncate(length); + lastKey.truncate(length); + + while (firstKey != lastKey) { + firstKey.chop(1); + lastKey.chop(1); + } + + return firstKey; +} + +IAssistProposalItem *BasicProposalItemListModel::proposalItem(int index) const +{ + return m_currentItems.at(index); +} + +QPair<QList<BasicProposalItem *>::iterator, + QList<BasicProposalItem *>::iterator> +BasicProposalItemListModel::currentItems() +{ + return qMakePair(m_currentItems.begin(), m_currentItems.end()); +} diff --git a/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.h b/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.h new file mode 100644 index 0000000000..482279a9a2 --- /dev/null +++ b/src/plugins/texteditor/codeassist/basicproposalitemlistmodel.h @@ -0,0 +1,85 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef BASICPROPOSALITEMLISTMODEL_H +#define BASICPROPOSALITEMLISTMODEL_H + +#include "igenericproposalmodel.h" + +#include <texteditor/texteditor_global.h> + +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QPair> + +namespace TextEditor { + +class BasicProposalItem; + +class TEXTEDITOR_EXPORT BasicProposalItemListModel : public IGenericProposalModel +{ +public: + BasicProposalItemListModel(); + BasicProposalItemListModel(const QList<BasicProposalItem *> &items); + virtual ~BasicProposalItemListModel(); + + virtual void reset(); + virtual int size() const; + virtual QString text(int index) const; + virtual QIcon icon(int index) const; + virtual QString detail(int index) const; + virtual int persistentId(int index) const; + virtual void removeDuplicates(); + virtual void filter(const QString &prefix); + virtual bool isSortable() const; + virtual void sort(); + virtual bool supportsPrefixExpansion() const; + virtual QString proposalPrefix() const; + virtual IAssistProposalItem *proposalItem(int index) const; + + void loadContent(const QList<BasicProposalItem *> &items); + +protected: + typedef QList<BasicProposalItem *>::iterator ItemIterator; + QPair<ItemIterator, ItemIterator> currentItems(); + +private: + void mapPersistentIds(); + + QHash<QString, int> m_idByText; + QList<BasicProposalItem *> m_originalItems; + QList<BasicProposalItem *> m_currentItems; +}; + +} // TextEditor + +#endif // BASICPROPOSALITEMLISTMODEL_H diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp new file mode 100644 index 0000000000..e59b6e426e --- /dev/null +++ b/src/plugins/texteditor/codeassist/codeassistant.cpp @@ -0,0 +1,506 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "codeassistant.h" +#include "completionassistprovider.h" +#include "quickfixassistprovider.h" +#include "iassistprocessor.h" +#include "iassistproposal.h" +#include "iassistproposalwidget.h" +#include "iassistinterface.h" +#include "iassistproposalitem.h" +#include "runner.h" + +#include <texteditor/basetexteditor.h> +#include <texteditor/texteditorsettings.h> +#include <texteditor/completionsettings.h> +#include <extensionsystem/pluginmanager.h> + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtCore/QTimer> +#include <QtCore/QDebug> +#include <QtGui/QKeyEvent> + +using namespace TextEditor; +using namespace Internal; + +namespace { + +template <class T> +void filterEditorSpecificProviders(QList<T *> *providers, const QString &editorId) +{ + typename QList<T *>::iterator it = providers->begin(); + while (it != providers->end()) { + if ((*it)->supportsEditor(editorId)) + ++it; + else + it = providers->erase(it); + } +} + +} // Anonymous + +namespace TextEditor { + +class CodeAssistantPrivate : public QObject +{ + Q_OBJECT + +public: + CodeAssistantPrivate(CodeAssistant *assistant); + virtual ~CodeAssistantPrivate(); + + void configure(BaseTextEditor *textEditor); + bool isConfigured() const; + + void invoke(AssistKind kind, IAssistProvider *provider = 0); + void process(); + void requestProposal(AssistReason reason, AssistKind kind, IAssistProvider *provider = 0); + void cancelCurrentRequest(); + void invalidateCurrentRequestData(); + void displayProposal(IAssistProposal *newProposal, AssistReason reason); + bool isDisplayingProposal() const; + bool isWaitingForProposal() const; + + void notifyChange(); + bool hasContext() const; + void destroyContext(); + + CompletionAssistProvider *identifyActivationSequence(); + + void stopAutomaticProposalTimer(); + void startAutomaticProposalTimer(); + + virtual bool eventFilter(QObject *o, QEvent *e); + +private slots: + void finalizeRequest(); + void proposalComputed(); + void processProposalItem(IAssistProposalItem *proposalItem); + void handlePrefixExpansion(const QString &newPrefix); + void finalizeProposal(); + void automaticProposalTimeout(); + void updateCompletionSettings(const TextEditor::CompletionSettings &settings); + +private: + CodeAssistant *m_q; + BaseTextEditor *m_textEditor; + QList<CompletionAssistProvider *> m_completionProviders; + QList<QuickFixAssistProvider *> m_quickFixProviders; + Internal::ProcessorRunner *m_requestRunner; + CompletionAssistProvider *m_requestProvider; + AssistKind m_assistKind; + IAssistProposalWidget *m_proposalWidget; + QScopedPointer<IAssistProposal> m_proposal; + bool m_proposalApplied; + bool m_receivedContentWhileWaiting; + QTimer m_automaticProposalTimer; + CompletionSettings m_settings; +}; + +} // TextEditor + +// -------------------- +// CodeAssistantPrivate +// -------------------- +CodeAssistantPrivate::CodeAssistantPrivate(CodeAssistant *assistant) + : m_q(assistant) + , m_textEditor(0) + , m_requestRunner(0) + , m_requestProvider(0) + , m_proposalWidget(0) + , m_proposalApplied(false) + , m_receivedContentWhileWaiting(false) + , m_settings(TextEditorSettings::instance()->completionSettings()) +{ + m_automaticProposalTimer.setSingleShot(true); + m_automaticProposalTimer.setInterval(250); + connect(&m_automaticProposalTimer, SIGNAL(timeout()), this, SLOT(automaticProposalTimeout())); + + connect(TextEditorSettings::instance(), + SIGNAL(completionSettingsChanged(TextEditor::CompletionSettings)), + this, + SLOT(updateCompletionSettings(TextEditor::CompletionSettings))); +} + +CodeAssistantPrivate::~CodeAssistantPrivate() +{} + +void CodeAssistantPrivate::configure(BaseTextEditor *textEditor) +{ + // @TODO: There's a list of providers but currently only the first one is used. Perhaps we + // should implement a truly mechanism to support multiple providers for an editor (either + // merging or not proposals) or just leave it as not extensible and store directly the one + // completion and quick-fix provider (getting rid of the list). + + m_textEditor = textEditor; + m_completionProviders = + ExtensionSystem::PluginManager::instance()->getObjects<CompletionAssistProvider>(); + filterEditorSpecificProviders(&m_completionProviders, m_textEditor->id()); + m_quickFixProviders = + ExtensionSystem::PluginManager::instance()->getObjects<QuickFixAssistProvider>(); + filterEditorSpecificProviders(&m_quickFixProviders, m_textEditor->id()); + + m_textEditor->editorWidget()->installEventFilter(this); +} + +bool CodeAssistantPrivate::isConfigured() const +{ + return m_textEditor != 0; +} + +void CodeAssistantPrivate::invoke(AssistKind kind, IAssistProvider *provider) +{ + if (!isConfigured()) + return; + + stopAutomaticProposalTimer(); + + if (isDisplayingProposal() && m_assistKind == kind && !m_proposal->isFragile()) { + m_proposalWidget->setReason(ExplicitlyInvoked); + } else { + destroyContext(); + requestProposal(ExplicitlyInvoked, kind, provider); + } +} + +void CodeAssistantPrivate::process() +{ + if (!isConfigured()) + return; + + stopAutomaticProposalTimer(); + + if (m_settings.m_completionTrigger != ManualCompletion) { + if (CompletionAssistProvider *provider = identifyActivationSequence()) { + if (isWaitingForProposal()) + cancelCurrentRequest(); + requestProposal(ActivationCharacter, Completion, provider); + return; + } + } + + startAutomaticProposalTimer(); +} + +void CodeAssistantPrivate::requestProposal(AssistReason reason, + AssistKind kind, + IAssistProvider *provider) +{ + Q_ASSERT(!isWaitingForProposal()); + + if (!provider) { + if (kind == Completion) { + if (!m_completionProviders.isEmpty()) + provider = m_completionProviders.at(0); + } else if (!m_quickFixProviders.isEmpty()) { + provider = m_quickFixProviders.at(0); + } + + if (!provider) + return; + } + + m_assistKind = kind; + IAssistProcessor *processor = provider->createProcessor(); + IAssistInterface *assistInterface = + m_textEditor->editorWidget()->createAssistInterface(kind, reason); + if (!assistInterface) + return; + + if (kind == Completion) { + CompletionAssistProvider *completionProvider = + static_cast<CompletionAssistProvider *>(provider); + if (completionProvider->isAsynchronous()) { + m_requestProvider = completionProvider; + m_requestRunner = new ProcessorRunner; + connect(m_requestRunner, SIGNAL(finished()), this, SLOT(proposalComputed())); + connect(m_requestRunner, SIGNAL(finished()), this, SLOT(finalizeRequest())); + assistInterface->detach(m_requestRunner); + m_requestRunner->setReason(reason); + m_requestRunner->setProcessor(processor); + m_requestRunner->setAssistInterface(assistInterface); + m_requestRunner->start(); + return; + } + } + + IAssistProposal *newProposal = processor->perform(assistInterface); + displayProposal(newProposal, reason); + delete processor; +} + +void CodeAssistantPrivate::cancelCurrentRequest() +{ + m_requestRunner->setDiscardProposal(true); + disconnect(m_requestRunner, SIGNAL(finished()), this, SLOT(proposalComputed())); + invalidateCurrentRequestData(); +} + +void CodeAssistantPrivate::proposalComputed() +{ + // Since the request runner is a different thread, there's still a gap in which the queued + // signal could be processed after an invalidation of the current request. + if (!m_requestRunner) + return; + + IAssistProposal *newProposal = m_requestRunner->proposal(); + AssistReason reason = m_requestRunner->reason(); + invalidateCurrentRequestData(); + displayProposal(newProposal, reason); +} + +void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistReason reason) +{ + if (!newProposal) + return; + + QScopedPointer<IAssistProposal> proposalCandidate(newProposal); + + if (isDisplayingProposal()) { + if (!m_proposal->isFragile() || proposalCandidate->isFragile()) + return; + destroyContext(); + } + + if (m_textEditor->position() < proposalCandidate->basePosition()) + return; + + m_proposal.reset(proposalCandidate.take()); + + if (m_proposal->isCorrective()) + m_proposal->makeCorrection(m_textEditor); + + m_proposalWidget = m_proposal->createWidget(); + connect(m_proposalWidget, SIGNAL(destroyed()), this, SLOT(finalizeProposal())); + connect(m_proposalWidget, SIGNAL(prefixExpanded(QString)), + this, SLOT(handlePrefixExpansion(QString))); + connect(m_proposalWidget, SIGNAL(proposalItemActivated(IAssistProposalItem*)), + this, SLOT(processProposalItem(IAssistProposalItem*))); + m_proposalWidget->setAssistant(m_q); + m_proposalWidget->setReason(reason); + m_proposalWidget->setUnderlyingWidget(m_textEditor->widget()); + m_proposalWidget->setModel(m_proposal->model()); + m_proposalWidget->setDisplayRect(m_textEditor->cursorRect(m_proposal->basePosition())); + if (m_receivedContentWhileWaiting) + m_proposalWidget->setIsSynchronized(false); + else + m_proposalWidget->setIsSynchronized(true); + m_proposalWidget->showProposal(m_textEditor->textAt( + m_proposal->basePosition(), + m_textEditor->position() - m_proposal->basePosition())); +} + +void CodeAssistantPrivate::processProposalItem(IAssistProposalItem *proposalItem) +{ + proposalItem->apply(m_textEditor, m_proposal->basePosition()); + destroyContext(); + process(); +} + +void CodeAssistantPrivate::handlePrefixExpansion(const QString &newPrefix) +{ + const int currentPosition = m_textEditor->position(); + m_textEditor->setCursorPosition(m_proposal->basePosition()); + m_textEditor->replace(currentPosition - m_proposal->basePosition(), newPrefix); + notifyChange(); +} + +void CodeAssistantPrivate::finalizeRequest() +{ + if (ProcessorRunner *runner = qobject_cast<ProcessorRunner *>(sender())) + delete runner; +} + +void CodeAssistantPrivate::finalizeProposal() +{ + m_proposal.reset(); + m_proposalWidget = 0; + if (m_receivedContentWhileWaiting) + m_receivedContentWhileWaiting = false; +} + +bool CodeAssistantPrivate::isDisplayingProposal() const +{ + return m_proposalWidget != 0; +} + +bool CodeAssistantPrivate::isWaitingForProposal() const +{ + return m_requestRunner != 0; +} + +void CodeAssistantPrivate::invalidateCurrentRequestData() +{ + m_requestRunner = 0; + m_requestProvider = 0; +} + +CompletionAssistProvider *CodeAssistantPrivate::identifyActivationSequence() +{ + for (int i = 0; i < m_completionProviders.size(); ++i) { + CompletionAssistProvider *provider = m_completionProviders.at(i); + const int length = provider->activationCharSequenceLength(); + if (length == 0) + continue; + const QString &sequence = m_textEditor->textAt(m_textEditor->position() - length, length); + if (provider->isActivationCharSequence(sequence)) + return provider; + } + return 0; +} + +void CodeAssistantPrivate::notifyChange() +{ + stopAutomaticProposalTimer(); + + if (isDisplayingProposal()) { + if (m_textEditor->position() < m_proposal->basePosition()) + destroyContext(); + else + m_proposalWidget->updateProposal( + m_textEditor->textAt(m_proposal->basePosition(), + m_textEditor->position() - m_proposal->basePosition())); + } +} + +bool CodeAssistantPrivate::hasContext() const +{ + return m_requestRunner || m_proposalWidget; +} + +void CodeAssistantPrivate::destroyContext() +{ + stopAutomaticProposalTimer(); + + if (isWaitingForProposal()) { + cancelCurrentRequest(); + } else if (isDisplayingProposal()) { + m_proposalWidget->closeProposal(); + disconnect(m_proposalWidget, SIGNAL(destroyed()), this, SLOT(finalizeProposal())); + finalizeProposal(); + } +} + +void CodeAssistantPrivate::startAutomaticProposalTimer() +{ + if (m_settings.m_completionTrigger == AutomaticCompletion) + m_automaticProposalTimer.start(); +} + +void CodeAssistantPrivate::automaticProposalTimeout() +{ + if (isWaitingForProposal() || (isDisplayingProposal() && !m_proposal->isFragile())) + return; + + requestProposal(IdleEditor, Completion); +} + +void CodeAssistantPrivate::stopAutomaticProposalTimer() +{ + if (m_automaticProposalTimer.isActive()) + m_automaticProposalTimer.stop(); +} + +void CodeAssistantPrivate::updateCompletionSettings(const TextEditor::CompletionSettings &settings) +{ + m_settings = settings; +} + +bool CodeAssistantPrivate::eventFilter(QObject *o, QEvent *e) +{ + Q_UNUSED(o); + + if (isWaitingForProposal()) { + QEvent::Type type = e->type(); + if (type == QEvent::FocusOut) { + destroyContext(); + } else if (type == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e); + const QString &keyText = keyEvent->text(); + if ((keyText.isEmpty() + && keyEvent->key() != Qt::LeftArrow + && keyEvent->key() != Qt::RightArrow + && keyEvent->key() != Qt::Key_Shift) + || (!keyText.isEmpty() && + !m_requestProvider->isContinuationChar(keyText.at(0)))) { + destroyContext(); + } else if (!keyText.isEmpty() && !m_receivedContentWhileWaiting) { + m_receivedContentWhileWaiting = true; + } + } + } + + return false; +} + +// ------------- +// CodeAssistant +// ------------- +CodeAssistant::CodeAssistant() : m_d(new CodeAssistantPrivate(this)) +{} + +CodeAssistant::~CodeAssistant() +{} + +void CodeAssistant::configure(BaseTextEditor *textEditor) +{ + m_d->configure(textEditor); +} + +void CodeAssistant::process() +{ + m_d->process(); +} + +void CodeAssistant::notifyChange() +{ + m_d->notifyChange(); +} + +bool CodeAssistant::hasContext() const +{ + return m_d->hasContext(); +} + +void CodeAssistant::destroyContext() +{ + m_d->destroyContext(); +} + +void CodeAssistant::invoke(AssistKind kind, IAssistProvider *provider) +{ + m_d->invoke(kind, provider); +} + +#include "codeassistant.moc" diff --git a/src/plugins/cppeditor/cppquickfixcollector.h b/src/plugins/texteditor/codeassist/codeassistant.h index 1e45088595..ec7eb00f9a 100644 --- a/src/plugins/cppeditor/cppquickfixcollector.h +++ b/src/plugins/texteditor/codeassist/codeassistant.h @@ -30,39 +30,40 @@ ** **************************************************************************/ -#ifndef QUICKFIXCOLLECTOR_H -#define QUICKFIXCOLLECTOR_H +#ifndef CODEASSISTANT_H +#define CODEASSISTANT_H -#include <texteditor/quickfix.h> +#include "assistenums.h" -namespace ExtensionSystem { -class IPlugin; -} +#include <texteditor/texteditor_global.h> + +#include <QtCore/QScopedPointer> namespace TextEditor { -class QuickFixState; -} -namespace CppEditor { -namespace Internal { +class CodeAssistantPrivate; +class IAssistProvider; +class BaseTextEditor; -class CppQuickFixCollector: public TextEditor::QuickFixCollector +class CodeAssistant { - Q_OBJECT public: - CppQuickFixCollector(); - virtual ~CppQuickFixCollector(); + CodeAssistant(); + ~CodeAssistant(); + + void configure(BaseTextEditor *textEditor); - virtual bool supportsEditor(TextEditor::ITextEditor *editor) const; - virtual TextEditor::QuickFixState *initializeCompletion(TextEditor::BaseTextEditorWidget *editor); + void process(); + void notifyChange(); + bool hasContext() const; + void destroyContext(); - virtual QList<TextEditor::QuickFixFactory *> quickFixFactories() const; + void invoke(AssistKind assistKind, IAssistProvider *provider = 0); - /// Registers all quick-fixes in this plug-in as auto-released objects. - static void registerQuickFixes(ExtensionSystem::IPlugin *plugIn); +private: + QScopedPointer<CodeAssistantPrivate> m_d; }; -} // namespace Internal -} // namespace CppEditor +} //TextEditor -#endif // QUICKFIXCOLLECTOR_H +#endif // CODEASSISTANT_H diff --git a/src/plugins/texteditor/codeassist/completionassistprovider.cpp b/src/plugins/texteditor/codeassist/completionassistprovider.cpp new file mode 100644 index 0000000000..d9d0e9ab48 --- /dev/null +++ b/src/plugins/texteditor/codeassist/completionassistprovider.cpp @@ -0,0 +1,64 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "completionassistprovider.h" + +#include <QtCore/QChar> + +using namespace TextEditor; + +CompletionAssistProvider::CompletionAssistProvider() +{} + +CompletionAssistProvider::~CompletionAssistProvider() +{} + +bool CompletionAssistProvider::isAsynchronous() const +{ + return true; +} + +int CompletionAssistProvider::activationCharSequenceLength() const +{ + return 0; +} + +bool CompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + Q_UNUSED(sequence) + return false; +} + +bool CompletionAssistProvider::isContinuationChar(const QChar &c) const +{ + return c.isLetterOrNumber() || c == QLatin1Char('_'); +} diff --git a/src/plugins/texteditor/codeassist/completionassistprovider.h b/src/plugins/texteditor/codeassist/completionassistprovider.h new file mode 100644 index 0000000000..9b51dce606 --- /dev/null +++ b/src/plugins/texteditor/codeassist/completionassistprovider.h @@ -0,0 +1,56 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef COMPLETIONASSISTPROVIDER_H +#define COMPLETIONASSISTPROVIDER_H + +#include "iassistprovider.h" + +namespace TextEditor { + +class TEXTEDITOR_EXPORT CompletionAssistProvider : public IAssistProvider +{ + Q_OBJECT + +public: + CompletionAssistProvider(); + virtual ~CompletionAssistProvider(); + + virtual bool isAsynchronous() const; + virtual int activationCharSequenceLength() const; + virtual bool isActivationCharSequence(const QString &sequence) const; + virtual bool isContinuationChar(const QChar &c) const; +}; + +} // TextEditor + +#endif // COMPLETIONASSISTPROVIDER_H diff --git a/src/plugins/texteditor/codeassist/defaultassistinterface.cpp b/src/plugins/texteditor/codeassist/defaultassistinterface.cpp new file mode 100644 index 0000000000..1bd9f46a50 --- /dev/null +++ b/src/plugins/texteditor/codeassist/defaultassistinterface.cpp @@ -0,0 +1,80 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "defaultassistinterface.h" + +#include <texteditor/convenience.h> + +#include <QtCore/QThread> +#include <QtGui/QTextDocument> +#include <QtGui/QTextCursor> + +using namespace TextEditor; + +DefaultAssistInterface::DefaultAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + AssistReason reason) + : m_document(document) + , m_detached(false) + , m_position(position) + , m_file(file) + , m_reason(reason) +{} + +DefaultAssistInterface::~DefaultAssistInterface() +{ + if (m_detached) + delete m_document; +} + +QChar DefaultAssistInterface::characterAt(int position) const +{ + return m_document->characterAt(position); +} + +QString DefaultAssistInterface::textAt(int pos, int length) const +{ + return Convenience::textAt(QTextCursor(m_document), pos, length); +} + +void DefaultAssistInterface::detach(QThread *destination) +{ + m_document = new QTextDocument(m_document->toPlainText()); + m_document->moveToThread(destination); + m_detached = true; +} + +AssistReason DefaultAssistInterface::reason() const +{ + return m_reason; +} diff --git a/src/plugins/texteditor/codeassist/defaultassistinterface.h b/src/plugins/texteditor/codeassist/defaultassistinterface.h new file mode 100644 index 0000000000..af4bfe1d60 --- /dev/null +++ b/src/plugins/texteditor/codeassist/defaultassistinterface.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef DEFAULTASSISTINTERFACE_H +#define DEFAULTASSISTINTERFACE_H + +#include "iassistinterface.h" + +namespace TextEditor { + +class TEXTEDITOR_EXPORT DefaultAssistInterface : public IAssistInterface +{ +public: + DefaultAssistInterface(QTextDocument *document, + int position, + Core::IFile *file, + AssistReason reason); + virtual ~DefaultAssistInterface(); + + virtual int position() const { return m_position; } + virtual QChar characterAt(int position) const; + virtual QString textAt(int position, int length) const; + virtual const Core::IFile *file() const { return m_file; } + virtual QTextDocument *document() const { return m_document; } + virtual void detach(QThread *destination); + virtual AssistReason reason() const; + +private: + QTextDocument *m_document; + bool m_detached; + int m_position; + Core::IFile *m_file; + AssistReason m_reason; +}; + +} // TextEditor + +#endif // DEFAULTASSISTINTERFACE_H diff --git a/src/plugins/texteditor/codeassist/functionhintproposal.cpp b/src/plugins/texteditor/codeassist/functionhintproposal.cpp new file mode 100644 index 0000000000..66e095bff5 --- /dev/null +++ b/src/plugins/texteditor/codeassist/functionhintproposal.cpp @@ -0,0 +1,73 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "functionhintproposal.h" +#include "ifunctionhintproposalmodel.h" +#include "functionhintproposalwidget.h" + +using namespace TextEditor; + +FunctionHintProposal::FunctionHintProposal(int cursorPos, IFunctionHintProposalModel *model) + : m_basePosition(cursorPos) + , m_model(model) +{} + +FunctionHintProposal::~FunctionHintProposal() +{} + +bool FunctionHintProposal::isFragile() const +{ + return true; +} + +int FunctionHintProposal::basePosition() const +{ + return m_basePosition; +} + +bool FunctionHintProposal::isCorrective() const +{ + return false; +} + +void FunctionHintProposal::makeCorrection(BaseTextEditor *) +{} + +IAssistProposalModel *FunctionHintProposal::model() const +{ + return m_model; +} + +IAssistProposalWidget *FunctionHintProposal::createWidget() const +{ + return new FunctionHintProposalWidget; +} diff --git a/src/plugins/texteditor/codeassist/functionhintproposal.h b/src/plugins/texteditor/codeassist/functionhintproposal.h new file mode 100644 index 0000000000..950ac81e05 --- /dev/null +++ b/src/plugins/texteditor/codeassist/functionhintproposal.h @@ -0,0 +1,62 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef FUNCTIONHINTPROPOSAL_H +#define FUNCTIONHINTPROPOSAL_H + +#include "iassistproposal.h" + +namespace TextEditor { + +class IFunctionHintProposalModel; + +class TEXTEDITOR_EXPORT FunctionHintProposal : public IAssistProposal +{ +public: + FunctionHintProposal(int cursorPos, IFunctionHintProposalModel *model); + virtual ~FunctionHintProposal(); + + virtual bool isFragile() const; + virtual int basePosition() const; + virtual bool isCorrective() const; + virtual void makeCorrection(BaseTextEditor *editor); + virtual IAssistProposalModel *model() const; + virtual IAssistProposalWidget *createWidget() const; + +private: + int m_basePosition; + IFunctionHintProposalModel *m_model; +}; + +} // TextEditor + +#endif // FUNCTIONHINTPROPOSAL_H diff --git a/src/plugins/texteditor/codeassist/functionhintproposalwidget.cpp b/src/plugins/texteditor/codeassist/functionhintproposalwidget.cpp new file mode 100644 index 0000000000..5dcde3de40 --- /dev/null +++ b/src/plugins/texteditor/codeassist/functionhintproposalwidget.cpp @@ -0,0 +1,314 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "functionhintproposalwidget.h" +#include "ifunctionhintproposalmodel.h" +#include "codeassistant.h" + +#include <utils/faketooltip.h> + +#include <QtCore/QDebug> +#include <QtGui/QApplication> +#include <QtGui/QLabel> +#include <QtGui/QToolButton> +#include <QtGui/QHBoxLayout> +#include <QtGui/QVBoxLayout> +#include <QtGui/QDesktopWidget> +#include <QtGui/QKeyEvent> +#include <QtGui/QShortcutEvent> + +namespace TextEditor { + +// ------------------------- +// HintProposalWidgetPrivate +// ------------------------- +struct FunctionHintProposalWidgetPrivate +{ + FunctionHintProposalWidgetPrivate(); + + const QWidget *m_underlyingWidget; + CodeAssistant *m_assistant; + IFunctionHintProposalModel *m_model; + Utils::FakeToolTip *m_popupFrame; + QLabel *m_numberLabel; + QLabel *m_hintLabel; + QWidget *m_pager; + QRect m_displayRect; + int m_currentHint; + int m_totalHints; + int m_currentArgument; + bool m_escapePressed; +}; + +FunctionHintProposalWidgetPrivate::FunctionHintProposalWidgetPrivate() + : m_underlyingWidget(0) + , m_assistant(0) + , m_model(0) + , m_popupFrame(new Utils::FakeToolTip) + , m_numberLabel(new QLabel) + , m_hintLabel(new QLabel) + , m_pager(new QWidget) + , m_currentHint(-1) + , m_totalHints(0) + , m_currentArgument(-1) + , m_escapePressed(false) +{ + m_hintLabel->setTextFormat(Qt::RichText); +} + +// ------------------ +// HintProposalWidget +// ------------------ +FunctionHintProposalWidget::FunctionHintProposalWidget() + : m_d(new FunctionHintProposalWidgetPrivate) +{ + QToolButton *downArrow = new QToolButton; + downArrow->setArrowType(Qt::DownArrow); + downArrow->setFixedSize(16, 16); + downArrow->setAutoRaise(true); + + QToolButton *upArrow = new QToolButton; + upArrow->setArrowType(Qt::UpArrow); + upArrow->setFixedSize(16, 16); + upArrow->setAutoRaise(true); + + QHBoxLayout *pagerLayout = new QHBoxLayout(m_d->m_pager); + pagerLayout->setMargin(0); + pagerLayout->setSpacing(0); + pagerLayout->addWidget(upArrow); + pagerLayout->addWidget(m_d->m_numberLabel); + pagerLayout->addWidget(downArrow); + + QHBoxLayout *popupLayout = new QHBoxLayout(m_d->m_popupFrame); + popupLayout->setMargin(0); + popupLayout->setSpacing(0); + popupLayout->addWidget(m_d->m_pager); + popupLayout->addWidget(m_d->m_hintLabel); + + connect(upArrow, SIGNAL(clicked()), SLOT(previousPage())); + connect(downArrow, SIGNAL(clicked()), SLOT(nextPage())); + + qApp->installEventFilter(this); + + setFocusPolicy(Qt::NoFocus); +} + +FunctionHintProposalWidget::~FunctionHintProposalWidget() +{ + delete m_d->m_model; +} + +void FunctionHintProposalWidget::setAssistant(CodeAssistant *assistant) +{ + m_d->m_assistant = assistant; +} + +void FunctionHintProposalWidget::setReason(AssistReason reason) +{ + Q_UNUSED(reason); +} + +void FunctionHintProposalWidget::setUnderlyingWidget(const QWidget *underlyingWidget) +{ + m_d->m_underlyingWidget = underlyingWidget; +} + +void FunctionHintProposalWidget::setModel(IAssistProposalModel *model) +{ + m_d->m_model = static_cast<IFunctionHintProposalModel *>(model); +} + +void FunctionHintProposalWidget::setDisplayRect(const QRect &rect) +{ + m_d->m_displayRect = rect; +} + +void FunctionHintProposalWidget::setIsSynchronized(bool) +{} + +void FunctionHintProposalWidget::showProposal(const QString &prefix) +{ + m_d->m_totalHints = m_d->m_model->size(); + if (m_d->m_totalHints == 0) { + abort(); + return; + } + m_d->m_pager->setVisible(m_d->m_totalHints > 1); + m_d->m_currentHint = 0; + if (!updateAndCheck(prefix)) { + abort(); + return; + } + m_d->m_popupFrame->show(); +} + +void FunctionHintProposalWidget::updateProposal(const QString &prefix) +{ + updateAndCheck(prefix); +} + +void FunctionHintProposalWidget::closeProposal() +{ + abort(); +} + +void FunctionHintProposalWidget::abort() +{ + if (m_d->m_popupFrame->isVisible()) + m_d->m_popupFrame->close(); + deleteLater(); +} + +bool FunctionHintProposalWidget::eventFilter(QObject *obj, QEvent *e) +{ + switch (e->type()) { + case QEvent::ShortcutOverride: + if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { + m_d->m_escapePressed = true; + } + break; + case QEvent::KeyPress: + if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { + m_d->m_escapePressed = true; + } + if (m_d->m_model->size() > 1) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if (ke->key() == Qt::Key_Up) { + previousPage(); + return true; + } else if (ke->key() == Qt::Key_Down) { + nextPage(); + return true; + } + return false; + } + break; + case QEvent::KeyRelease: + if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape && m_d->m_escapePressed) { + abort(); + return false; + } + m_d->m_assistant->notifyChange(); + break; + case QEvent::WindowDeactivate: + case QEvent::FocusOut: + if (obj != m_d->m_underlyingWidget) { + break; + } + abort(); + break; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::Wheel: { + QWidget *widget = qobject_cast<QWidget *>(obj); + if (! (widget == this || isAncestorOf(widget))) { + abort(); + } + } + break; + default: + break; + } + return false; +} + +void FunctionHintProposalWidget::nextPage() +{ + m_d->m_currentHint = (m_d->m_currentHint + 1) % m_d->m_totalHints; + updateContent(); +} + +void FunctionHintProposalWidget::previousPage() +{ + if (m_d->m_currentHint == 0) + m_d->m_currentHint = m_d->m_totalHints - 1; + else + --m_d->m_currentHint; + updateContent(); +} + +bool FunctionHintProposalWidget::updateAndCheck(const QString &prefix) +{ + const int activeArgument = m_d->m_model->activeArgument(prefix); + if (activeArgument == -1) { + abort(); + return false; + } else if (activeArgument != m_d->m_currentArgument) { + m_d->m_currentArgument = activeArgument; + updateContent(); + } + + return true; +} + +void FunctionHintProposalWidget::updateContent() +{ + m_d->m_hintLabel->setText(m_d->m_model->text(m_d->m_currentHint)); + m_d->m_numberLabel->setText(tr("%1 of %2").arg(m_d->m_currentHint + 1).arg(m_d->m_totalHints)); + updatePosition(); +} + +void FunctionHintProposalWidget::updatePosition() +{ + const QDesktopWidget *desktop = QApplication::desktop(); +#ifdef Q_WS_MAC + const QRect &screen = desktop->availableGeometry(desktop->screenNumber(m_d->m_underlyingWidget)); +#else + const QRect &screen = desktop->screenGeometry(desktop->screenNumber(m_d->m_underlyingWidget)); +#endif + + m_d->m_pager->setFixedWidth(m_d->m_pager->minimumSizeHint().width()); + + m_d->m_hintLabel->setWordWrap(false); + const int maxDesiredWidth = screen.width() - 10; + const QSize &minHint = m_d->m_popupFrame->minimumSizeHint(); + if (minHint.width() > maxDesiredWidth) { + m_d->m_hintLabel->setWordWrap(true); + m_d->m_popupFrame->setFixedWidth(maxDesiredWidth); + const int extra = m_d->m_popupFrame->contentsMargins().bottom() + + m_d->m_popupFrame->contentsMargins().top(); + m_d->m_popupFrame->setFixedHeight( + m_d->m_hintLabel->heightForWidth(maxDesiredWidth - m_d->m_pager->width()) + extra); + } else { + m_d->m_popupFrame->setFixedSize(minHint); + } + + const QSize &sz = m_d->m_popupFrame->size(); + QPoint pos = m_d->m_displayRect.topLeft(); + pos.setY(pos.y() - sz.height() - 1); + if (pos.x() + sz.width() > screen.right()) + pos.setX(screen.right() - sz.width()); + m_d->m_popupFrame->move(pos); +} + +} // TextEditor diff --git a/src/plugins/texteditor/codeassist/functionhintproposalwidget.h b/src/plugins/texteditor/codeassist/functionhintproposalwidget.h new file mode 100644 index 0000000000..bd60293d1b --- /dev/null +++ b/src/plugins/texteditor/codeassist/functionhintproposalwidget.h @@ -0,0 +1,82 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef FUNCTIONHINTPROPOSALWIDGET_H +#define FUNCTIONHINTPROPOSALWIDGET_H + +#include "iassistproposalwidget.h" + +#include <QtCore/QScopedPointer> + +namespace TextEditor { + +struct FunctionHintProposalWidgetPrivate; + +class TEXTEDITOR_EXPORT FunctionHintProposalWidget : public IAssistProposalWidget +{ + Q_OBJECT + +public: + FunctionHintProposalWidget(); + virtual ~FunctionHintProposalWidget(); + + virtual void setAssistant(CodeAssistant *assistant); + virtual void setReason(AssistReason reason); + virtual void setUnderlyingWidget(const QWidget *underlyingWidget); + virtual void setModel(IAssistProposalModel *model); + virtual void setDisplayRect(const QRect &rect); + virtual void setIsSynchronized(bool isSync); + + virtual void showProposal(const QString &prefix); + virtual void updateProposal(const QString &prefix); + virtual void closeProposal(); + +protected: + virtual bool eventFilter(QObject *o, QEvent *e); + +private slots: + void nextPage(); + void previousPage(); + +private: + bool updateAndCheck(const QString &prefix); + void updateContent(); + void updatePosition(); + void abort(); + +private: + QScopedPointer<FunctionHintProposalWidgetPrivate> m_d; +}; + +} // TextEditor + +#endif // FUNCTIONHINTPROPOSALWIDGET_H diff --git a/src/plugins/texteditor/codeassist/genericproposal.cpp b/src/plugins/texteditor/codeassist/genericproposal.cpp new file mode 100644 index 0000000000..f652aa408e --- /dev/null +++ b/src/plugins/texteditor/codeassist/genericproposal.cpp @@ -0,0 +1,78 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "genericproposal.h" +#include "igenericproposalmodel.h" +#include "genericproposalwidget.h" + +using namespace TextEditor; + +GenericProposal::GenericProposal(int cursorPos, IGenericProposalModel *model) + : m_basePosition(cursorPos) + , m_model(model) +{} + +GenericProposal::~GenericProposal() +{} + +bool GenericProposal::isFragile() const +{ + return false; +} + +int GenericProposal::basePosition() const +{ + return m_basePosition; +} + +bool GenericProposal::isCorrective() const +{ + return false; +} + +void GenericProposal::makeCorrection(BaseTextEditor *) +{} + +IAssistProposalModel *GenericProposal::model() const +{ + return m_model; +} + +IAssistProposalWidget *GenericProposal::createWidget() const +{ + return new GenericProposalWidget; +} + +void GenericProposal::moveBasePosition(int length) +{ + m_basePosition += length; +} diff --git a/src/plugins/texteditor/codeassist/genericproposal.h b/src/plugins/texteditor/codeassist/genericproposal.h new file mode 100644 index 0000000000..682350e36f --- /dev/null +++ b/src/plugins/texteditor/codeassist/genericproposal.h @@ -0,0 +1,65 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef GENERICPROPOSAL_H +#define GENERICPROPOSAL_H + +#include "iassistproposal.h" + +namespace TextEditor { + +class IGenericProposalModel; + +class TEXTEDITOR_EXPORT GenericProposal : public IAssistProposal +{ +public: + GenericProposal(int cursorPos, IGenericProposalModel *model); + ~GenericProposal(); + + virtual bool isFragile() const; + virtual int basePosition() const; + virtual bool isCorrective() const; + virtual void makeCorrection(BaseTextEditor *editor); + virtual IAssistProposalModel *model() const; + virtual IAssistProposalWidget *createWidget() const; + +protected: + void moveBasePosition(int length); + +private: + int m_basePosition; + IGenericProposalModel *m_model; +}; + +} // TextEditor + +#endif // GENERICPROPOSAL_H diff --git a/src/plugins/texteditor/codeassist/genericproposalwidget.cpp b/src/plugins/texteditor/codeassist/genericproposalwidget.cpp new file mode 100644 index 0000000000..96ec69c848 --- /dev/null +++ b/src/plugins/texteditor/codeassist/genericproposalwidget.cpp @@ -0,0 +1,576 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "genericproposalwidget.h" +#include "iassistprovider.h" +#include "igenericproposalmodel.h" +#include "iassistproposalitem.h" +#include "genericproposal.h" +#include "codeassistant.h" + +#include <texteditor/texteditorsettings.h> +#include <texteditor/completionsettings.h> + +#include <utils/faketooltip.h> + +#include <QtCore/QRect> +#include <QtCore/QLatin1String> +#include <QtCore/QAbstractListModel> +#include <QtCore/QPointer> +#include <QtCore/QDebug> +#include <QtCore/QTimer> +#include <QtGui/QApplication> +#include <QtGui/QVBoxLayout> +#include <QtGui/QListView> +#include <QtGui/QAbstractItemView> +#include <QtGui/QScrollBar> +#include <QtGui/QKeyEvent> +#include <QtGui/QDesktopWidget> +#include <QtGui/QLabel> + +namespace TextEditor { + +// ------------ +// ModelAdapter +// ------------ +class ModelAdapter : public QAbstractListModel +{ + Q_OBJECT + +public: + ModelAdapter(IGenericProposalModel *completionModel, QWidget *parent); + + virtual int rowCount(const QModelIndex &) const; + virtual QVariant data(const QModelIndex &index, int role) const; + +private: + IGenericProposalModel *m_completionModel; +}; + +ModelAdapter::ModelAdapter(IGenericProposalModel *completionModel, QWidget *parent) + : QAbstractListModel(parent) + , m_completionModel(completionModel) +{} + +int ModelAdapter::rowCount(const QModelIndex &) const +{ + return m_completionModel->size(); +} + +QVariant ModelAdapter::data(const QModelIndex &index, int role) const +{ + if (index.row() >= m_completionModel->size()) + return QVariant(); + + if (role == Qt::DisplayRole) { + return m_completionModel->text(index.row()); + } else if (role == Qt::DecorationRole) { + return m_completionModel->icon(index.row()); + } else if (role == Qt::WhatsThisRole) { + return m_completionModel->detail(index.row()); + } + + return QVariant(); +} + +// ------------------------ +// GenericProposalInfoFrame +// ------------------------ +class GenericProposalInfoFrame : public Utils::FakeToolTip +{ +public: + GenericProposalInfoFrame(QWidget *parent = 0) + : Utils::FakeToolTip(parent), m_label(new QLabel(this)) + { + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(m_label); + + m_label->setForegroundRole(QPalette::ToolTipText); + m_label->setBackgroundRole(QPalette::ToolTipBase); + } + + void setText(const QString &text) + { + m_label->setText(text); + } + +private: + QLabel *m_label; +}; + +// ----------------------- +// GenericProposalListView +// ----------------------- +class GenericProposalListView : public QListView +{ +public: + GenericProposalListView(QWidget *parent) : QListView(parent) {} + + QSize calculateSize() const; + QPoint infoFramePos() const; + + int rowSelected() const { return currentIndex().row(); } + bool isFirstRowSelected() const { return rowSelected() == 0; } + bool isLastRowSelected() const { return rowSelected() == model()->rowCount() - 1; } + void selectRow(int row) { setCurrentIndex(model()->index(row, 0)); } + void selectFirstRow() { selectRow(0); } + void selectLastRow() { selectRow(model()->rowCount() - 1); } +}; + +QSize GenericProposalListView::calculateSize() const +{ + static const int maxVisibleItems = 10; + + // Determine size by calculating the space of the visible items + int visibleItems = model()->rowCount(); + if (visibleItems > maxVisibleItems) + visibleItems = maxVisibleItems; + + const QStyleOptionViewItem &option = viewOptions(); + QSize shint; + for (int i = 0; i < visibleItems; ++i) { + QSize tmp = itemDelegate()->sizeHint(option, model()->index(i, 0)); + if (shint.width() < tmp.width()) + shint = tmp; + } + shint.rheight() *= visibleItems; + + return shint; +} + +QPoint GenericProposalListView::infoFramePos() const +{ + const QRect &r = rectForIndex(currentIndex()); + QPoint p((parentWidget()->mapToGlobal( + parentWidget()->rect().topRight())).x() + 3, + mapToGlobal(r.topRight()).y() - verticalOffset() + ); + return p; +} + +// ---------------------------- +// GenericProposalWidgetPrivate +// ---------------------------- +class GenericProposalWidgetPrivate : public QObject +{ + Q_OBJECT + +public: + GenericProposalWidgetPrivate(QWidget *completionWidget); + + const QWidget *m_underlyingWidget; + GenericProposalListView *m_completionListView; + IGenericProposalModel *m_model; + QRect m_displayRect; + bool m_isSynchronized; + bool m_explicitlySelected; + AssistReason m_reason; + bool m_gotContent; + QPointer<GenericProposalInfoFrame> m_infoFrame; + QTimer m_infoTimer; + CodeAssistant *m_assistant; + +public slots: + void handleActivation(const QModelIndex &modelIndex); + void maybeShowInfoTip(); +}; + +GenericProposalWidgetPrivate::GenericProposalWidgetPrivate(QWidget *completionWidget) + : m_underlyingWidget(0) + , m_completionListView(new GenericProposalListView(completionWidget)) + , m_model(0) + , m_isSynchronized(true) + , m_explicitlySelected(false) + , m_gotContent(false) + , m_assistant(0) +{ + connect(m_completionListView, SIGNAL(activated(QModelIndex)), + this, SLOT(handleActivation(QModelIndex))); + + m_infoTimer.setInterval(1000); + m_infoTimer.setSingleShot(true); + connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip())); +} + +void GenericProposalWidgetPrivate::handleActivation(const QModelIndex &modelIndex) +{ + static_cast<GenericProposalWidget *> + (m_completionListView->parent())->notifyActivation(modelIndex.row()); +} + +void GenericProposalWidgetPrivate::maybeShowInfoTip() +{ + const QModelIndex ¤t = m_completionListView->currentIndex(); + if (!current.isValid()) + return; + + const QString &infoTip = current.data(Qt::WhatsThisRole).toString(); + if (infoTip.isEmpty()) { + delete m_infoFrame.data(); + m_infoTimer.setInterval(200); + return; + } + + if (m_infoFrame.isNull()) + m_infoFrame = new GenericProposalInfoFrame(m_completionListView); + + m_infoFrame->move(m_completionListView->infoFramePos()); + m_infoFrame->setText(infoTip); + m_infoFrame->adjustSize(); + m_infoFrame->show(); + m_infoFrame->raise(); + + m_infoTimer.setInterval(0); +} + +// ------------------------ +// GenericProposalWidget +// ------------------------ +GenericProposalWidget::GenericProposalWidget() + : m_d(new GenericProposalWidgetPrivate(this)) +{ +#ifdef Q_WS_MAC + if (m_d->m_completionListView->horizontalScrollBar()) + m_d->m_completionListView->horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize); + if (m_d->m_completionListView->verticalScrollBar()) + m_d->m_completionListView->verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize); +#else + // This improves the look with QGTKStyle. + setFrameStyle(m_d->m_completionListView->frameStyle()); +#endif + m_d->m_completionListView->setFrameStyle(QFrame::NoFrame); + m_d->m_completionListView->setAttribute(Qt::WA_MacShowFocusRect, false); + m_d->m_completionListView->setUniformItemSizes(true); + m_d->m_completionListView->setSelectionBehavior(QAbstractItemView::SelectItems); + m_d->m_completionListView->setSelectionMode(QAbstractItemView::SingleSelection); + m_d->m_completionListView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_d->m_completionListView->setMinimumSize(1, 1); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->addWidget(m_d->m_completionListView); + + m_d->m_completionListView->installEventFilter(this); + + setObjectName(QLatin1String("m_popupFrame")); + setMinimumSize(1, 1); +} + +GenericProposalWidget::~GenericProposalWidget() +{ + delete m_d->m_model; +} + +void GenericProposalWidget::setAssistant(CodeAssistant *assistant) +{ + m_d->m_assistant = assistant; +} + +void GenericProposalWidget::setReason(AssistReason reason) +{ + m_d->m_reason = reason; +} + +void GenericProposalWidget::setUnderlyingWidget(const QWidget *underlyingWidget) +{ + setFont(underlyingWidget->font()); + m_d->m_underlyingWidget = underlyingWidget; +} + +void GenericProposalWidget::setModel(IAssistProposalModel *model) +{ + delete m_d->m_model; + m_d->m_model = static_cast<IGenericProposalModel *>(model); + m_d->m_completionListView->setModel(new ModelAdapter(m_d->m_model, m_d->m_completionListView)); + + connect(m_d->m_completionListView->selectionModel(), + SIGNAL(currentChanged(QModelIndex,QModelIndex)), + &m_d->m_infoTimer, + SLOT(start())); +} + +void GenericProposalWidget::setDisplayRect(const QRect &rect) +{ + m_d->m_displayRect = rect; +} + +void GenericProposalWidget::setIsSynchronized(bool isSync) +{ + m_d->m_isSynchronized = isSync; +} + +void GenericProposalWidget::showProposal(const QString &prefix) +{ + ensurePolished(); + if (m_d->m_isSynchronized && !prefix.isEmpty()) + m_d->m_gotContent = true; + m_d->m_model->removeDuplicates(); + if (!updateAndCheck(prefix)) + return; + show(); + m_d->m_completionListView->setFocus(); +} + +void GenericProposalWidget::updateProposal(const QString &prefix) +{ + if (!isVisible()) + return; + updateAndCheck(prefix); +} + +void GenericProposalWidget::closeProposal() +{ + abort(); +} + +void GenericProposalWidget::notifyActivation(int index) +{ + abort(); + emit proposalItemActivated(m_d->m_model->proposalItem(index)); +} + +void GenericProposalWidget::abort() +{ + if (isVisible()) + close(); + deleteLater(); +} + +bool GenericProposalWidget::updateAndCheck(const QString &prefix) +{ + // Keep track in the case there has been an explicit selection. + int preferredItemId = -1; + if (m_d->m_explicitlySelected) + preferredItemId = + m_d->m_model->persistentId(m_d->m_completionListView->currentIndex().row()); + + // Filter, sort, etc. + m_d->m_model->reset(); + if (!prefix.isEmpty()) + m_d->m_model->filter(prefix); + if (m_d->m_model->size() == 0 + || (m_d->m_model->size() == 1 && prefix == m_d->m_model->proposalPrefix())) { + abort(); + return false; + } + if (m_d->m_model->isSortable()) + m_d->m_model->sort(); + m_d->m_completionListView->reset(); + + // Try to find the previosly explicit selection (if any). If we can find the item set it + // as the current. Otherwise (it might have been filtered out) select the first row. + if (m_d->m_explicitlySelected) { + Q_ASSERT(preferredItemId != -1); + for (int i = 0; i < m_d->m_model->size(); ++i) { + if (m_d->m_model->persistentId(i) == preferredItemId) { + m_d->m_completionListView->selectRow(i); + break; + } + } + } + if (!m_d->m_completionListView->currentIndex().isValid()) { + m_d->m_completionListView->selectFirstRow(); + if (m_d->m_explicitlySelected) + m_d->m_explicitlySelected = false; + } + + if (TextEditorSettings::instance()->completionSettings().m_partiallyComplete + && m_d->m_reason == ExplicitlyInvoked + && m_d->m_gotContent) { + if (m_d->m_model->size() == 1) { + IAssistProposalItem *item = m_d->m_model->proposalItem(0); + if (item->implicitlyApplies()) { + abort(); + emit proposalItemActivated(item); + return false; + } + } + if (m_d->m_model->supportsPrefixExpansion()) { + const QString &proposalPrefix = m_d->m_model->proposalPrefix(); + if (proposalPrefix.length() > prefix.length()) + emit prefixExpanded(proposalPrefix); + } + } + + updatePositionAndSize(); + return true; +} + +void GenericProposalWidget::updatePositionAndSize() +{ + const QSize &shint = m_d->m_completionListView->calculateSize(); + const int fw = frameWidth(); + const int width = shint.width() + fw * 2 + 30; + const int height = shint.height() + fw * 2; + + // Determine the position, keeping the popup on the screen + const QDesktopWidget *desktop = QApplication::desktop(); +#ifdef Q_WS_MAC + const QRect screen = desktop->availableGeometry(desktop->screenNumber(m_d->m_underlyingWidget)); +#else + const QRect screen = desktop->screenGeometry(desktop->screenNumber(m_d->m_underlyingWidget)); +#endif + + QPoint pos = m_d->m_displayRect.bottomLeft(); + pos.rx() -= 16 + fw; // Space for the icons + if (pos.y() + height > screen.bottom()) + pos.setY(m_d->m_displayRect.top() - height); + if (pos.x() + width > screen.right()) + pos.setX(screen.right() - width); + setGeometry(pos.x(), pos.y(), width, height); +} + +bool GenericProposalWidget::eventFilter(QObject *o, QEvent *e) +{ + if (e->type() == QEvent::FocusOut) { + abort(); +#if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA) + QFocusEvent *fe = static_cast<QFocusEvent *>(e); + if (fe->reason() == Qt::OtherFocusReason) { + // Qt/carbon workaround + // focus out is received before the key press event. + if (m_d->m_completionListView->currentIndex().isValid()) + emit proposalItemActivated(m_d->m_model->proposalItem( + m_d->m_completionListView->currentIndex().row())); + } +#endif + if (m_d->m_infoFrame) + m_d->m_infoFrame->close(); + return true; + } else if (e->type() == QEvent::ShortcutOverride) { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + switch (ke->key()) { + case Qt::Key_N: + case Qt::Key_P: + if (ke->modifiers() == Qt::ControlModifier) { + e->accept(); + return true; + } + } + } else if (e->type() == QEvent::KeyPress) { + m_d->m_gotContent = false; + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + switch (ke->key()) { + case Qt::Key_Escape: + abort(); + return true; + + case Qt::Key_N: + case Qt::Key_P: + // select next/previous completion + m_d->m_explicitlySelected = true; + if (ke->modifiers() == Qt::ControlModifier) { + int change = (ke->key() == Qt::Key_N) ? 1 : -1; + int nrows = m_d->m_model->size(); + int row = m_d->m_completionListView->currentIndex().row(); + int newRow = (row + change + nrows) % nrows; + if (newRow == row + change || !ke->isAutoRepeat()) + m_d->m_completionListView->selectRow(newRow); + return true; + } + m_d->m_gotContent = true; + break; + + case Qt::Key_Tab: + case Qt::Key_Return: +#if defined(QT_MAC_USE_COCOA) || !defined(Q_OS_DARWIN) + abort(); + if (m_d->m_completionListView->currentIndex().isValid()) + emit proposalItemActivated(m_d->m_model->proposalItem( + m_d->m_completionListView->currentIndex().row())); +#endif + return true; + + case Qt::Key_Up: + m_d->m_explicitlySelected = true; + if (!ke->isAutoRepeat() && m_d->m_completionListView->isFirstRowSelected()) { + m_d->m_completionListView->selectLastRow(); + return true; + } + return false; + + case Qt::Key_Down: + m_d->m_explicitlySelected = true; + if (!ke->isAutoRepeat() && m_d->m_completionListView->isLastRowSelected()) { + m_d->m_completionListView->selectFirstRow(); + return true; + } + return false; + + case Qt::Key_Enter: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + return false; + + case Qt::Key_Right: + case Qt::Key_Left: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Backspace: + // We want these navigation keys to work in the editor. + break; + + default: + // Only forward keys that insert text and refine the completion. + if (ke->text().isEmpty()) + return true; + m_d->m_gotContent = true; + break; + } + + if (ke->text().length() == 1 + && m_d->m_completionListView->currentIndex().isValid() + && qApp->focusWidget() == o) { + const QChar &typedChar = ke->text().at(0); + IAssistProposalItem *item = + m_d->m_model->proposalItem(m_d->m_completionListView->currentIndex().row()); + if (item->prematurelyApplies(typedChar)) { + abort(); + emit proposalItemActivated(item); + return true; + } + } + + QApplication::sendEvent(const_cast<QWidget *>(m_d->m_underlyingWidget), e); + if (isVisible()) + m_d->m_assistant->notifyChange(); + + return true; + } + return false; +} + +#include "genericproposalwidget.moc" + +} // TextEditor diff --git a/src/plugins/texteditor/codeassist/genericproposalwidget.h b/src/plugins/texteditor/codeassist/genericproposalwidget.h new file mode 100644 index 0000000000..a765d413dc --- /dev/null +++ b/src/plugins/texteditor/codeassist/genericproposalwidget.h @@ -0,0 +1,79 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef GENERICPROPOSALWIDGET_H +#define GENERICPROPOSALWIDGET_H + +#include "iassistproposalwidget.h" + +#include <QtCore/QScopedPointer> + +namespace TextEditor { + +class GenericProposalWidgetPrivate; + +class GenericProposalWidget : public IAssistProposalWidget +{ + friend class GenericProposalWidgetPrivate; + Q_OBJECT + +public: + GenericProposalWidget(); + virtual ~GenericProposalWidget(); + + virtual void setAssistant(CodeAssistant *assistant); + virtual void setReason(AssistReason reason); + virtual void setUnderlyingWidget(const QWidget *underlyingWidget); + virtual void setModel(IAssistProposalModel *model); + virtual void setDisplayRect(const QRect &rect); + virtual void setIsSynchronized(bool isSync); + + virtual void showProposal(const QString &prefix); + virtual void updateProposal(const QString &prefix); + virtual void closeProposal(); + +private: + bool updateAndCheck(const QString &prefix); + void updatePositionAndSize(); + void notifyActivation(int index); + void abort(); + +protected: + virtual bool eventFilter(QObject *o, QEvent *e); + +private: + QScopedPointer<GenericProposalWidgetPrivate> m_d; +}; + +} // TextEditor + +#endif // GENERICPROPOSALWIDGET_H diff --git a/src/plugins/texteditor/codeassist/iassistinterface.cpp b/src/plugins/texteditor/codeassist/iassistinterface.cpp new file mode 100644 index 0000000000..0fa73257ca --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistinterface.cpp @@ -0,0 +1,100 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistinterface.h" + +using namespace TextEditor; + +/*! + \class IAssistInterface + \brief The IAssistInterface is an interface for providing access to the document from which + a proposal is computed. + + This interface existis in order to avoid a direct dependency on the text editor. This is + particularly important and safer for asynchronous providers, since in such cases computation + of the proposal is not done in the GUI thread. + + In general this API tries to be as decoupled as possible from the base text editor. + This is in order to make the design a bit more generic and allow code assist to be + pluggable into different types of documents (there are still issues to be treated). + + \sa IAssistProposal, IAssistProvider, IAssistProcessor +*/ + +IAssistInterface::IAssistInterface() +{} + +IAssistInterface::~IAssistInterface() +{} + +/*! + \fn int position() const + + Returns the cursor position. +*/ + +/*! + \fn QChar characterAt(int position) const + + Returns the character at \a position. +*/ + +/*! + \fn QString textAt(int position, int length) const + + Returns the text at \a position with the given \a length. +*/ + +/*! + \fn const Core::IFile *file() const + + Returns the file associated. +*/ + +/*! + \fn QTextDocument *document() const + Returns the document. +*/ + +/*! + \fn void detach(QThread *destination) + + Detaches the interface. If it is necessary to take any special care in order to allow + this interface to be run in a separate thread \a destination this needs to be done + in this method. +*/ + +/*! + \fn AssistReason reason() const + + The reason which triggered the assist. +*/ diff --git a/src/plugins/texteditor/codeassist/iassistinterface.h b/src/plugins/texteditor/codeassist/iassistinterface.h new file mode 100644 index 0000000000..49ab63605b --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistinterface.h @@ -0,0 +1,70 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTINTERFACE_H +#define IASSISTINTERFACE_H + +#include "assistenums.h" + +#include <texteditor/texteditor_global.h> + +#include <QtCore/QString> + +QT_BEGIN_NAMESPACE +class QTextDocument; +class QThread; +QT_END_NAMESPACE + +namespace Core { +class IFile; +} + +namespace TextEditor { + +class TEXTEDITOR_EXPORT IAssistInterface +{ +public: + IAssistInterface(); + virtual ~IAssistInterface(); + + virtual int position() const = 0; + virtual QChar characterAt(int position) const = 0; + virtual QString textAt(int position, int length) const = 0; + virtual const Core::IFile *file() const = 0; + virtual QTextDocument *document() const = 0; + virtual void detach(QThread *destination) = 0; + virtual AssistReason reason() const = 0; +}; + +} // TextEditor + +#endif // IASSISTINTERFACE_H diff --git a/src/plugins/texteditor/codeassist/iassistprocessor.cpp b/src/plugins/texteditor/codeassist/iassistprocessor.cpp new file mode 100644 index 0000000000..fff1c7ff7a --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistprocessor.cpp @@ -0,0 +1,61 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistprocessor.h" + +using namespace TextEditor; + +/*! + \class IAssistProcessor + \brief The IAssistProcessor is an interface that actually computes an assist proposal. + + \sa IAssistProposal, IAssistProvider +*/ + +IAssistProcessor::IAssistProcessor() +{} + +IAssistProcessor::~IAssistProcessor() +{} + +/*! + \fn IAssistProposal *perform(const IAssistInterface *interface) + + Computes a proposal and returns it. Access to the document is made through the \a interface. + If this is an asynchronous processor the \a interface will be detached. + + The processor takes ownership of the interface. Also, one should be careful in the case of + sharing data across asynchronous processors since there might be more than one instance of + them computing a proposal at a particular time. + + \sa IAssistInterface::detach() +*/ diff --git a/src/plugins/texteditor/codeassist/iassistprocessor.h b/src/plugins/texteditor/codeassist/iassistprocessor.h new file mode 100644 index 0000000000..43cbb4e7ff --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistprocessor.h @@ -0,0 +1,57 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTPROCESSOR_H +#define IASSISTPROCESSOR_H + +#include "iassistproposalwidget.h" + +#include <texteditor/texteditor_global.h> + +namespace TextEditor { + +class IAssistProvider; +class IAssistInterface; +class IAssistProposal; + +class TEXTEDITOR_EXPORT IAssistProcessor +{ +public: + IAssistProcessor(); + virtual ~IAssistProcessor(); + + virtual IAssistProposal *perform(const IAssistInterface *interface) = 0; +}; + +} // TextEditor + +#endif // IASSISTPROCESSOR_H diff --git a/src/plugins/texteditor/codeassist/iassistproposal.cpp b/src/plugins/texteditor/codeassist/iassistproposal.cpp new file mode 100644 index 0000000000..ba7a2fd514 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposal.cpp @@ -0,0 +1,102 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistproposal.h" + +using namespace TextEditor; + +/*! + \class IAssistProposal + \brief The IAssistProposal is an interface for representing an assist proposal. + + Known implenters of this interface are FunctionHintProposal and GenericProposal. The + former is recommended to be used when assisting function call constructs (overloads + and parameters) while the latter is quite generic so that it could be used to propose + snippets, refactoring operations (quickfixes), and contextual content (the member of + class or a string existent in the document, for example). + + \sa IAssistProposalWidget, IAssistModel +*/ + +IAssistProposal::IAssistProposal() +{} + +IAssistProposal::~IAssistProposal() +{} + +/*! + \fn bool isFragile() const + + Returns whether this is a fragile proposal. When a proposal is fragile it means that + it will be replaced by a new proposal in the case one is created, even if due to an + idle editor. +*/ + +/*! + \fn int basePosition() const + + Returns the position from which this proposal starts. +*/ + +/*! + \fn bool isCorrective() const + + Returns whether this proposal is also corrective. This could happen in C++, for example, + when a dot operator (.) needs to be replaced by an arrow operator (->) before the proposal + is displayed. +*/ + +/*! + \fn void makeCorrection(BaseTextEditor *editor) + + This allows a correction to be made in the case this is a corrective proposal. +*/ + +/*! + \fn IAssistModel *model() const + + Returns the model associated with this proposal. + + Although the IAssistModel from this proposal may be used on its own, it needs to be + consistent with the widget returned by createWidget(). + + \sa createWidget() +*/ + +/*! + \fn IAssistProposalWidget *createWidget() const + + Returns the widget associated with this proposal. The IAssistProposalWidget implementor + recommended for function hint proposals is FunctionHintProposalWidget. For snippets, + refactoring operations (quickfixes), and contextual content the recommeded implementor + is GenericProposalWidget. +*/ diff --git a/src/plugins/texteditor/codeassist/iassistproposal.h b/src/plugins/texteditor/codeassist/iassistproposal.h new file mode 100644 index 0000000000..efacbc4abb --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposal.h @@ -0,0 +1,60 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTPROPOSAL_H +#define IASSISTPROPOSAL_H + +#include <texteditor/texteditor_global.h> + +namespace TextEditor { + +class IAssistProposalModel; +class IAssistProposalWidget; +class BaseTextEditor; + +class TEXTEDITOR_EXPORT IAssistProposal +{ +public: + IAssistProposal(); + virtual ~IAssistProposal(); + + virtual bool isFragile() const = 0; + virtual int basePosition() const = 0; + virtual bool isCorrective() const = 0; + virtual void makeCorrection(BaseTextEditor *editor) = 0; + virtual IAssistProposalModel *model() const = 0; + virtual IAssistProposalWidget *createWidget() const = 0; +}; + +} // TextEditor + +#endif // IASSISTPROPOSAL_H diff --git a/src/plugins/texteditor/codeassist/iassistproposalitem.cpp b/src/plugins/texteditor/codeassist/iassistproposalitem.cpp new file mode 100644 index 0000000000..33d09d4c01 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalitem.cpp @@ -0,0 +1,65 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistproposalitem.h" + +using namespace TextEditor; + +/*! + \class IAssistProposalItem + \brief The IAssistProposalItem is an interface for representing an assist proposal item. +*/ + +IAssistProposalItem::IAssistProposalItem() +{} + +IAssistProposalItem::~IAssistProposalItem() +{} + +/*! + \fn bool implicitlyApplies() const + + Returns whether this item should implicitly apply in the case it is the only proposal + item available. +*/ + +/*! + \fn bool prematurelyApplies(const QChar &c) const + + Returns whether the character \a c causes this item to be applied. +*/ + +/*! + \fn void apply(BaseTextEditor *editor, int basePosition) const + + This is the place to implement the actual application of the item. +*/ diff --git a/src/plugins/texteditor/codeassist/iassistproposalitem.h b/src/plugins/texteditor/codeassist/iassistproposalitem.h new file mode 100644 index 0000000000..319a934463 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalitem.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTPROPOSALITEM_H +#define IASSISTPROPOSALITEM_H + +#include <texteditor/texteditor_global.h> + +QT_BEGIN_NAMESPACE +class QChar; +QT_END_NAMESPACE + +namespace TextEditor { + +class BaseTextEditor; + +class TEXTEDITOR_EXPORT IAssistProposalItem +{ +public: + IAssistProposalItem(); + virtual ~IAssistProposalItem(); + + virtual bool implicitlyApplies() const = 0; + virtual bool prematurelyApplies(const QChar &c) const = 0; + virtual void apply(BaseTextEditor *editor, int basePosition) const = 0; +}; + +} // TextEditor + +#endif // IASSISTPROPOSALITEM_H diff --git a/src/plugins/texteditor/codeassist/iassistproposalmodel.cpp b/src/plugins/texteditor/codeassist/iassistproposalmodel.cpp new file mode 100644 index 0000000000..6222d5695e --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalmodel.cpp @@ -0,0 +1,52 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistproposalmodel.h" + +using namespace TextEditor; + +/*! + \class IAssistProposalModel + \brief The IAssistProposalModel is an interface for representing proposals. + + Known implenters of this interface are IFunctionHintProposalModel and IGenericProposalModel. + The former is recommeded to be used when assisting function calls constructs (overloads + and parameters) while the latter is quite generic so that it could be used to propose + snippets, refactoring operations (quickfixes), and contextual content (the member of class + or a string existent in the document, for example). +*/ + +IAssistProposalModel::IAssistProposalModel() +{} + +IAssistProposalModel::~IAssistProposalModel() +{} diff --git a/src/plugins/texteditor/codeassist/iassistproposalmodel.h b/src/plugins/texteditor/codeassist/iassistproposalmodel.h new file mode 100644 index 0000000000..e017f8da97 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalmodel.h @@ -0,0 +1,57 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTMODEL_H +#define IASSISTMODEL_H + +#include <texteditor/texteditor_global.h> + +#include <QtCore/QString> + +namespace TextEditor { + +class IAssistProposalItem; + +class TEXTEDITOR_EXPORT IAssistProposalModel +{ +public: + IAssistProposalModel(); + virtual ~IAssistProposalModel(); + + virtual void reset() = 0; + virtual int size() const = 0; + virtual QString text(int index) const = 0; +}; + +} // TextEditor + +#endif // IASSISTMODEL_H diff --git a/src/plugins/texteditor/codeassist/iassistproposalwidget.cpp b/src/plugins/texteditor/codeassist/iassistproposalwidget.cpp new file mode 100644 index 0000000000..9c7cfef193 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalwidget.cpp @@ -0,0 +1,142 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistproposalwidget.h" + +using namespace TextEditor; + +/*! + \class IAssistProposalWidget + \brief The IAssistProposalWidget is an interface for widgets that display assist proposals. + + Known implenters of this interface are FunctionHintProposalWidget and GenericProposalWidget. + The former is recommeded to be used when assisting function calls constructs (overloads + and parameters) while the latter is quite generic so that it could be used to propose + snippets, refactoring operations (quickfixes), and contextual content (the member of class + or a string existent in the document, for example). + + In general this API tries to be as decoupled as possible from the base text editor. + This is in order to make the design a bit more generic and allow code assist to be + pluggable into different types of documents (there are still issues to be treated). + + \sa IAssistProposal +*/ + +IAssistProposalWidget::IAssistProposalWidget() + : QFrame(0, Qt::Popup) +{} + +IAssistProposalWidget::~IAssistProposalWidget() +{} + +/*! + \fn void setAssistant(CodeAssistant *assistant) + + Sets the code assistant which is the owner of this widget. This is used so that the code + assistant can be notified when changes on the underlying widget happen. +*/ + +/*! + \fn void setReason(AssistReason reason) + + Sets the reason which triggered the assist. +*/ + +/*! + \fn void setUnderlyingWidget(const QWidget *underlyingWidget) + + Sets the underlying widget upon which this proposal operates. +*/ + +/*! + \fn void setModel(IAssistModel *model) + + Sets the model. +*/ + +/*! + \fn void setDisplayRect(const QRect &rect) + + Sets the \a rect on which this widget should be displayed. +*/ + +/*! + \fn void showProposal(const QString &prefix) + + Shows the proposal. The \a prefix is the string comprised from the character at the base + position of the proposal until the character immediately after the cursor at the moment + the proposal is displayed. + + \sa IAssistProposal::basePosition() +*/ + +/*! + \fn virtual void updateProposal(const QString &prefix) + + Updates the proposal base on the give \a prefix. + + \sa showProposal() +*/ + +/*! + \fn void closeProposal() + + Closes the proposal. +*/ + +/*! + \fn void setIsSynchronized(bool isSync) + + Sets whether this widget is synchronized. If a widget is synchronized it means that from + the moment a proposal started being computed until the moment it is actually displayed, + there was no content input into the underlying widget. + + A widget is not synchronized in the case a proposal is computed in a separate thread and + in the meanwhile (while it is still being processed) content is input into the underlying + widget. +*/ + +/*! + \fn void prefixExpanded(const QString &newPrefix) + + The signal is emitted whenever this widget automatically expands the prefix of the proposal. + This can happen if all available proposal items share the same prefix and if the proposal's + model supports prefix expansion. + + \sa IGenericProposalModel::supportsPrefixExpansion() +*/ + +/*! + void proposalItemActivated(IAssistProposalItem *proposalItem) + + This signal is emitted whenever \a proposalItem is chosen to be applied. +*/ diff --git a/src/plugins/texteditor/codeassist/iassistproposalwidget.h b/src/plugins/texteditor/codeassist/iassistproposalwidget.h new file mode 100644 index 0000000000..1a9d866363 --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistproposalwidget.h @@ -0,0 +1,74 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IASSISTPROPOSALWIDGET_H +#define IASSISTPROPOSALWIDGET_H + +#include "assistenums.h" + +#include <texteditor/texteditor_global.h> + +#include <QtGui/QFrame> + +namespace TextEditor { + +class CodeAssistant; +class IAssistProposalModel; +class IAssistProposalItem; + +class TEXTEDITOR_EXPORT IAssistProposalWidget : public QFrame +{ + Q_OBJECT + +public: + IAssistProposalWidget(); + virtual ~IAssistProposalWidget(); + + virtual void setAssistant(CodeAssistant *assistant) = 0; + virtual void setReason(AssistReason reason) = 0; + virtual void setUnderlyingWidget(const QWidget *underlyingWidget) = 0; + virtual void setModel(IAssistProposalModel *model) = 0; + virtual void setDisplayRect(const QRect &rect) = 0; + virtual void setIsSynchronized(bool isSync) = 0; + + virtual void showProposal(const QString &prefix) = 0; + virtual void updateProposal(const QString &prefix) = 0; + virtual void closeProposal() = 0; + +signals: + void prefixExpanded(const QString &newPrefix); + void proposalItemActivated(IAssistProposalItem *proposalItem); +}; + +} // TextEditor + +#endif // IASSISTPROPOSALWIDGET_H diff --git a/src/plugins/texteditor/codeassist/iassistprovider.cpp b/src/plugins/texteditor/codeassist/iassistprovider.cpp new file mode 100644 index 0000000000..4e4cc8dccd --- /dev/null +++ b/src/plugins/texteditor/codeassist/iassistprovider.cpp @@ -0,0 +1,68 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "iassistprovider.h" + +using namespace TextEditor; + +/*! + \class IAssistProvider + \brief The IAssistProvider is an interface for providing code assist. + + There might be different kinds of assist such as completions or refactoring + actions (quickfixes). + + Within this API the term completion denotes any kind of information prompted + to the user in order to auxiliate her to "complete" a particular code construction. + Examples of completions currently supported are snippets, function hints, and + contextual contents. + + \sa IAssistProposal, IAssistProcessor +*/ + +IAssistProvider::IAssistProvider() +{} + +IAssistProvider::~IAssistProvider() +{} + +/*! + \fn bool supportsEditor(const QString &editorId) const + + Returns whether this provider supports the editor which has the give \a editorId. +*/ + +/*! + \fn IAssistProcessor *createProcessor() const + + Creates and returns the IAssistProcessor responsible for computing an IAssistProposal. +*/ diff --git a/src/plugins/texteditor/completionsupport.h b/src/plugins/texteditor/codeassist/iassistprovider.h index c4787ba04e..88d04c6486 100644 --- a/src/plugins/texteditor/completionsupport.h +++ b/src/plugins/texteditor/codeassist/iassistprovider.h @@ -30,44 +30,29 @@ ** **************************************************************************/ -#ifndef COMPLETIONSUPPORT_H -#define COMPLETIONSUPPORT_H +#ifndef IASSISTPROVIDER_H +#define IASSISTPROVIDER_H #include <texteditor/texteditor_global.h> -#include <texteditor/icompletioncollector.h> #include <QtCore/QObject> namespace TextEditor { -class ITextEditor; -class CompletionSupportPrivate; +class IAssistProcessor; -/* Completion support is responsible for querying the list of completion collectors - and popping up the CompletionWidget with the available completions. - */ -class TEXTEDITOR_EXPORT CompletionSupport : public QObject +class TEXTEDITOR_EXPORT IAssistProvider : public QObject { Q_OBJECT public: - virtual ~CompletionSupport(); + IAssistProvider(); + virtual ~IAssistProvider(); - static CompletionSupport *instance(); - - bool isActive() const; - CompletionPolicy policy() const; - -public slots: - void complete(TextEditor::ITextEditor *editor, - TextEditor::CompletionPolicy policy, bool forced); - -private: - CompletionSupport(); - QScopedPointer<CompletionSupportPrivate> d; + virtual bool supportsEditor(const QString &editorId) const = 0; + virtual IAssistProcessor *createProcessor() const = 0; }; -} // namespace TextEditor - -#endif // COMPLETIONSUPPORT_H +} // TextEditor +#endif // IASSISTPROVIDER_H diff --git a/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.cpp b/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.cpp new file mode 100644 index 0000000000..1f0a9ab2d7 --- /dev/null +++ b/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.cpp @@ -0,0 +1,41 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "ifunctionhintproposalmodel.h" + +using namespace TextEditor; + +IFunctionHintProposalModel::IFunctionHintProposalModel() +{} + +IFunctionHintProposalModel::~IFunctionHintProposalModel() +{} diff --git a/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.h b/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.h new file mode 100644 index 0000000000..b56fadf0d1 --- /dev/null +++ b/src/plugins/texteditor/codeassist/ifunctionhintproposalmodel.h @@ -0,0 +1,53 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IFUNCTIONHINTPROPOSALMODEL_H +#define IFUNCTIONHINTPROPOSALMODEL_H + +#include "iassistproposalmodel.h" + +#include <texteditor/texteditor_global.h> + +namespace TextEditor { + +class TEXTEDITOR_EXPORT IFunctionHintProposalModel : public IAssistProposalModel +{ +public: + IFunctionHintProposalModel(); + virtual ~IFunctionHintProposalModel(); + + virtual int activeArgument(const QString &prefix) const = 0; +}; + +} // TextEditor + +#endif // IFUNCTIONHINTPROPOSALMODEL_H diff --git a/src/plugins/texteditor/codeassist/igenericproposalmodel.cpp b/src/plugins/texteditor/codeassist/igenericproposalmodel.cpp new file mode 100644 index 0000000000..5bf0bfcf08 --- /dev/null +++ b/src/plugins/texteditor/codeassist/igenericproposalmodel.cpp @@ -0,0 +1,41 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "igenericproposalmodel.h" + +using namespace TextEditor; + +IGenericProposalModel::IGenericProposalModel() +{} + +IGenericProposalModel::~IGenericProposalModel() +{} diff --git a/src/plugins/texteditor/codeassist/igenericproposalmodel.h b/src/plugins/texteditor/codeassist/igenericproposalmodel.h new file mode 100644 index 0000000000..a6f6699728 --- /dev/null +++ b/src/plugins/texteditor/codeassist/igenericproposalmodel.h @@ -0,0 +1,66 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef IGENERICPROPOSALMODEL_H +#define IGENERICPROPOSALMODEL_H + +#include "iassistproposalmodel.h" + +#include <texteditor/texteditor_global.h> + +#include <QtGui/QIcon> + +namespace TextEditor { + +class IAssistProposalItem; + +class TEXTEDITOR_EXPORT IGenericProposalModel : public IAssistProposalModel +{ +public: + IGenericProposalModel(); + virtual ~IGenericProposalModel(); + + virtual QIcon icon(int index) const = 0; + virtual QString detail(int index) const = 0; + virtual int persistentId(int index) const = 0; + virtual void removeDuplicates() = 0; + virtual void filter(const QString &prefix) = 0; + virtual bool isSortable() const = 0; + virtual void sort() = 0; + virtual bool supportsPrefixExpansion() const = 0; + virtual QString proposalPrefix() const = 0; + virtual IAssistProposalItem *proposalItem(int index) const = 0; +}; + +} // TextEditor + +#endif // IGENERICPROPOSALMODEL_H diff --git a/src/plugins/texteditor/codeassist/quickfixassistprocessor.cpp b/src/plugins/texteditor/codeassist/quickfixassistprocessor.cpp new file mode 100644 index 0000000000..83618d243a --- /dev/null +++ b/src/plugins/texteditor/codeassist/quickfixassistprocessor.cpp @@ -0,0 +1,96 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "quickfixassistprocessor.h" +#include "quickfixassistprovider.h" +#include "iassistinterface.h" +#include "basicproposalitemlistmodel.h" +#include "basicproposalitem.h" +#include "genericproposal.h" + +// @TODO: Move... +#include <texteditor/quickfix.h> + +#include <QtCore/QMap> +#include <QtCore/QDebug> + +using namespace TextEditor; + +QuickFixAssistProcessor::QuickFixAssistProcessor() +{} + +QuickFixAssistProcessor::~QuickFixAssistProcessor() +{} + +IAssistProposal *QuickFixAssistProcessor::perform(const IAssistInterface *interface) +{ + if (!interface) + return 0; + + QSharedPointer<const IAssistInterface> assistInterface(interface); + + const QuickFixAssistProvider *quickFixProvider = + static_cast<const QuickFixAssistProvider *>(provider()); + QMap<int, QList<QuickFixOperation::Ptr> > matchedOps; + foreach (QuickFixFactory *factory, quickFixProvider->quickFixFactories()) { + QList<QuickFixOperation::Ptr> ops = factory->matchingOperations(assistInterface); + + foreach (QuickFixOperation::Ptr op, ops) { + const int priority = op->priority(); + if (priority != -1) + matchedOps[priority].append(op); + } + } + + QList<QuickFixOperation::Ptr> quickFixes; + QMapIterator<int, QList<QuickFixOperation::Ptr> > it(matchedOps); + it.toBack(); + if (it.hasPrevious()) { + it.previous(); + quickFixes = it.value(); + } + + if (!quickFixes.isEmpty()) { + QList<BasicProposalItem *> items; + foreach (const QuickFixOperation::Ptr &op, quickFixes) { + QVariant v; + v.setValue(op); + BasicProposalItem *item = new BasicProposalItem; + item->setText(op->description()); + item->setData(v); + items.append(item); + } + return new GenericProposal(interface->position(), new BasicProposalItemListModel(items)); + } + + return 0; +} diff --git a/src/plugins/texteditor/codeassist/quickfixassistprocessor.h b/src/plugins/texteditor/codeassist/quickfixassistprocessor.h new file mode 100644 index 0000000000..c855907d86 --- /dev/null +++ b/src/plugins/texteditor/codeassist/quickfixassistprocessor.h @@ -0,0 +1,52 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QUICKFIXASSISTPROCESSOR_H +#define QUICKFIXASSISTPROCESSOR_H + +#include "iassistprocessor.h" + +namespace TextEditor { + +class TEXTEDITOR_EXPORT QuickFixAssistProcessor : public IAssistProcessor +{ +public: + QuickFixAssistProcessor(); + virtual ~QuickFixAssistProcessor(); + + virtual const IAssistProvider *provider() const = 0; + virtual IAssistProposal *perform(const IAssistInterface *interface); +}; + +} // TextEditor + +#endif // QUICKFIXASSISTPROCESSOR_H diff --git a/src/plugins/texteditor/codeassist/quickfixassistprovider.cpp b/src/plugins/texteditor/codeassist/quickfixassistprovider.cpp new file mode 100644 index 0000000000..d87b51e882 --- /dev/null +++ b/src/plugins/texteditor/codeassist/quickfixassistprovider.cpp @@ -0,0 +1,46 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "quickfixassistprovider.h" + +using namespace TextEditor; + +QuickFixAssistProvider::QuickFixAssistProvider() +{} + +QuickFixAssistProvider::~QuickFixAssistProvider() +{} + +QList<QuickFixFactory *> QuickFixAssistProvider::quickFixFactories() const +{ + return QList<QuickFixFactory *>(); +} diff --git a/src/plugins/texteditor/codeassist/quickfixassistprovider.h b/src/plugins/texteditor/codeassist/quickfixassistprovider.h new file mode 100644 index 0000000000..3e16fc6b19 --- /dev/null +++ b/src/plugins/texteditor/codeassist/quickfixassistprovider.h @@ -0,0 +1,57 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef QUICKFIXASSISTPROVIDER_H +#define QUICKFIXASSISTPROVIDER_H + +#include "iassistprovider.h" + +#include <QtCore/QList> + +namespace TextEditor { + +class QuickFixFactory; + +class TEXTEDITOR_EXPORT QuickFixAssistProvider : public IAssistProvider +{ + Q_OBJECT + +public: + QuickFixAssistProvider(); + virtual ~QuickFixAssistProvider(); + + virtual QList<QuickFixFactory *> quickFixFactories() const; +}; + +} // TextEditor + +#endif // QUICKFIXASSISTPROVIDER_H diff --git a/src/plugins/texteditor/codeassist/runner.cpp b/src/plugins/texteditor/codeassist/runner.cpp new file mode 100644 index 0000000000..a6afedcc16 --- /dev/null +++ b/src/plugins/texteditor/codeassist/runner.cpp @@ -0,0 +1,88 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "runner.h" +#include "iassistprocessor.h" +#include "iassistproposal.h" +#include "iassistinterface.h" + +using namespace TextEditor; +using namespace Internal; + +ProcessorRunner::ProcessorRunner() + : m_processor(0) + , m_interface(0) + , m_discardProposal(false) + , m_proposal(0) +{} + +ProcessorRunner::~ProcessorRunner() +{ + delete m_processor; + if (m_discardProposal) + delete m_proposal; +} + +void ProcessorRunner::setProcessor(IAssistProcessor *computer) +{ + m_processor = computer; +} + +void ProcessorRunner::run() +{ + m_proposal = m_processor->perform(m_interface); +} + +IAssistProposal *ProcessorRunner::proposal() const +{ + return m_proposal; +} + +void ProcessorRunner::setReason(AssistReason reason) +{ + m_reason = reason; +} + +AssistReason ProcessorRunner::reason() const +{ + return m_reason; +} + +void ProcessorRunner::setDiscardProposal(bool discard) +{ + m_discardProposal = discard; +} + +void ProcessorRunner::setAssistInterface(IAssistInterface *interface) +{ + m_interface = interface; +} diff --git a/src/plugins/texteditor/codeassist/runner.h b/src/plugins/texteditor/codeassist/runner.h new file mode 100644 index 0000000000..175733639f --- /dev/null +++ b/src/plugins/texteditor/codeassist/runner.h @@ -0,0 +1,79 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef PROCESSORRUNNER_H +#define PROCESSORRUNNER_H + +#include "iassistproposalwidget.h" + +#include <QtCore/QThread> + +namespace TextEditor { + +class IAssistProcessor; +class IAssistProposal; +class IAssistInterface; + +namespace Internal { + +class ProcessorRunner : public QThread +{ + Q_OBJECT + +public: + ProcessorRunner(); + virtual ~ProcessorRunner(); + + void setProcessor(IAssistProcessor *processor); // Takes ownership of the processor. + void setAssistInterface(IAssistInterface *interface); + void setDiscardProposal(bool discard); + + // @TODO: Not really necessary... + void setReason(AssistReason reason); + AssistReason reason() const; + + virtual void run(); + + IAssistProposal *proposal() const; + +private: + IAssistProcessor *m_processor; + IAssistInterface *m_interface; + bool m_discardProposal; + IAssistProposal *m_proposal; + AssistReason m_reason; +}; + +} // Internal +} // TextEditor + +#endif // PROCESSORRUNNER_H diff --git a/src/plugins/texteditor/completionsettings.h b/src/plugins/texteditor/completionsettings.h index c72d5eb842..16c2d03a71 100644 --- a/src/plugins/texteditor/completionsettings.h +++ b/src/plugins/texteditor/completionsettings.h @@ -48,9 +48,9 @@ enum CaseSensitivity { }; enum CompletionTrigger { - ManualCompletion, - TriggeredCompletion, - AutomaticCompletion + ManualCompletion, // Display proposal only when explicitly invoked by the user. + TriggeredCompletion, // When triggered by the user or upon contextual activation characters. + AutomaticCompletion // The above plus an automatic trigger when the editor is "idle". }; /** diff --git a/src/plugins/texteditor/completionsupport.cpp b/src/plugins/texteditor/completionsupport.cpp deleted file mode 100644 index fa33e0ea35..0000000000 --- a/src/plugins/texteditor/completionsupport.cpp +++ /dev/null @@ -1,243 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "completionsupport.h" -#include "completionwidget.h" -#include "icompletioncollector.h" - -#include <coreplugin/icore.h> -#include <extensionsystem/pluginmanager.h> -#include <texteditor/itexteditor.h> -#include <texteditor/completionsettings.h> -#include <utils/qtcassert.h> - -#include <QtCore/QString> -#include <QtCore/QList> - -namespace TextEditor { - -CompletionSupport *CompletionSupport::instance() -{ - static CompletionSupport *m_instance = 0; - if (!m_instance) - m_instance = new CompletionSupport; - return m_instance; -} - -class CompletionSupportPrivate : public QObject -{ - Q_OBJECT - -public: - explicit CompletionSupportPrivate(CompletionSupport *support); - -private slots: - void performCompletion(const TextEditor::CompletionItem &item); - void cleanupCompletions(); - -public: - QList<CompletionItem> getCompletions() const; - void complete(ITextEditor *editor, CompletionPolicy policy, bool forced); - - CompletionSupport *m_support; - Internal::CompletionWidget *m_completionList; - int m_startPosition; - bool m_checkCompletionTrigger; // Whether to check for completion trigger after cleanup - ITextEditor *m_editor; - const QList<ICompletionCollector *> m_completionCollectors; - ICompletionCollector *m_completionCollector; - CompletionPolicy m_policy; -}; - -CompletionSupportPrivate::CompletionSupportPrivate(CompletionSupport *support) : - m_support(support), - m_completionList(0), - m_startPosition(0), - m_checkCompletionTrigger(false), - m_editor(0), - m_completionCollectors(ExtensionSystem::PluginManager::instance() - ->getObjects<ICompletionCollector>()), - m_completionCollector(0), - m_policy(SemanticCompletion) -{ -} - -QList<CompletionItem> CompletionSupportPrivate::getCompletions() const -{ - if (m_completionCollector) - return m_completionCollector->getCompletions(); - return QList<CompletionItem>(); -} - - -CompletionSupport::CompletionSupport() - : QObject(Core::ICore::instance()), - d(new CompletionSupportPrivate(this)) -{ -} - -CompletionSupport::~CompletionSupport() -{ -} - -void CompletionSupportPrivate::performCompletion(const CompletionItem &item) -{ - item.collector->complete(item, m_completionList->typedChar()); - m_checkCompletionTrigger = true; -} - -void CompletionSupportPrivate::cleanupCompletions() -{ - if (m_completionList) - disconnect(m_completionList, SIGNAL(destroyed(QObject*)), - this, SLOT(cleanupCompletions())); - - if (m_checkCompletionTrigger) - m_checkCompletionTrigger = m_completionCollector->shouldRestartCompletion(); - - m_completionList = 0; - m_completionCollector->cleanup(); - - if (m_checkCompletionTrigger) { - m_checkCompletionTrigger = false; - - // Only check for completion trigger when some text was entered - if (m_editor->position() > m_startPosition) - complete(m_editor, m_policy, false); - } -} - -bool CompletionSupport::isActive() const -{ - return d->m_completionList != 0; -} - -CompletionPolicy CompletionSupport::policy() const -{ - return d->m_policy; -} - -void CompletionSupport::complete(ITextEditor *editor, CompletionPolicy policy, bool forced) -{ - d->complete(editor, policy, forced); -} - -void CompletionSupportPrivate::complete(ITextEditor *editor, CompletionPolicy policy, bool forced) -{ - m_completionCollector = 0; - - foreach (ICompletionCollector *collector, m_completionCollectors) { - QTC_ASSERT(collector, continue); - if (collector->supportsEditor(editor) - && collector->supportsPolicy(policy)) { - m_policy = policy; - m_completionCollector = collector; - break; - } - } - - if (!m_completionCollector) - return; - - m_editor = editor; - QList<CompletionItem> completionItems; - - int currentIndex = 0; - - if (!m_completionList) { - if (!forced) { - const CompletionSettings &completionSettings = m_completionCollector->completionSettings(); - if (completionSettings.m_completionTrigger == ManualCompletion) - return; - if (!m_completionCollector->triggersCompletion(editor)) - return; - } - - m_startPosition = m_completionCollector->startCompletion(editor); - completionItems = getCompletions(); - - QTC_ASSERT(!(m_startPosition == -1 && completionItems.size() > 0), return); - - if (completionItems.isEmpty()) { - cleanupCompletions(); - return; - } - - m_completionList = new Internal::CompletionWidget(m_support, editor); - - connect(m_completionList, SIGNAL(itemSelected(TextEditor::CompletionItem)), - this, SLOT(performCompletion(TextEditor::CompletionItem))); - connect(m_completionList, SIGNAL(completionListClosed()), - this, SLOT(cleanupCompletions())); - - // Make sure to clean up the completions if the list is destroyed without - // emitting completionListClosed (can happen when no focus out event is received, - // for example when switching applications on the Mac) - connect(m_completionList, SIGNAL(destroyed(QObject*)), - this, SLOT(cleanupCompletions())); - } else { - completionItems = getCompletions(); - - if (completionItems.isEmpty()) { - m_completionList->closeList(); - return; - } - - if (m_completionList->explicitlySelected()) { - const int originalIndex = m_completionList->currentCompletionItem().originalIndex; - - for (int index = 0; index < completionItems.size(); ++index) { - if (completionItems.at(index).originalIndex == originalIndex) { - currentIndex = index; - break; - } - } - } - } - - m_completionList->setCompletionItems(completionItems); - - if (currentIndex) - m_completionList->setCurrentIndex(currentIndex); - - // Partially complete when completion was forced - if (forced && m_completionCollector->partiallyComplete(completionItems)) { - m_checkCompletionTrigger = true; - m_completionList->closeList(); - } else { - m_completionList->showCompletions(m_startPosition); - } -} - -} // namespace TextEditor - -#include "completionsupport.moc" diff --git a/src/plugins/texteditor/completionwidget.cpp b/src/plugins/texteditor/completionwidget.cpp deleted file mode 100644 index 3b82d61010..0000000000 --- a/src/plugins/texteditor/completionwidget.cpp +++ /dev/null @@ -1,499 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "completionwidget.h" -#include "completionsupport.h" -#include "icompletioncollector.h" - -#include <texteditor/itexteditor.h> - -#include <utils/faketooltip.h> -#include <utils/qtcassert.h> - -#include <QtCore/QEvent> -#include <QtGui/QApplication> -#include <QtGui/QDesktopWidget> -#include <QtGui/QKeyEvent> -#include <QtGui/QVBoxLayout> -#include <QtGui/QScrollBar> -#include <QtGui/QLabel> -#include <QtGui/QStylePainter> -#include <QtGui/QToolTip> - -#include <limits.h> - -using namespace TextEditor; -using namespace TextEditor::Internal; - -#define NUMBER_OF_VISIBLE_ITEMS 10 - -namespace TextEditor { -namespace Internal { - -class AutoCompletionModel : public QAbstractListModel -{ -public: - AutoCompletionModel(QObject *parent); - - inline const CompletionItem &itemAt(const QModelIndex &index) const - { return m_items.at(index.row()); } - - void setItems(const QList<CompletionItem> &items); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - -private: - QList<CompletionItem> m_items; -}; - - -class CompletionInfoFrame : public Utils::FakeToolTip -{ -public: - CompletionInfoFrame(QWidget *parent = 0) : - Utils::FakeToolTip(parent), - m_label(new QLabel(this)) - { - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(m_label); - - m_label->setForegroundRole(QPalette::ToolTipText); - m_label->setBackgroundRole(QPalette::ToolTipBase); - } - - void setText(const QString &text) - { - m_label->setText(text); - } - -private: - QLabel *m_label; -}; - - -} // namespace Internal -} // namespace TextEditor - - -AutoCompletionModel::AutoCompletionModel(QObject *parent) - : QAbstractListModel(parent) -{ -} - -void AutoCompletionModel::setItems(const QList<CompletionItem> &items) -{ - m_items = items; - reset(); -} - -int AutoCompletionModel::rowCount(const QModelIndex &) const -{ - return m_items.count(); -} - -QVariant AutoCompletionModel::data(const QModelIndex &index, int role) const -{ - if (index.row() >= m_items.count()) - return QVariant(); - - if (role == Qt::DisplayRole) { - return itemAt(index).text; - } else if (role == Qt::DecorationRole) { - return itemAt(index).icon; - } else if (role == Qt::WhatsThisRole) { - return itemAt(index).details; - } - - return QVariant(); -} - - -CompletionWidget::CompletionWidget(CompletionSupport *support, - ITextEditor *editor) - : QFrame(0, Qt::Popup), - m_support(support), - m_editor(editor), - m_completionListView(new CompletionListView(support, editor, this)) -{ - // We disable the frame on this list view and use a QFrame around it instead. - // This improves the look with QGTKStyle. -#ifndef Q_WS_MAC - setFrameStyle(m_completionListView->frameStyle()); -#endif - m_completionListView->setFrameStyle(QFrame::NoFrame); - - setObjectName(QLatin1String("m_popupFrame")); - setAttribute(Qt::WA_DeleteOnClose); - setMinimumSize(1, 1); - setFont(editor->widget()->font()); - - QVBoxLayout *layout = new QVBoxLayout(this); - layout->setMargin(0); - - layout->addWidget(m_completionListView); - setFocusProxy(m_completionListView); - - connect(m_completionListView, SIGNAL(itemSelected(TextEditor::CompletionItem)), - this, SIGNAL(itemSelected(TextEditor::CompletionItem))); - connect(m_completionListView, SIGNAL(completionListClosed()), - this, SIGNAL(completionListClosed())); - connect(m_completionListView, SIGNAL(activated(QModelIndex)), - SLOT(closeList(QModelIndex))); - connect(editor, SIGNAL(contentsChangedBecauseOfUndo()), - this, SLOT(closeList())); -} - -CompletionWidget::~CompletionWidget() -{ -} - -void CompletionWidget::setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems) -{ - m_completionListView->setCompletionItems(completionitems); -} - -void CompletionWidget::closeList(const QModelIndex &index) -{ - m_completionListView->closeList(index); - close(); -} - -void CompletionWidget::showCompletions(int startPos) -{ - ensurePolished(); - updatePositionAndSize(startPos); - show(); - setFocus(); -} - -QChar CompletionWidget::typedChar() const -{ - return m_completionListView->m_typedChar; -} - -CompletionItem CompletionWidget::currentCompletionItem() const -{ - return m_completionListView->currentCompletionItem(); -} - -bool CompletionWidget::explicitlySelected() const -{ - return m_completionListView->explicitlySelected(); -} - -void CompletionWidget::setCurrentIndex(int index) -{ - m_completionListView->setCurrentIndex(m_completionListView->model()->index(index, 0)); -} - -void CompletionWidget::updatePositionAndSize(int startPos) -{ - // Determine size by calculating the space of the visible items - QAbstractItemModel *model = m_completionListView->model(); - int visibleItems = model->rowCount(); - if (visibleItems > NUMBER_OF_VISIBLE_ITEMS) - visibleItems = NUMBER_OF_VISIBLE_ITEMS; - - const QStyleOptionViewItem &option = m_completionListView->viewOptions(); - - QSize shint; - for (int i = 0; i < visibleItems; ++i) { - QSize tmp = m_completionListView->itemDelegate()->sizeHint(option, model->index(i, 0)); - if (shint.width() < tmp.width()) - shint = tmp; - } - - const int fw = frameWidth(); - const int width = shint.width() + fw * 2 + 30; - const int height = shint.height() * visibleItems + fw * 2; - - // Determine the position, keeping the popup on the screen - const QRect cursorRect = m_editor->cursorRect(startPos); - const QDesktopWidget *desktop = QApplication::desktop(); - - QWidget *editorWidget = m_editor->widget(); - -#ifdef Q_WS_MAC - const QRect screen = desktop->availableGeometry(desktop->screenNumber(editorWidget)); -#else - const QRect screen = desktop->screenGeometry(desktop->screenNumber(editorWidget)); -#endif - - QPoint pos = cursorRect.bottomLeft(); - pos.rx() -= 16 + fw; // Space for the icons - - if (pos.y() + height > screen.bottom()) - pos.setY(cursorRect.top() - height); - - if (pos.x() + width > screen.right()) - pos.setX(screen.right() - width); - - setGeometry(pos.x(), pos.y(), width, height); -} - -CompletionListView::CompletionListView(CompletionSupport *support, - ITextEditor *editor, CompletionWidget *completionWidget) - : QListView(completionWidget), - m_blockFocusOut(false), - m_editor(editor), - m_editorWidget(editor->widget()), - m_completionWidget(completionWidget), - m_model(new AutoCompletionModel(this)), - m_support(support), - m_explicitlySelected(false) -{ - QTC_ASSERT(m_editorWidget, return); - - m_infoTimer.setInterval(1000); - m_infoTimer.setSingleShot(true); - connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip())); - - setAttribute(Qt::WA_MacShowFocusRect, false); - setUniformItemSizes(true); - setSelectionBehavior(QAbstractItemView::SelectItems); - setSelectionMode(QAbstractItemView::SingleSelection); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setMinimumSize(1, 1); - setModel(m_model); -#ifdef Q_WS_MAC - if (horizontalScrollBar()) - horizontalScrollBar()->setAttribute(Qt::WA_MacMiniSize); - if (verticalScrollBar()) - verticalScrollBar()->setAttribute(Qt::WA_MacMiniSize); -#endif -} - -CompletionListView::~CompletionListView() -{ -} - -CompletionItem CompletionListView::currentCompletionItem() const -{ - int row = currentIndex().row(); - if (row >= 0 && row < m_model->rowCount()) - return m_model->itemAt(currentIndex()); - - return CompletionItem(); -} - -bool CompletionListView::explicitlySelected() const -{ - return m_explicitlySelected; -} - -void CompletionListView::maybeShowInfoTip() -{ - QModelIndex current = currentIndex(); - if (!current.isValid()) - return; - QString infoTip = current.data(Qt::WhatsThisRole).toString(); - - if (infoTip.isEmpty()) { - delete m_infoFrame.data(); - m_infoTimer.setInterval(200); - return; - } - - if (m_infoFrame.isNull()) - m_infoFrame = new CompletionInfoFrame(this); - - - QRect r = rectForIndex(current); - m_infoFrame->move( - (parentWidget()->mapToGlobal( - parentWidget()->rect().topRight())).x() + 3, - mapToGlobal(r.topRight()).y() - verticalOffset() - ); - m_infoFrame->setText(infoTip); - m_infoFrame->adjustSize(); - m_infoFrame->show(); - m_infoFrame->raise(); - - m_infoTimer.setInterval(0); -} - -void CompletionListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) -{ - QListView::currentChanged(current, previous); - m_infoTimer.start(); -} - - -bool CompletionListView::event(QEvent *e) -{ - if (m_blockFocusOut) - return QListView::event(e); - - bool forwardKeys = true; - if (e->type() == QEvent::FocusOut) { - QModelIndex index; -#if defined(Q_OS_DARWIN) && ! defined(QT_MAC_USE_COCOA) - QFocusEvent *fe = static_cast<QFocusEvent *>(e); - if (fe->reason() == Qt::OtherFocusReason) { - // Qt/carbon workaround - // focus out is received before the key press event. - index = currentIndex(); - } -#endif - m_completionWidget->closeList(index); - if (m_infoFrame) - m_infoFrame->close(); - return true; - } else if (e->type() == QEvent::ShortcutOverride) { - QKeyEvent *ke = static_cast<QKeyEvent *>(e); - switch (ke->key()) { - case Qt::Key_N: - case Qt::Key_P: - // select next/previous completion - if (ke->modifiers() == Qt::ControlModifier) - { - e->accept(); - int change = (ke->key() == Qt::Key_N) ? 1 : -1; - int nrows = model()->rowCount(); - int row = currentIndex().row(); - int newRow = (row + change + nrows) % nrows; - if (newRow == row + change || !ke->isAutoRepeat()) - setCurrentIndex(m_model->index(newRow)); - return true; - } - } - } else if (e->type() == QEvent::KeyPress) { - QKeyEvent *ke = static_cast<QKeyEvent *>(e); - switch (ke->key()) { - case Qt::Key_N: - case Qt::Key_P: - // select next/previous completion - so don't pass on to editor - if (ke->modifiers() == Qt::ControlModifier) - forwardKeys = false; - break; - - case Qt::Key_Escape: - m_completionWidget->closeList(); - return true; - - case Qt::Key_Right: - case Qt::Key_Left: - case Qt::Key_Home: - case Qt::Key_End: - // We want these navigation keys to work in the editor, so forward them - break; - - case Qt::Key_Tab: - case Qt::Key_Return: - //independently from style, accept current entry if return is pressed - if (qApp->focusWidget() == this) - m_completionWidget->closeList(currentIndex()); - return true; - - case Qt::Key_Up: - m_explicitlySelected = true; - if (!ke->isAutoRepeat() - && currentIndex().row() == 0) { - setCurrentIndex(model()->index(model()->rowCount()-1, 0)); - return true; - } - forwardKeys = false; - break; - - case Qt::Key_Down: - m_explicitlySelected = true; - if (!ke->isAutoRepeat() - && currentIndex().row() == model()->rowCount()-1) { - setCurrentIndex(model()->index(0, 0)); - return true; - } - forwardKeys = false; - break; - - case Qt::Key_Enter: - case Qt::Key_PageDown: - case Qt::Key_PageUp: - forwardKeys = false; - break; - - default: - // if a key is forwarded, completion widget is re-opened and selected item is reset to first, - // so only forward keys that insert text and refine the completed item - forwardKeys = !ke->text().isEmpty(); - break; - } - - const CompletionPolicy policy = m_support->policy(); - if (forwardKeys && policy != QuickFixCompletion) { - if (ke->text().length() == 1 && currentIndex().isValid() && qApp->focusWidget() == this) { - QChar typedChar = ke->text().at(0); - const CompletionItem &item = m_model->itemAt(currentIndex()); - if (item.collector->typedCharCompletes(item, typedChar)) { - m_typedChar = typedChar; - m_completionWidget->closeList(currentIndex()); - return true; - } - } - - m_blockFocusOut = true; - QApplication::sendEvent(m_editorWidget, e); - m_blockFocusOut = false; - - // Have the completion support update the list of items - m_support->complete(m_editor, policy, false); - - return true; - } - } - return QListView::event(e); -} - -void CompletionListView::keyboardSearch(const QString &search) -{ - Q_UNUSED(search) -} - -void CompletionListView::setCompletionItems(const QList<TextEditor::CompletionItem> &completionItems) -{ - m_model->setItems(completionItems); - setCurrentIndex(m_model->index(0)); // Select the first item -} - -void CompletionListView::closeList(const QModelIndex &index) -{ - m_blockFocusOut = true; - - if (index.isValid()) - emit itemSelected(m_model->itemAt(index)); - - emit completionListClosed(); - - m_blockFocusOut = false; -} diff --git a/src/plugins/texteditor/completionwidget.h b/src/plugins/texteditor/completionwidget.h deleted file mode 100644 index 2cd7af27f5..0000000000 --- a/src/plugins/texteditor/completionwidget.h +++ /dev/null @@ -1,136 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#ifndef COMPLETIONWIDGET_H -#define COMPLETIONWIDGET_H - -#include <QtGui/QListView> -#include <QtCore/QPointer> -#include <QtCore/QTimer> - -namespace TextEditor { - -class CompletionItem; -class ITextEditor; -class CompletionSupport; - -namespace Internal { - -class AutoCompletionModel; -class CompletionListView; -class CompletionInfoFrame; - -/* The completion widget is responsible for showing a list of possible completions. - It is only used by the CompletionSupport. - */ -class CompletionWidget : public QFrame -{ - Q_OBJECT - -public: - CompletionWidget(CompletionSupport *support, ITextEditor *editor); - ~CompletionWidget(); - - void setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems); - void showCompletions(int startPos); - - QChar typedChar() const; - CompletionItem currentCompletionItem() const; - - void setCurrentIndex(int index); - bool explicitlySelected() const; - -signals: - void itemSelected(const TextEditor::CompletionItem &item); - void completionListClosed(); - -public slots: - void closeList(const QModelIndex &index = QModelIndex()); - -private: - void updatePositionAndSize(int startPos); - -private: - CompletionSupport *m_support; - ITextEditor *m_editor; - CompletionListView *m_completionListView; -}; - -class CompletionListView : public QListView -{ - Q_OBJECT - -public: - ~CompletionListView(); - - CompletionItem currentCompletionItem() const; - bool explicitlySelected() const; - -signals: - void itemSelected(const TextEditor::CompletionItem &item); - void completionListClosed(); - -protected: - bool event(QEvent *e); - - void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); - -private: - friend class CompletionWidget; - - CompletionListView(CompletionSupport *support, ITextEditor *editor, CompletionWidget *completionWidget); - - void setCompletionItems(const QList<TextEditor::CompletionItem> &completionitems); - void keyboardSearch(const QString &search); - void closeList(const QModelIndex &index); - -private slots: - void maybeShowInfoTip(); - -private: - bool m_blockFocusOut; - ITextEditor *m_editor; - QWidget *m_editorWidget; - CompletionWidget *m_completionWidget; - AutoCompletionModel *m_model; - CompletionSupport *m_support; - QPointer<CompletionInfoFrame> m_infoFrame; - QTimer m_infoTimer; - QChar m_typedChar; - bool m_explicitlySelected; -}; - -} // namespace Internal -} // namespace TextEditor - -#endif // COMPLETIONWIDGET_H - diff --git a/src/plugins/texteditor/convenience.cpp b/src/plugins/texteditor/convenience.cpp new file mode 100644 index 0000000000..3facb9ef63 --- /dev/null +++ b/src/plugins/texteditor/convenience.cpp @@ -0,0 +1,70 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#include "convenience.h" + +#include <QtGui/QTextDocument> +#include <QtGui/QTextBlock> + +namespace TextEditor { +namespace Convenience { + +bool convertPosition(const QTextDocument *document, int pos, int *line, int *column) +{ + QTextBlock block = document->findBlock(pos); + if (!block.isValid()) { + (*line) = -1; + (*column) = -1; + return false; + } else { + (*line) = block.blockNumber() + 1; + (*column) = pos - block.position(); + return true; + } +} + +QString textAt(QTextCursor tc, int pos, int length) +{ + if (pos < 0) + pos = 0; + tc.movePosition(QTextCursor::End); + if (pos + length > tc.position()) + length = tc.position() - pos; + + tc.setPosition(pos); + tc.setPosition(pos + length, QTextCursor::KeepAnchor); + + return tc.selectedText(); +} + +} // Util +} // TextEditor diff --git a/src/plugins/texteditor/convenience.h b/src/plugins/texteditor/convenience.h new file mode 100644 index 0000000000..e7ae14f57b --- /dev/null +++ b/src/plugins/texteditor/convenience.h @@ -0,0 +1,57 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (info@qt.nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** 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. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at info@qt.nokia.com. +** +**************************************************************************/ + +#ifndef BASETEXTEDITORUTIL_H +#define BASETEXTEDITORUTIL_H + +#include "texteditor_global.h" + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +#include <QtCore/QString> +#include <QtGui/QTextCursor> + +namespace TextEditor { +namespace Convenience { + +TEXTEDITOR_EXPORT bool convertPosition(const QTextDocument *document, + int pos, + int *line, int *column); + +TEXTEDITOR_EXPORT QString textAt(QTextCursor tc, int pos, int length); + +} // Util +} // TextEditor + +#endif // BASETEXTEDITORUTIL_H diff --git a/src/plugins/texteditor/icompletioncollector.cpp b/src/plugins/texteditor/icompletioncollector.cpp deleted file mode 100644 index 0d16966700..0000000000 --- a/src/plugins/texteditor/icompletioncollector.cpp +++ /dev/null @@ -1,218 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#include "icompletioncollector.h" - -#include "completionsettings.h" -#include "itexteditor.h" - -#include <QtCore/QRegExp> -#include <algorithm> - -using namespace TextEditor; -using namespace TextEditor::Internal; - -namespace TextEditor { -namespace Internal { - -class ICompletionCollectorPrivate -{ -public: - CompletionSettings m_completionSettings; -}; - -} // namespace Internal -} // namespace TextEditor - -bool ICompletionCollector::compareChar(const QChar &l, const QChar &r) -{ - if (l == QLatin1Char('_')) - return false; - else if (r == QLatin1Char('_')) - return true; - else - return l < r; -} - -bool ICompletionCollector::lessThan(const QString &l, const QString &r) -{ - return std::lexicographical_compare(l.begin(), l.end(), - r.begin(), r.end(), - compareChar); -} - -bool ICompletionCollector::completionItemLessThan(const CompletionItem &i1, const CompletionItem &i2) -{ - // The order is case-insensitive in principle, but case-sensitive when this would otherwise mean equality - const QString lower1 = i1.text.toLower(); - const QString lower2 = i2.text.toLower(); - if (lower1 == lower2) - return lessThan(i1.text, i2.text); - else - return lessThan(lower1, lower2); -} - -ICompletionCollector::ICompletionCollector(QObject *parent) - : QObject(parent) - , m_d(new Internal::ICompletionCollectorPrivate) -{ -} - -ICompletionCollector::~ICompletionCollector() -{ - delete m_d; -} - -void ICompletionCollector::setCompletionSettings(const CompletionSettings &settings) -{ - m_d->m_completionSettings = settings; -} - -const CompletionSettings &ICompletionCollector::completionSettings() const -{ - return m_d->m_completionSettings; -} - -QList<CompletionItem> ICompletionCollector::getCompletions() -{ - QList<CompletionItem> completionItems; - - completions(&completionItems); - - qStableSort(completionItems.begin(), completionItems.end(), completionItemLessThan); - - // Remove duplicates - QString lastKey; - QList<CompletionItem> uniquelist; - - foreach (const CompletionItem &item, completionItems) { - if (item.text != lastKey) { - uniquelist.append(item); - lastKey = item.text; - } else { - uniquelist.last().duplicateCount++; - } - } - - return uniquelist; -} - -bool ICompletionCollector::partiallyComplete(const QList<TextEditor::CompletionItem> &items) -{ - if (! m_d->m_completionSettings.m_partiallyComplete) - return false; - if (items.size() >= 100) - return false; - - QList<TextEditor::CompletionItem> completionItems = items; - sortCompletion(completionItems); - - // Compute common prefix - QString firstKey = completionItems.first().text; - QString lastKey = completionItems.last().text; - const int length = qMin(firstKey.length(), lastKey.length()); - firstKey.truncate(length); - lastKey.truncate(length); - - while (firstKey != lastKey) { - firstKey.chop(1); - lastKey.chop(1); - } - - if (ITextEditor *ed = editor()) { - const int typedLength = ed->position() - startPosition(); - if (!firstKey.isEmpty() && firstKey.length() > typedLength) { - ed->setCursorPosition(startPosition()); - ed->replace(typedLength, firstKey); - } - } - - return false; -} - -void ICompletionCollector::sortCompletion(QList<TextEditor::CompletionItem> &completionItems) -{ - qStableSort(completionItems.begin(), completionItems.end(), - &ICompletionCollector::completionItemLessThan); -} - -void ICompletionCollector::filter(const QList<TextEditor::CompletionItem> &items, - QList<TextEditor::CompletionItem> *filteredItems, - const QString &key) -{ - const TextEditor::CaseSensitivity caseSensitivity = m_d->m_completionSettings.m_caseSensitivity; - - /* - * This code builds a regular expression in order to more intelligently match - * camel-case style. This means upper-case characters will be rewritten as follows: - * - * A => [a-z0-9_]*A (for any but the first capital letter) - * - * Meaning it allows any sequence of lower-case characters to preceed an - * upper-case character. So for example gAC matches getActionController. - * - * It also implements the first-letter-only case sensitivity. - */ - QString keyRegExp; - keyRegExp += QLatin1Char('^'); - bool first = true; - const QLatin1String wordContinuation("[a-z0-9_]*"); - foreach (const QChar &c, key) { - if (caseSensitivity == TextEditor::CaseInsensitive || - (caseSensitivity == TextEditor::FirstLetterCaseSensitive && !first)) { - - keyRegExp += QLatin1String("(?:"); - if (c.isUpper() && !first) - keyRegExp += wordContinuation; - keyRegExp += QRegExp::escape(c.toUpper()); - keyRegExp += QLatin1Char('|'); - keyRegExp += QRegExp::escape(c.toLower()); - keyRegExp += QLatin1Char(')'); - } else { - if (c.isUpper() && !first) - keyRegExp += wordContinuation; - keyRegExp += QRegExp::escape(c); - } - - first = false; - } - const QRegExp regExp(keyRegExp); - - foreach (const TextEditor::CompletionItem &item, items) - if (regExp.indexIn(item.text) == 0) - filteredItems->append(item); -} - -bool ICompletionCollector::shouldRestartCompletion() -{ - return true; -} diff --git a/src/plugins/texteditor/icompletioncollector.h b/src/plugins/texteditor/icompletioncollector.h deleted file mode 100644 index 71bf1993ed..0000000000 --- a/src/plugins/texteditor/icompletioncollector.h +++ /dev/null @@ -1,204 +0,0 @@ -/************************************************************************** -** -** This file is part of Qt Creator -** -** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (info@qt.nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at info@qt.nokia.com. -** -**************************************************************************/ - -#ifndef COMPLETIONCOLLECTORINTERFACE_H -#define COMPLETIONCOLLECTORINTERFACE_H - -#include "texteditor_global.h" - -#include <QtCore/QObject> -#include <QtCore/QVariant> -#include <QtGui/QIcon> - -namespace TextEditor { - -namespace Internal { -class ICompletionCollectorPrivate; -} - -class ICompletionCollector; -class ITextEditor; -class CompletionSettings; - -enum CompletionPolicy -{ - QuickFixCompletion, // Used for "Quick Fix" operation. - TextCompletion, // Plain word completion. - SemanticCompletion // Completion using code models. -}; - -class CompletionItem -{ -public: - CompletionItem(ICompletionCollector *collector = 0) - : duplicateCount(0), - order(0), - originalIndex(0), - collector(collector), - isSnippet(false) - { } - - bool isValid() const - { return collector != 0; } - - QString text; - QString details; - QIcon icon; - QVariant data; - int duplicateCount; - int order; - int originalIndex; - ICompletionCollector *collector; - bool isSnippet; -}; - -/* Defines the interface to completion collectors. A completion collector tells - * the completion support code when a completion is triggered and provides the - * list of possible completions. It keeps an internal state so that it can be - * polled for the list of completions, which is reset with a call to reset. - */ -class TEXTEDITOR_EXPORT ICompletionCollector : public QObject -{ - Q_OBJECT -public: - ICompletionCollector(QObject *parent = 0); - virtual ~ICompletionCollector(); - - const CompletionSettings &completionSettings() const; - - virtual QList<CompletionItem> getCompletions(); - virtual bool shouldRestartCompletion(); - - /* Returns the current active ITextEditor */ - virtual ITextEditor *editor() const = 0; - virtual int startPosition() const = 0; - - /* - * Returns true if this completion collector can be used with the given editor. - */ - virtual bool supportsEditor(ITextEditor *editor) const = 0; - - /* - * Returns true if this completion collector supports the given completion policy. - */ - virtual bool supportsPolicy(CompletionPolicy policy) const = 0; - - /* This method should return whether the cursor is at a position which could - * trigger an autocomplete. It will be called each time a character is typed in - * the text editor. - */ - virtual bool triggersCompletion(ITextEditor *editor) = 0; - - // returns starting position - virtual int startCompletion(ITextEditor *editor) = 0; - - /* This method should add all the completions it wants to show into the list, - * based on the given cursor position. - */ - virtual void completions(QList<CompletionItem> *completions) = 0; - - /** - * This method should return true when the given typed character should cause - * the selected completion item to be completed. - */ - virtual bool typedCharCompletes(const CompletionItem &item, QChar typedChar) = 0; - - /** - * This method should complete the given completion item. - * - * \param typedChar Non-null when completion was triggered by typing a - * character. Possible values depend on typedCharCompletes() - */ - virtual void complete(const CompletionItem &item, QChar typedChar) = 0; - - /* This method gives the completion collector a chance to partially complete - * based on a set of items. The general use case is to complete the common - * prefix shared by all possible completion items. - * - * Returns whether the completion popup should be closed. - */ - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &completionItems); - - virtual void sortCompletion(QList<TextEditor::CompletionItem> &completionItems); - - /* Called when it's safe to clean up the completion items. - */ - virtual void cleanup() = 0; - - // helpers - - void filter(const QList<TextEditor::CompletionItem> &items, - QList<TextEditor::CompletionItem> *filteredItems, - const QString &key); - -public slots: - void setCompletionSettings(const TextEditor::CompletionSettings &); - -protected: - static bool compareChar(const QChar &item, const QChar &other); - static bool lessThan(const QString &item, const QString &other); - static bool completionItemLessThan(const CompletionItem &item, const CompletionItem &other); - -private: - Internal::ICompletionCollectorPrivate *m_d; -}; - -class TEXTEDITOR_EXPORT IQuickFixCollector : public ICompletionCollector -{ - Q_OBJECT - -public: - IQuickFixCollector(QObject *parent = 0) : ICompletionCollector(parent) {} - virtual ~IQuickFixCollector() {} - - virtual bool typedCharCompletes(const CompletionItem &, QChar) - { return false; } - - virtual void fix(const TextEditor::CompletionItem &item) = 0; - - virtual void complete(const CompletionItem &item, QChar typedChar) - { - Q_UNUSED(typedChar) - fix(item); - } - - virtual bool triggersCompletion(TextEditor::ITextEditor *) - { return false; } - - virtual bool partiallyComplete(const QList<TextEditor::CompletionItem> &) - { return false; } -}; - - -} // namespace TextEditor - -#endif // COMPLETIONCOLLECTORINTERFACE_H diff --git a/src/plugins/texteditor/quickfix.cpp b/src/plugins/texteditor/quickfix.cpp index 8db6ebb7e8..6ca695b5ea 100644 --- a/src/plugins/texteditor/quickfix.cpp +++ b/src/plugins/texteditor/quickfix.cpp @@ -31,32 +31,9 @@ **************************************************************************/ #include "quickfix.h" -#include "basetexteditor.h" - -#include <coreplugin/ifile.h> -#include <extensionsystem/pluginmanager.h> -#include <QtGui/QApplication> -#include <QtGui/QTextBlock> - -#include <QtCore/QDebug> using namespace TextEditor; -QuickFixState::QuickFixState(TextEditor::BaseTextEditorWidget *editor) - : _editor(editor) -{ -} - -QuickFixState::~QuickFixState() -{ -} - -TextEditor::BaseTextEditorWidget *QuickFixState::editor() const -{ - return _editor; -} - - QuickFixOperation::QuickFixOperation(int priority) { setPriority(priority); @@ -86,8 +63,6 @@ void QuickFixOperation::setDescription(const QString &description) _description = description; } - - QuickFixFactory::QuickFixFactory(QObject *parent) : QObject(parent) { @@ -96,91 +71,3 @@ QuickFixFactory::QuickFixFactory(QObject *parent) QuickFixFactory::~QuickFixFactory() { } - -QuickFixCollector::QuickFixCollector() - : m_editor(0) -{ -} - -QuickFixCollector::~QuickFixCollector() -{ -} - -TextEditor::ITextEditor *QuickFixCollector::editor() const -{ - return m_editor; -} - -int QuickFixCollector::startPosition() const -{ - return m_editor->position(); -} - -bool QuickFixCollector::triggersCompletion(TextEditor::ITextEditor *) -{ - return false; -} - -int QuickFixCollector::startCompletion(TextEditor::ITextEditor *editable) -{ - Q_ASSERT(editable != 0); - - m_editor = editable; - TextEditor::BaseTextEditorWidget *editor = qobject_cast<TextEditor::BaseTextEditorWidget *>(editable->widget()); - Q_ASSERT(editor != 0); - - if (TextEditor::QuickFixState *state = initializeCompletion(editor)) { - QMap<int, QList<QuickFixOperation::Ptr> > matchedOps; - - foreach (QuickFixFactory *factory, quickFixFactories()) { - QList<QuickFixOperation::Ptr> ops = factory->matchingOperations(state); - - 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); - it.toBack(); - if (it.hasPrevious()) { - it.previous(); - m_quickFixes = it.value(); - } - - delete state; - - if (! m_quickFixes.isEmpty()) - return editable->position(); - } - - return -1; -} - -void QuickFixCollector::completions(QList<TextEditor::CompletionItem> *quickFixItems) -{ - for (int i = 0; i < m_quickFixes.size(); ++i) { - TextEditor::QuickFixOperation::Ptr op = m_quickFixes.at(i); - - TextEditor::CompletionItem item(this); - item.text = op->description(); - item.data = QVariant::fromValue(i); - quickFixItems->append(item); - } -} - -void QuickFixCollector::fix(const TextEditor::CompletionItem &item) -{ - const int index = item.data.toInt(); - - if (index < m_quickFixes.size()) { - TextEditor::QuickFixOperation::Ptr quickFix = m_quickFixes.at(index); - quickFix->perform(); - } -} - -void QuickFixCollector::cleanup() -{ - m_quickFixes.clear(); -} diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h index 869c351891..dc97ef43c7 100644 --- a/src/plugins/texteditor/quickfix.h +++ b/src/plugins/texteditor/quickfix.h @@ -34,31 +34,14 @@ #define TEXTEDITORQUICKFIX_H #include "texteditor_global.h" -#include "icompletioncollector.h" +#include <QtCore/QString> +#include <QtCore/QMetaType> #include <QtCore/QSharedPointer> namespace TextEditor { -class BaseTextEditorWidget; - -/*! - State of the editor on which the QuickFixFactory and the QuickFixOperation work. - - This class contains a reference - */ -class TEXTEDITOR_EXPORT QuickFixState -{ -public: - /// Creates a new state object for the given text editor. - QuickFixState(TextEditor::BaseTextEditorWidget *editor); - virtual ~QuickFixState(); - - TextEditor::BaseTextEditorWidget *editor() const; - -private: - TextEditor::BaseTextEditorWidget *_editor; -}; +class IAssistInterface; /*! Class to perform a single quick-fix. @@ -127,58 +110,14 @@ class TEXTEDITOR_EXPORT QuickFixFactory: public QObject public: QuickFixFactory(QObject *parent = 0); - virtual ~QuickFixFactory() = 0; + virtual ~QuickFixFactory(); - /*! - \returns A list of operations which can be performed for the given state. - */ - virtual QList<QuickFixOperation::Ptr> matchingOperations(QuickFixState *state) = 0; -}; - -/*! - A completion collector which will use the QuickFixFactory classes to generate - quickfixes for the given editor. - - All QuickFixFactory instances returned by #quickFixFactories are queried for - possible quick-fix operations. The operations(s) with the highest priority are - stored, and can be queried by calling #quickFixes . - */ -class TEXTEDITOR_EXPORT QuickFixCollector: public TextEditor::IQuickFixCollector -{ - Q_OBJECT - -public: - QuickFixCollector(); - virtual ~QuickFixCollector(); - - QList<TextEditor::QuickFixOperation::Ptr> quickFixes() const - { return m_quickFixes; } - - virtual TextEditor::ITextEditor *editor() const; - virtual int startPosition() const; - virtual bool triggersCompletion(TextEditor::ITextEditor *editor); - virtual int startCompletion(TextEditor::ITextEditor *editor); - virtual void completions(QList<TextEditor::CompletionItem> *completions); - - virtual bool supportsPolicy(TextEditor::CompletionPolicy policy) const - { return policy == TextEditor::QuickFixCompletion; } - - /// See IQuickFixCollector::fix - virtual void fix(const TextEditor::CompletionItem &item); - - /// See ICompletionCollector::cleanup . - virtual void cleanup(); - - /// Called from #startCompletion to create a QuickFixState . - virtual TextEditor::QuickFixState *initializeCompletion(BaseTextEditorWidget *editable) = 0; - - virtual QList<QuickFixFactory *> quickFixFactories() const = 0; - -private: - TextEditor::ITextEditor *m_editor; - QList<QuickFixOperation::Ptr> m_quickFixes; + virtual QList<QuickFixOperation::Ptr> + matchingOperations(const QSharedPointer<const IAssistInterface> &interface) = 0; }; } // namespace TextEditor +Q_DECLARE_METATYPE(TextEditor::QuickFixOperation::Ptr) + #endif // TEXTEDITORQUICKFIX_H diff --git a/src/plugins/texteditor/snippets/snippetcollector.cpp b/src/plugins/texteditor/snippets/snippetassistcollector.cpp index d2ae70e4cc..b6a27e69ab 100644 --- a/src/plugins/texteditor/snippets/snippetcollector.cpp +++ b/src/plugins/texteditor/snippets/snippetassistcollector.cpp @@ -30,18 +30,18 @@ ** **************************************************************************/ -#include "snippetcollector.h" +#include "snippetassistcollector.h" #include "snippetscollection.h" #include <texteditor/texteditorconstants.h> +#include <texteditor/codeassist/basicproposalitem.h> using namespace TextEditor; using namespace Internal; namespace { -void appendSnippets(ICompletionCollector *collector, - QList<CompletionItem> *completionItems, +void appendSnippets(QList<BasicProposalItem *> *items, const QString &groupId, const QIcon &icon, int order) @@ -50,30 +50,32 @@ void appendSnippets(ICompletionCollector *collector, const int size = collection->totalActiveSnippets(groupId); for (int i = 0; i < size; ++i) { const Snippet &snippet = collection->snippet(i, groupId); - CompletionItem item(collector); - item.text = snippet.trigger() + QLatin1Char(' ') + snippet.complement(); - item.data = snippet.content(); - item.details = snippet.generateTip(); - item.icon = icon; - item.order = order; - item.isSnippet = true; - completionItems->append(item); + BasicProposalItem *item = new BasicProposalItem; + item->setText(snippet.trigger() + QLatin1Char(' ') + snippet.complement()); + item->setData(snippet.content()); + item->setDetail(snippet.generateTip()); + item->setIcon(icon); + item->setOrder(order); + items->append(item); } } } // anonymous -SnippetCollector::SnippetCollector(const QString &groupId, const QIcon &icon, int order) : - m_groupId(groupId), m_icon(icon), m_order(order) + +SnippetAssistCollector::SnippetAssistCollector(const QString &groupId, const QIcon &icon, int order) + : m_groupId(groupId) + , m_icon(icon) + , m_order(order) {} -SnippetCollector::~SnippetCollector() +SnippetAssistCollector::~SnippetAssistCollector() {} -QList<CompletionItem> SnippetCollector::getSnippets(ICompletionCollector *collector) const +QList<BasicProposalItem *> SnippetAssistCollector::collect() const { - QList<CompletionItem> completionItems; - appendSnippets(collector, &completionItems, m_groupId, m_icon, m_order); - appendSnippets(collector, &completionItems, Constants::TEXT_SNIPPET_GROUP_ID, m_icon, m_order); - return completionItems; + QList<BasicProposalItem *> snippets; + appendSnippets(&snippets, m_groupId, m_icon, m_order); + appendSnippets(&snippets, Constants::TEXT_SNIPPET_GROUP_ID, m_icon, m_order); + return snippets; } diff --git a/src/plugins/texteditor/snippets/snippetcollector.h b/src/plugins/texteditor/snippets/snippetassistcollector.h index f07d192a73..942ca9371f 100644 --- a/src/plugins/texteditor/snippets/snippetcollector.h +++ b/src/plugins/texteditor/snippets/snippetassistcollector.h @@ -30,11 +30,10 @@ ** **************************************************************************/ -#ifndef SNIPPETPROVIDER_H -#define SNIPPETPROVIDER_H +#ifndef SNIPPETASSISTCOLLECTOR_H +#define SNIPPETASSISTCOLLECTOR_H #include <texteditor/texteditor_global.h> -#include <texteditor/icompletioncollector.h> #include <QtCore/QString> #include <QtCore/QList> @@ -42,13 +41,15 @@ namespace TextEditor { -class TEXTEDITOR_EXPORT SnippetCollector +class BasicProposalItem; + +class TEXTEDITOR_EXPORT SnippetAssistCollector { public: - SnippetCollector(const QString &groupId, const QIcon &icon, int order = 0); - ~SnippetCollector(); + SnippetAssistCollector(const QString &groupId, const QIcon &icon, int order = 0); + ~SnippetAssistCollector(); - QList<CompletionItem> getSnippets(ICompletionCollector *collector) const; + QList<BasicProposalItem *> collect() const; private: QString m_groupId; @@ -58,4 +59,4 @@ private: } // TextEditor -#endif // SNIPPETPROVIDER_H +#endif // SNIPPETASSISTCOLLECTOR_H diff --git a/src/plugins/texteditor/texteditor.pro b/src/plugins/texteditor/texteditor.pro index 51558f8ed2..2056ae391e 100644 --- a/src/plugins/texteditor/texteditor.pro +++ b/src/plugins/texteditor/texteditor.pro @@ -6,7 +6,8 @@ include(../../qtcreatorplugin.pri) include(texteditor_dependencies.pri) INCLUDEPATH += generichighlighter \ tooltip \ - snippets + snippets \ + codeassist SOURCES += texteditorplugin.cpp \ textfilewizard.cpp \ plaintexteditor.cpp \ @@ -16,9 +17,6 @@ SOURCES += texteditorplugin.cpp \ behaviorsettings.cpp \ behaviorsettingspage.cpp \ texteditoractionhandler.cpp \ - icompletioncollector.cpp \ - completionsupport.cpp \ - completionwidget.cpp \ fontsettingspage.cpp \ tabsettings.cpp \ storagesettings.cpp \ @@ -76,10 +74,32 @@ SOURCES += texteditorplugin.cpp \ snippets/snippetscollection.cpp \ snippets/snippetssettings.cpp \ snippets/isnippetprovider.cpp \ - snippets/snippetcollector.cpp \ snippets/plaintextsnippetprovider.cpp \ behaviorsettingswidget.cpp \ - extraencodingsettings.cpp + extraencodingsettings.cpp \ + codeassist/functionhintproposalwidget.cpp \ + codeassist/ifunctionhintproposalmodel.cpp \ + codeassist/functionhintproposal.cpp \ + codeassist/iassistprovider.cpp \ + codeassist/iassistproposal.cpp \ + codeassist/iassistprocessor.cpp \ + codeassist/iassistproposalwidget.cpp \ + codeassist/codeassistant.cpp \ + snippets/snippetassistcollector.cpp \ + codeassist/iassistinterface.cpp \ + codeassist/defaultassistinterface.cpp \ + codeassist/iassistproposalitem.cpp \ + convenience.cpp \ + codeassist/runner.cpp \ + codeassist/completionassistprovider.cpp \ + codeassist/igenericproposalmodel.cpp \ + codeassist/quickfixassistprovider.cpp \ + codeassist/quickfixassistprocessor.cpp \ + codeassist/genericproposal.cpp \ + codeassist/genericproposalwidget.cpp \ + codeassist/basicproposalitem.cpp \ + codeassist/basicproposalitemlistmodel.cpp \ + codeassist/iassistproposalmodel.cpp HEADERS += texteditorplugin.h \ textfilewizard.h \ @@ -89,12 +109,9 @@ HEADERS += texteditorplugin.h \ basetextdocument.h \ behaviorsettings.h \ behaviorsettingspage.h \ - completionsupport.h \ - completionwidget.h \ basetexteditor.h \ texteditoractionhandler.h \ fontsettingspage.h \ - icompletioncollector.h \ texteditorconstants.h \ tabsettings.h \ storagesettings.h \ @@ -160,10 +177,33 @@ HEADERS += texteditorplugin.h \ snippets/reuse.h \ snippets/snippetssettings.h \ snippets/isnippetprovider.h \ - snippets/snippetcollector.h \ snippets/plaintextsnippetprovider.h \ behaviorsettingswidget.h \ - extraencodingsettings.h + extraencodingsettings.h \ + codeassist/functionhintproposalwidget.h \ + codeassist/ifunctionhintproposalmodel.h \ + codeassist/functionhintproposal.h \ + codeassist/iassistprovider.h \ + codeassist/iassistprocessor.h \ + codeassist/iassistproposalwidget.h \ + codeassist/iassistproposal.h \ + codeassist/codeassistant.h \ + snippets/snippetassistcollector.h \ + codeassist/iassistinterface.h \ + codeassist/defaultassistinterface.h \ + codeassist/iassistproposalitem.h \ + convenience.h \ + codeassist/assistenums.h \ + codeassist/runner.h \ + codeassist/completionassistprovider.h \ + codeassist/igenericproposalmodel.h \ + codeassist/quickfixassistprovider.h \ + codeassist/quickfixassistprocessor.h \ + codeassist/genericproposal.h \ + codeassist/genericproposalwidget.h \ + codeassist/basicproposalitem.h \ + codeassist/basicproposalitemlistmodel.h \ + codeassist/iassistproposalmodel.h FORMS += \ displaysettingspage.ui \ diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp index a3b9ef8cc1..b4388e7d03 100644 --- a/src/plugins/texteditor/texteditorplugin.cpp +++ b/src/plugins/texteditor/texteditorplugin.cpp @@ -32,7 +32,6 @@ #include "texteditorplugin.h" -#include "completionsupport.h" #include "findinfiles.h" #include "findincurrentfile.h" #include "fontsettings.h" @@ -46,6 +45,7 @@ #include "manager.h" #include "outlinefactory.h" #include "snippets/plaintextsnippetprovider.h" +#include "codeassist/assistenums.h" #include <coreplugin/icore.h> #include <coreplugin/coreconstants.h> @@ -215,17 +215,15 @@ void TextEditorPlugin::initializeEditor(PlainTextEditorWidget *editor) void TextEditorPlugin::invokeCompletion() { Core::IEditor *iface = Core::EditorManager::instance()->currentEditor(); - ITextEditor *editor = qobject_cast<ITextEditor *>(iface); - if (editor) - CompletionSupport::instance()->complete(editor, SemanticCompletion, true); + if (BaseTextEditorWidget *w = qobject_cast<BaseTextEditorWidget *>(iface->widget())) + w->invokeAssist(Completion); } void TextEditorPlugin::invokeQuickFix() { Core::IEditor *iface = Core::EditorManager::instance()->currentEditor(); - ITextEditor *editor = qobject_cast<ITextEditor *>(iface); - if (editor) - CompletionSupport::instance()->complete(editor, QuickFixCompletion, true); + if (BaseTextEditorWidget *w = qobject_cast<BaseTextEditorWidget *>(iface->widget())) + w->invokeAssist(QuickFix); } void TextEditorPlugin::updateSearchResultsFont(const FontSettings &settings) |