From: Leandro Melo Date: Fri, 15 Apr 2011 14:19:23 +0000 (+0200) Subject: New code assist API X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=bec4f02495b97b17e0b0f8cb67d0909634c16228;p=qt-creator-jp%2Fqt-creator-jp.git 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). --- 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 #include @@ -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 &interface, + int priority, + CompoundStatementAST *compoundStatement, + const QStringList &values) + : CppQuickFixOperation(interface, priority) , compoundStatement(compoundStatement) , values(values) { @@ -150,15 +154,15 @@ static Enum *findEnum(const QList &results, return 0; } -static Enum *conditionEnum(const CppQuickFixState &state, +static Enum *conditionEnum(const QSharedPointer &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 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 CompleteSwitchCaseStatement::match(const CppQuickFixState &state) +QList CompleteSwitchCaseStatement::match( + const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &path = interface->path(); if (path.isEmpty()) return noResult(); // nothing to do @@ -178,13 +183,13 @@ QList 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 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 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 match(const CppQuickFixState &state); + virtual QList match( + const QSharedPointer &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 #include @@ -66,6 +65,7 @@ #include #include #include +#include #include #include @@ -83,6 +83,9 @@ #include #include #include +#include +#include +#include #include #include @@ -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 processor( + CppPlugin::instance()->quickFixProvider()->createProcessor()); + QScopedPointer proposal(processor->perform(interface)); + if (!proposal.isNull()) { + TextEditor::BasicProposalItemListModel *model = + static_cast(proposal->model()); + for (int index = 0; index < model->size(); ++index) { + TextEditor::BasicProposalItem *item = + static_cast(model->proposalItem(index)); + TextEditor::QuickFixOperation::Ptr op = + item->data().value(); + 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 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(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 #include #include #include +#include #include #include @@ -189,6 +189,9 @@ public: static QVector 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 #include @@ -53,11 +54,12 @@ namespace { class InsertDeclOperation: public CppQuickFixOperation { public: - InsertDeclOperation(const CppQuickFixState &state, int priority, + InsertDeclOperation(const QSharedPointer &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 DeclFromDef::match(const CppQuickFixState &state) +QList DeclFromDef::match( + const QSharedPointer &interface) { - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); FunctionDefinitionAST *funDef = 0; int idx = 0; @@ -158,7 +161,7 @@ QList 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 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 DeclFromDef::match(const CppQuickFixState &stat return noResult(); } -QString DeclFromDef::generateDeclaration(const CppQuickFixState &, +QString DeclFromDef::generateDeclaration(const QSharedPointer &, Function *method, ClassOrNamespace *targetBinding) { @@ -214,9 +217,9 @@ namespace { class InsertDefOperation: public CppQuickFixOperation { public: - InsertDefOperation(const CppQuickFixState &state, int priority, + InsertDefOperation(const QSharedPointer &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 DefFromDecl::match(const CppQuickFixState &state) +QList DefFromDecl::match( + const QSharedPointer &interface) { - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &path = interface->path(); + const CppRefactoringFile &file = interface->currentFile(); int idx = path.size() - 1; for (; idx >= 0; --idx) { @@ -292,12 +296,12 @@ QList 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 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 match(const CppQuickFixState &state); + virtual QList + match(const QSharedPointer &interface); protected: - static QString generateDeclaration(const CppQuickFixState &state, + static QString generateDeclaration(const QSharedPointer &interface, CPlusPlus::Function *method, CPlusPlus::ClassOrNamespace *targetBinding); }; @@ -58,7 +59,8 @@ protected: class DefFromDecl: public CppQuickFixFactory { public: - virtual QList match(const CppQuickFixState &state); + virtual QList + match(const QSharedPointer &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 #include @@ -38,6 +39,7 @@ #include #include #include +#include using namespace CPlusPlus; using namespace CppTools; @@ -46,9 +48,10 @@ using namespace Utils; using namespace CppEditor; using namespace CppEditor::Internal; -QList InsertQtPropertyMembers::match(const CppQuickFixState &state) +QList InsertQtPropertyMembers::match( + const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &path = interface->path(); if (path.isEmpty()) return noResult(); @@ -67,8 +70,8 @@ QList 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 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 &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 match(const CppQuickFixState &state); + virtual QList + match(const QSharedPointer &interface); private: enum GenerateFlag { @@ -75,7 +76,8 @@ private: class Operation: public CppQuickFixOperation { public: - Operation(const CppQuickFixState &state, int priority, + Operation(const QSharedPointer &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 #include @@ -54,7 +53,6 @@ #include #include #include -#include #include #include #include @@ -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(em->currentEditor()->widget()); - - if (CPPEditorWidget *editor = qobject_cast(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 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 #include @@ -58,53 +58,10 @@ using namespace TextEditor; using namespace CPlusPlus; using namespace Utils; -CppQuickFixState::CppQuickFixState(TextEditor::BaseTextEditorWidget *editor) - : QuickFixState(editor) -{} - -const QList &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 &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 CppQuickFixFactory::matchingOperations(QuickFixState *state) +QList CppQuickFixFactory::matchingOperations( + const QSharedPointer &interface) { - if (CppQuickFixState *cppState = static_cast(state)) - return match(*cppState); - else - return QList(); + QSharedPointer cppInterface = + interface.staticCast(); + if (cppInterface->path().isEmpty()) + return QList(); + return match(cppInterface); } QList 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 &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 _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 &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 m_interface; }; class CPPEDITOR_EXPORT CppQuickFixFactory: public TextEditor::QuickFixFactory @@ -111,12 +88,15 @@ public: CppQuickFixFactory(); virtual ~CppQuickFixFactory(); - virtual QList matchingOperations(TextEditor::QuickFixState *state); + virtual QList + matchingOperations(const QSharedPointer &interface); + /*! Implement this method to match and create the appropriate CppQuickFixOperation objects. */ - virtual QList match(const CppQuickFixState &state) = 0; + virtual QList match( + const QSharedPointer &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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +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 CppQuickFixAssistProvider::quickFixFactories() const +{ + QList results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (CppQuickFixFactory *f, pm->getObjects()) + 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 &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 +#include + +#include +#include +#include + +namespace CppTools { +class CppRefactoringFile; +} + +namespace CppEditor { +namespace Internal { + +class CPPEditorWidget; + +class CppQuickFixAssistInterface : public TextEditor::DefaultAssistInterface +{ +public: + CppQuickFixAssistInterface(CPPEditorWidget *editor, TextEditor::AssistReason reason); + + const QList &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 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 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 - -#include -#include -#include - -#include -#include - -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(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 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 CppQuickFixCollector::quickFixFactories() const -{ - QList results; - ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); - foreach (CppQuickFixFactory *f, pm->getObjects()) - 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 #include @@ -85,17 +86,17 @@ namespace { class UseInverseOp: public CppQuickFixFactory { public: - virtual QList match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { QList result; - const CppRefactoringFile &file = state.currentFile(); + const CppRefactoringFile &file = interface->currentFile(); - const QList &path = state.path(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { QList result; - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { QList result; BinaryExpressionAST *expression = 0; - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 op(new Operation(state)); + QSharedPointer 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 &interface) + : CppQuickFixOperation(interface) , mk(new ASTPatternBuilder) { left = mk->UnaryExpression(); @@ -400,12 +403,12 @@ class SplitSimpleDeclarationOp: public CppQuickFixFactory } public: - virtual QList match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { QList result; CoreDeclaratorAST *core_declarator = 0; - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); - QSharedPointer op(new Operation(state)); + const QList &path = interface->path(); + QSharedPointer 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 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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); - QSharedPointer op(new Operation(state)); + const QList &path = interface->path(); + QSharedPointer 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 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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { IfStatementAST *pattern = 0; - const QList &path = state.path(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { ExpressionAST *literal = 0; Type type = TypeNone; - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &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 = state.context().control(); + QSharedPointer 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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &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 &path = state.path(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { QList result; - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &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 &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 &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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); - const CppRefactoringFile &file = state.currentFile(); + const QList &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 results = state.context().lookup(nameAST->name, file.scopeAt(nameAST->firstToken())); + const QList 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 &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 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 match(const CppQuickFixState &state) + virtual QList match(const QSharedPointer &interface) { - const QList &path = state.path(); + const QList &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 &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(state().editor())->renameUsagesNow(m_name); + static_cast(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 -#include -#include -#include -#include - -#include -#include - -#include -#include - -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 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 *completions); - - bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - void complete(const TextEditor::CompletionItem &item, QChar typedChar); - bool partiallyComplete(const QList &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 *processed, - QSet *definedMacros); - void addCompletionItem(CPlusPlus::Symbol *symbol); - - bool completeInclude(const QTextCursor &cursor); - void completePreprocessor(); - - void globalCompletion(CPlusPlus::Scope *scope); - - bool completeConstructorOrFunction(const QList &results, - int endOfExpression, bool toolTipOnly); - - bool completeMember(const QList &results); - bool completeScope(const QList &results); - - void completeNamespace(CPlusPlus::ClassOrNamespace *binding); - - void completeClass(CPlusPlus::ClassOrNamespace *b, - bool staticLookup = true); - - bool completeConstructors(CPlusPlus::Class *klass); - - bool completeQtMethod(const QList &results, - bool wantSignals); - - bool completeSignal(const QList &results) - { return completeQtMethod(results, true); } - - bool completeSlot(const QList &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 removeDuplicates(const QList &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 m_functionArgumentWidget; - - QList 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 similarity index 55% rename from src/plugins/cpptools/cppcodecompletion.cpp rename to 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 #include @@ -55,648 +55,689 @@ #include #include -#include - +#include #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include // Qt::escape() -#include -#include -#include +#include +#include -namespace { - const bool debug = ! qgetenv("CPLUSPLUS_DEBUG").isEmpty(); -} +#include +#include +#include +#include using namespace CPlusPlus; +using namespace CppEditor; +using namespace CppTools; +using namespace Internal; +using namespace TextEditor; -namespace CppTools { -namespace Internal { - -class FunctionArgumentWidget : public QLabel -{ - Q_OBJECT - -public: - FunctionArgumentWidget(); - void showFunctionHint(QList functionSymbols, - const LookupContext &context, - int startPosition); - -protected: - bool eventFilter(QObject *obj, QEvent *e); - -private slots: - void nextPage(); - void previousPage(); - -private: - void updateArgumentHighlight(); - void updateHintText(); - void placeInsideScreen(); - - 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; - QList m_items; - LookupContext m_context; -}; - -class ConvertToCompletionItem: protected NameVisitor -{ - // The completion collector. - CppCodeCompletion *_collector; - - // The completion item. - TextEditor::CompletionItem _item; - - // The current symbol. - Symbol *_symbol; - - // The pretty printer. - Overview overview; - -public: - ConvertToCompletionItem(CppCodeCompletion *collector) - : _collector(collector), - _item(0), - _symbol(0) - { } - - TextEditor::CompletionItem operator()(Symbol *symbol) - { - if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId()) - return 0; - - 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); - } - -protected: - Symbol *switchSymbol(Symbol *symbol) - { - Symbol *previousSymbol = _symbol; - _symbol = symbol; - return previousSymbol; - } - - TextEditor::CompletionItem switchCompletionItem(TextEditor::CompletionItem item) - { - TextEditor::CompletionItem previousItem = _item; - _item = item; - return previousItem; - } - - TextEditor::CompletionItem newCompletionItem(const Name *name) - { - TextEditor::CompletionItem item(_collector); - item.text = overview.prettyName(name); - item.icon = _collector->iconForSymbol(_symbol); - return item; - } - - virtual void visit(const Identifier *name) - { _item = newCompletionItem(name); } - - virtual void visit(const TemplateNameId *name) - { - _item = newCompletionItem(name); - _item.text = QLatin1String(name->identifier()->chars()); - } - - virtual void visit(const DestructorNameId *name) - { _item = newCompletionItem(name); } - - virtual void visit(const OperatorNameId *name) - { _item = newCompletionItem(name); } - - virtual void visit(const ConversionNameId *name) - { _item = newCompletionItem(name); } - - virtual void visit(const QualifiedNameId *name) - { _item = newCompletionItem(name->name()); } -}; - -struct CompleteFunctionDeclaration -{ - explicit CompleteFunctionDeclaration(Function *f = 0) - : function(f) - {} - - Function *function; -}; - -} // namespace Internal -} // namespace CppTools - -using namespace CppTools::Internal; - -Q_DECLARE_METATYPE(CompleteFunctionDeclaration) - - -FunctionArgumentWidget::FunctionArgumentWidget(): - m_startpos(-1), - m_current(0), - m_escapePressed(false) -{ - QObject *editorObject = Core::EditorManager::instance()->currentEditor(); - m_editor = qobject_cast(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(QList functionSymbols, - const LookupContext &context, - int startPosition) -{ - Q_ASSERT(!functionSymbols.isEmpty()); - - if (m_startpos == startPosition) - return; - - m_pager->setVisible(functionSymbols.size() > 1); - - m_items = functionSymbols; - m_context = context; - 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; - } - - QString str = m_editor->textAt(m_startpos, curpos - m_startpos); - int argnr = 0; - int parcount = 0; - SimpleLexer tokenize; - QList tokens = tokenize(str); - for (int i = 0; i < tokens.count(); ++i) { - const Token &tk = tokens.at(i); - if (tk.is(T_LPAREN)) - ++parcount; - else if (tk.is(T_RPAREN)) - --parcount; - else if (! parcount && tk.is(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(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - if (m_items.size() > 1) { - QKeyEvent *ke = static_cast(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(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(obj); - if (! (widget == this || m_popupFrame->isAncestorOf(widget))) { - m_popupFrame->close(); - } - } - break; - default: - break; - } - return false; -} - -void FunctionArgumentWidget::updateHintText() -{ - 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 += ""; - hintText += Qt::escape(prettyMethod.mid(begin, end - begin)); - hintText += ""; - hintText += Qt::escape(prettyMethod.mid(end)); - setText(hintText); - - 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); -} - -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"))) -{ -} - -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, +int activationSequenceChar(const QChar &ch, + const QChar &ch2, + const QChar &ch3, + unsigned *kind, bool wantFunctionCall) { - 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(); - - int start = pos; + int referencePosition = 0; int completionKind = T_EOF_SYMBOL; - switch (ch.toLatin1()) { case '.': if (ch2 != QLatin1Char('.')) { completionKind = T_DOT; - --start; + referencePosition = 1; } break; case ',': completionKind = T_COMMA; - --start; + referencePosition = 1; break; case '(': if (wantFunctionCall) { completionKind = T_LPAREN; - --start; + referencePosition = 1; } break; case ':': if (ch3 != QLatin1Char(':') && ch2 == QLatin1Char(':')) { completionKind = T_COLON_COLON; - start -= 2; + referencePosition = 2; } break; case '>': if (ch2 == QLatin1Char('-')) { completionKind = T_ARROW; - start -= 2; + referencePosition = 2; } break; case '*': if (ch2 == QLatin1Char('.')) { completionKind = T_DOT_STAR; - start -= 2; + referencePosition = 2; } else if (ch3 == QLatin1Char('-') && ch2 == QLatin1Char('>')) { completionKind = T_ARROW_STAR; - start -= 3; + referencePosition = 3; } break; case '\\': case '@': if (ch2.isNull() || ch2.isSpace()) { completionKind = T_DOXY_COMMENT; - --start; + referencePosition = 1; } break; case '<': completionKind = T_ANGLE_STRING_LITERAL; - --start; + referencePosition = 1; break; case '"': completionKind = T_STRING_LITERAL; - --start; + referencePosition = 1; break; case '/': completionKind = T_SLASH; - --start; + referencePosition = 1; break; case '#': completionKind = T_POUND; - --start; + referencePosition = 1; break; } - if (start == pos) - return start; + if (kind) + *kind = completionKind; - TextEditor::BaseTextEditorWidget *edit = qobject_cast(editor->widget()); - QTextCursor tc(edit->textCursor()); - tc.setPosition(pos); + return referencePosition; +} - // 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; - } - } +} // Anonymous - if (completionKind == T_COMMA) { - ExpressionUnderCursor expressionUnderCursor; - if (expressionUnderCursor.startOfFunctionCall(tc) == -1) { - completionKind = T_EOF_SYMBOL; - start = pos; - } - } +namespace CppTools { +namespace Internal { - SimpleLexer tokenize; - tokenize.setQtMocRunEnabled(true); - tokenize.setObjCEnabled(true); - tokenize.setSkipComments(false); - const QList &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; - } - 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; - } - } - } - // 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; - } - } - } +struct CompleteFunctionDeclaration +{ + explicit CompleteFunctionDeclaration(Function *f = 0) + : function(f) + {} - if (!include) { - completionKind = T_EOF_SYMBOL; - start = pos; - } - } + Function *function; +}; - if (kind) - *kind = completionKind; +// ---------------------- +// CppAssistProposalModel +// ---------------------- +class CppAssistProposalModel : public TextEditor::BasicProposalItemListModel +{ +public: + CppAssistProposalModel() + : TextEditor::BasicProposalItemListModel() + , m_sortable(false) + , m_completionOperator(T_EOF_SYMBOL) + , m_replaceDotForArrow(false) + {} - return start; -} + virtual bool isSortable() const { return m_sortable; } + virtual IAssistProposalItem *proposalItem(int index) const; -bool CppCodeCompletion::supportsPolicy(TextEditor::CompletionPolicy policy) const -{ - return policy == TextEditor::SemanticCompletion; -} + bool m_sortable; + unsigned m_completionOperator; + bool m_replaceDotForArrow; + Snapshot m_snapshot; +}; -bool CppCodeCompletion::supportsEditor(TextEditor::ITextEditor *editor) const +// --------------------- +// CppAssistProposalItem +// --------------------- +class CppAssistProposalItem : public TextEditor::BasicProposalItem { - return m_manager->isCppEditor(editor); -} +public: + CppAssistProposalItem() : m_isOverloaded(false) {} -TextEditor::ITextEditor *CppCodeCompletion::editor() const -{ return m_editor; } + virtual bool prematurelyApplies(const QChar &c) const; + virtual void applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const; -int CppCodeCompletion::startPosition() const -{ return m_startPosition; } + 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; } -bool CppCodeCompletion::shouldRestartCompletion() -{ return m_shouldRestartCompletion; } +private: + bool m_isOverloaded; + unsigned m_completionOperator; + mutable QChar m_typedChar; + Snapshot m_snapshot; +}; -bool CppCodeCompletion::triggersCompletion(TextEditor::ITextEditor *editor) -{ - m_editor = editor; - m_automaticCompletion = false; +} // Internal +} // CppTools - const int pos = editor->position(); - unsigned token = T_EOF_SYMBOL; +Q_DECLARE_METATYPE(CppTools::Internal::CompleteFunctionDeclaration) - if (startOfOperator(editor, pos, &token, /*want function call=*/ true) != pos) { - if (token == T_POUND) { - int line, column; - editor->convertPosition(pos, &line, &column); - if (column != 1) - return false; - } +IAssistProposalItem *CppAssistProposalModel::proposalItem(int index) const +{ + BasicProposalItem *item = + static_cast(BasicProposalItemListModel::proposalItem(index)); + if (!item->data().canConvert()) { + CppAssistProposalItem *cppItem = static_cast(item); + cppItem->keepCompletionOperator(m_completionOperator); + cppItem->keepSnapshot(m_snapshot); + } + return item; +} - return true; - } else if (completionSettings().m_completionTrigger == TextEditor::AutomaticCompletion) { - // Trigger completion after three characters of a name have been typed, when not editing an existing name - QChar characterUnderCursor = editor->characterAt(pos); +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()) { + if (typedChar == QLatin1Char(':') + || typedChar == QLatin1Char(';') + || typedChar == QLatin1Char('.') + || typedChar == QLatin1Char(',') + || typedChar == QLatin1Char('(')) { + m_typedChar = typedChar; + return true; + } + } else if (data().canConvert()) { + if (typedChar == QLatin1Char('(')) { + m_typedChar = typedChar; + return true; + } + } + + return false; +} + +void CppAssistProposalItem::applyContextualContent(TextEditor::BaseTextEditor *editor, + int basePosition) const +{ + Symbol *symbol = 0; + + if (data().isValid()) + symbol = data().value(); + + QString toInsert; + QString extraChars; + int extraLength = 0; + int cursorOffset = 0; + + bool autoParenthesesEnabled = true; + + if (m_completionOperator == T_SIGNAL || m_completionOperator == T_SLOT) { + toInsert = text(); + extraChars += QLatin1Char(')'); + + 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(); + + const CompletionSettings &completionSettings = + TextEditorSettings::instance()->completionSettings(); + const bool autoInsertBrackets = completionSettings.m_autoInsertBrackets; + + 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 + + /// ### 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('('); + + if (completionSettings.m_spaceAfterFunctionName) + extraChars += QLatin1Char(' '); + extraChars += QLatin1Char('('); + if (m_typedChar == QLatin1Char('(')) + m_typedChar = QChar(); + + // 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(); + } + + // 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. + } + } + } + } + + if (autoInsertBrackets && data().canConvert()) { + if (m_typedChar == QLatin1Char('(')) + m_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 (!m_typedChar.isNull()) { + extraChars += m_typedChar; + if (cursorOffset != 0) + --cursorOffset; + } + + // 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; + } + + toInsert += extraChars; + + // 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); +} + +// -------------------- +// CppFunctionHintModel +// -------------------- +class CppFunctionHintModel : public TextEditor::IFunctionHintProposalModel +{ +public: + CppFunctionHintModel(QList functionSymbols) + : m_functionSymbols(functionSymbols) + , m_currentArg(-1) + {} + + 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 m_functionSymbols; + mutable int m_currentArg; +}; + +QString CppFunctionHintModel::text(int index) const +{ + 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(); + + QString hintText; + hintText += Qt::escape(prettyMethod.left(begin)); + hintText += ""; + hintText += Qt::escape(prettyMethod.mid(begin, end - begin)); + hintText += ""; + hintText += Qt::escape(prettyMethod.mid(end)); + return hintText; +} + +int CppFunctionHintModel::activeArgument(const QString &prefix) const +{ + int argnr = 0; + int parcount = 0; + SimpleLexer tokenize; + QList tokens = tokenize(prefix); + for (int i = 0; i < tokens.count(); ++i) { + const Token &tk = tokens.at(i); + if (tk.is(T_LPAREN)) + ++parcount; + else if (tk.is(T_RPAREN)) + --parcount; + else if (! parcount && tk.is(T_COMMA)) + ++argnr; + } + + if (parcount < 0) + return -1; + + if (argnr != m_currentArg) + m_currentArg = argnr; + + return argnr; +} + +// --------------------------- +// CppCompletionAssistProvider +// --------------------------- +bool CppCompletionAssistProvider::supportsEditor(const QString &editorId) const +{ + return editorId == QLatin1String(CppEditor::Constants::CPPEDITOR_ID); +} + +int CppCompletionAssistProvider::activationCharSequenceLength() const +{ + return 3; +} + +bool CppCompletionAssistProvider::isActivationCharSequence(const QString &sequence) const +{ + 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; +} + +IAssistProcessor *CppCompletionAssistProvider::createProcessor() const +{ + return new CppCompletionAssistProcessor; +} + +// ----------------- +// CppAssistProposal +// ----------------- +class CppAssistProposal : public TextEditor::GenericProposal +{ +public: + CppAssistProposal(int cursorPos, TextEditor::IGenericProposalModel *model) + : TextEditor::GenericProposal(cursorPos, model) + , m_replaceDotForArrow(static_cast(model)->m_replaceDotForArrow) + {} + + virtual bool isCorrective() const { return m_replaceDotForArrow; } + virtual void makeCorrection(BaseTextEditor *editor); + +private: + bool m_replaceDotForArrow; +}; + +void CppAssistProposal::makeCorrection(BaseTextEditor *editor) +{ + editor->setCursorPosition(basePosition() - 1); + editor->replace(1, QLatin1String("->")); + moveBasePosition(1); +} + +namespace { + +class ConvertToCompletionItem: protected NameVisitor +{ + // The completion item. + BasicProposalItem *_item; + + // The current symbol. + Symbol *_symbol; + + // The pretty printer. + Overview overview; + +public: + ConvertToCompletionItem() + : _item(0) + , _symbol(0) + { } + + BasicProposalItem *operator()(Symbol *symbol) + { + if (! symbol || ! symbol->name() || symbol->name()->isQualifiedNameId()) + return 0; + + BasicProposalItem *previousItem = switchCompletionItem(0); + Symbol *previousSymbol = switchSymbol(symbol); + accept(symbol->unqualifiedName()); + if (_item) + _item->setData(QVariant::fromValue(symbol)); + (void) switchSymbol(previousSymbol); + return switchCompletionItem(previousItem); + } + +protected: + Symbol *switchSymbol(Symbol *symbol) + { + Symbol *previousSymbol = _symbol; + _symbol = symbol; + return previousSymbol; + } + + BasicProposalItem *switchCompletionItem(BasicProposalItem *item) + { + BasicProposalItem *previousItem = _item; + _item = item; + return previousItem; + } + + BasicProposalItem *newCompletionItem(const Name *name) + { + BasicProposalItem *item = new CppAssistProposalItem; + item->setText(overview.prettyName(name)); + return item; + } + + virtual void visit(const Identifier *name) + { _item = newCompletionItem(name); } + + virtual void visit(const TemplateNameId *name) + { + _item = newCompletionItem(name); + _item->setText(QLatin1String(name->identifier()->chars())); + } + + virtual void visit(const DestructorNameId *name) + { _item = newCompletionItem(name); } + + virtual void visit(const OperatorNameId *name) + { _item = newCompletionItem(name); } + + virtual void visit(const ConversionNameId *name) + { _item = newCompletionItem(name); } + + virtual void visit(const QualifiedNameId *name) + { _item = newCompletionItem(name->name()); } +}; + +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; +} + +Scope *enclosingNonTemplateScope(Symbol *symbol) +{ + if (symbol) { + if (Scope *scope = symbol->enclosingScope()) { + if (Template *templ = scope->asTemplate()) + return templ->enclosingScope(); + return scope; + } + } + return 0; +} + +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; +} + +} // 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) +{} + +CppCompletionAssistProcessor::~CppCompletionAssistProcessor() +{} + +IAssistProposal * CppCompletionAssistProcessor::perform(const IAssistInterface *interface) +{ + m_interface.reset(static_cast(interface)); + + if (interface->reason() != ExplicitlyInvoked && !accepts()) + return 0; + + 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; + + const int start = startOfOperator(pos, &token, /*want function call=*/ true); + if (start != pos) { + if (token == T_POUND) { + const int column = pos - m_interface->document()->findBlock(start).position(); + if (column != 1) + return false; + } + + return true; + } else { + // Trigger completion after three characters of a name have been typed, when not editing an existing name + 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(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,110 +758,265 @@ 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 processed; + QList::iterator it = m_completions.begin(); + while (it != m_completions.end()) { + CppAssistProposalItem *item = static_cast(*it); + if (!processed.contains(item->text()) || item->data().canConvert()) { + ++it; + if (!item->data().canConvert()) { + processed.insert(item->text()); + if (!item->isOverloaded()) { + if (Symbol *symbol = qvariant_cast(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 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 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 &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; + } + } + } + // 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; } } } + + if (!include) { + *kind = T_EOF_SYMBOL; + start = pos; + } } } + + return start; +} + +int CppCompletionAssistProcessor::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 CppCodeCompletion::tryObjCCompletion(TextEditor::BaseTextEditorWidget *edit) +int CppCompletionAssistProcessor::startCompletionHelper() { - Q_ASSERT(edit); + if (m_objcEnabled) { + if (tryObjCCompletion()) + return m_startPosition; + } + + const int startOfName = findStartOfName(); + m_startPosition = startOfName; + m_model->m_completionOperator = T_EOF_SYMBOL; + + int endOfOperator = m_startPosition; + + // Skip whitespace preceding this position + while (m_interface->characterAt(endOfOperator - 1).isSpace()) + --endOfOperator; + + int endOfExpression = startOfOperator(endOfOperator, + &m_model->m_completionOperator, + /*want function call =*/ true); + + const Core::IFile *file = m_interface->file(); + QString fileName = file->fileName(); + + 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_model->m_completionOperator == T_POUND) { + completePreprocessor(); + m_startPosition = startOfName; + return m_startPosition; + } + + // Include completion + if (m_model->m_completionOperator == T_STRING_LITERAL + || m_model->m_completionOperator == T_ANGLE_STRING_LITERAL + || m_model->m_completionOperator == T_SLASH) { + + QTextCursor c(m_interface->document()); + c.setPosition(endOfExpression); + if (completeInclude(c)) + m_startPosition = startOfName; + return m_startPosition; + } + + ExpressionUnderCursor expressionUnderCursor; + QTextCursor tc(m_interface->document()); + + if (m_model->m_completionOperator == T_COMMA) { + tc.setPosition(endOfExpression); + const int start = expressionUnderCursor.startOfFunctionCall(tc); + if (start == -1) { + m_model->m_completionOperator = T_EOF_SYMBOL; + return -1; + } + + endOfExpression = start; + m_startPosition = start + 1; + m_model->m_completionOperator = T_LPAREN; + } + + QString expression; + int startOfExpression = m_interface->position(); + tc.setPosition(endOfExpression); + + if (m_model->m_completionOperator) { + expression = expressionUnderCursor(tc); + startOfExpression = endOfExpression - expression.length(); + + if (m_model->m_completionOperator == T_LPAREN) { + if (expression.endsWith(QLatin1String("SIGNAL"))) + m_model->m_completionOperator = T_SIGNAL; + + else if (expression.endsWith(QLatin1String("SLOT"))) + m_model->m_completionOperator = T_SLOT; + + 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_model->m_completionOperator = T_EOF_SYMBOL; + m_startPosition = startOfName; + startOfExpression = m_interface->position(); + } + } + } else if (expression.isEmpty()) { + while (startOfExpression > 0 && m_interface->characterAt(startOfExpression).isSpace()) + --startOfExpression; + } + + int line = 0, column = 0; + Convenience::convertPosition(m_interface->document(), startOfExpression, &line, &column); + return startCompletionInternal(fileName, line, column, expression, endOfExpression); +} - int end = m_editor->position(); - while (m_editor->characterAt(end).isSpace()) +bool CppCompletionAssistProcessor::tryObjCCompletion() +{ + int end = m_interface->position(); + while (m_interface->characterAt(end).isSpace()) ++end; - if (m_editor->characterAt(end) != QLatin1Char(']')) + if (m_interface->characterAt(end) != QLatin1Char(']')) return false; - QTextCursor tc(edit->document()); + QTextCursor tc(m_interface->document()); tc.setPosition(end); BackwardsScanner tokens(tc); if (tokens[tokens.startToken() - 1].isNot(T_RBRACKET)) @@ -833,22 +1027,21 @@ bool CppCodeCompletion::tryObjCCompletion(TextEditor::BaseTextEditorWidget *edit return false; const int startPos = tokens[start].begin() + tokens.startPosition(); - const QString expr = m_editor->textAt(startPos, m_editor->position() - startPos); + const QString expr = m_interface->textAt(startPos, m_interface->position() - startPos); - const Snapshot snapshot = m_manager->snapshot(); - Document::Ptr thisDocument = snapshot.document(m_editor->file()->fileName()); + Document::Ptr thisDocument = m_model->m_snapshot.document(m_interface->file()->fileName()); if (! thisDocument) return false; - typeOfExpression.init(thisDocument, snapshot); + typeOfExpression.init(thisDocument, m_model->m_snapshot); int line = 0, column = 0; - edit->convertPosition(m_editor->position(), &line, &column); + Convenience::convertPosition(m_interface->document(), m_interface->position(), &line, &column); Scope *scope = thisDocument->scopeAt(line, column); if (!scope) return false; const QList items = typeOfExpression(expr, scope); - LookupContext lookupContext(thisDocument, snapshot); + LookupContext lookupContext(thisDocument, m_model->m_snapshot); foreach (const LookupItem &item, items) { FullySpecifiedType ty = item.type().simplified(); @@ -870,141 +1063,168 @@ bool CppCodeCompletion::tryObjCCompletion(TextEditor::BaseTextEditorWidget *edit if (m_completions.isEmpty()) return false; - m_startPosition = m_editor->position(); + m_startPosition = m_interface->position(); return true; } -int CppCodeCompletion::startCompletionHelper(TextEditor::ITextEditor *editor) +void CppCompletionAssistProcessor::addCompletionItem(const QString &text, + const QIcon &icon, + int order, + const QVariant &data) { - TextEditor::BaseTextEditorWidget *edit = qobject_cast(editor->widget()); - if (! edit) - return -1; - - m_editor = editor; + BasicProposalItem *item = new CppAssistProposalItem; + item->setText(text); + item->setIcon(icon); + item->setOrder(order); + item->setData(data); + m_completions.append(item); +} - if (m_objcEnabled) { - if (tryObjCCompletion(edit)) - return m_startPosition; +void CppCompletionAssistProcessor::addCompletionItem(CPlusPlus::Symbol *symbol) +{ + ConvertToCompletionItem toCompletionItem; + BasicProposalItem *item = toCompletionItem(symbol); + if (item) { + item->setIcon(m_icons.iconForSymbol(symbol)); + m_completions.append(item); } +} - const int startOfName = findStartOfName(); - m_startPosition = startOfName; - m_completionOperator = T_EOF_SYMBOL; - - int endOfOperator = m_startPosition; - - // Skip whitespace preceding this position - while (editor->characterAt(endOfOperator - 1).isSpace()) - --endOfOperator; +void CppCompletionAssistProcessor::completeObjCMsgSend(CPlusPlus::ClassOrNamespace *binding, + bool staticClassAccess) +{ + QList memberScopes; + foreach (Symbol *s, binding->symbols()) { + if (ObjCClass *c = s->asObjCClass()) + memberScopes.append(c); + } - int endOfExpression = startOfOperator(editor, endOfOperator, - &m_completionOperator, - /*want function call =*/ true); + foreach (Scope *scope, memberScopes) { + for (unsigned i = 0; i < scope->memberCount(); ++i) { + Symbol *symbol = scope->memberAt(i); - Core::IFile *file = editor->file(); - QString fileName = file->fileName(); + 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 (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 (!text.isEmpty()) + addCompletionItem(text, QIcon(), 0, QVariant::fromValue(data)); + } + } } - - return m_startPosition; } +} - // Pre-processor completion - if (m_completionOperator == T_POUND) { - completePreprocessor(); - m_startPosition = startOfName; - return m_startPosition; +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); } - // Include completion - if (m_completionOperator == T_STRING_LITERAL - || m_completionOperator == T_ANGLE_STRING_LITERAL - || m_completionOperator == T_SLASH) { - - QTextCursor c = edit->textCursor(); - c.setPosition(endOfExpression); - if (completeInclude(c)) - m_startPosition = startOfName; - return m_startPosition; + // 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()); } - ExpressionUnderCursor expressionUnderCursor; - QTextCursor tc(edit->document()); - - if (m_completionOperator == T_COMMA) { - tc.setPosition(endOfExpression); - const int start = expressionUnderCursor.startOfFunctionCall(tc); - if (start == -1) { - m_completionOperator = T_EOF_SYMBOL; - return -1; + foreach (const QString &frameworkPath, m_interface->frameworkPaths()) { + QString realPath = frameworkPath; + if (!directoryPrefix.isEmpty()) { + realPath += QLatin1Char('/'); + realPath += directoryPrefix; + realPath += QLatin1String(".framework/Headers"); } - - endOfExpression = start; - m_startPosition = start + 1; - m_completionOperator = T_LPAREN; + foreach (const QString &itemText, manager->includesInPath(realPath)) + addCompletionItem(itemText, m_icons.keywordIcon()); } - QString expression; - int startOfExpression = editor->position(); - tc.setPosition(endOfExpression); + return !m_completions.isEmpty(); +} - if (m_completionOperator) { - expression = expressionUnderCursor(tc); - startOfExpression = endOfExpression - expression.length(); +void CppCompletionAssistProcessor::completePreprocessor() +{ + foreach (const QString &preprocessorCompletion, preprocessorCompletions) + addCompletionItem(preprocessorCompletion); - if (m_completionOperator == T_LPAREN) { - if (expression.endsWith(QLatin1String("SIGNAL"))) - m_completionOperator = T_SIGNAL; + if (objcKeywordsWanted()) + addCompletionItem(QLatin1String("import")); +} - else if (expression.endsWith(QLatin1String("SLOT"))) - m_completionOperator = T_SLOT; +bool CppCompletionAssistProcessor::objcKeywordsWanted() const +{ + if (!m_objcEnabled) + return false; - else if (editor->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_startPosition = startOfName; - startOfExpression = editor->position(); - } - } - } else if (expression.isEmpty()) { - while (startOfExpression > 0 && editor->characterAt(startOfExpression).isSpace()) - --startOfExpression; - } + const Core::IFile *file = m_interface->file(); + QString fileName = file->fileName(); - 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); + const Core::MimeDatabase *mdb = Core::ICore::instance()->mimeDatabase(); + return mdb->findByFile(fileName).type() == CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE; } -int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget *edit, - const QString fileName, - unsigned line, unsigned column, - const QString &expr, - int endOfExpression) +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 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; @@ -1059,375 +1279,138 @@ int CppCodeCompletion::startCompletionInternal(TextEditor::BaseTextEditorWidget } return -1; - } else { - // nothing to do. - return -1; - - } - } - - switch (m_completionOperator) { - case T_LPAREN: - if (completeConstructorOrFunction(results, endOfExpression, false)) - return m_startPosition; - break; - - case T_DOT: - case T_ARROW: - if (completeMember(results)) - return m_startPosition; - break; - - case T_COLON_COLON: - if (completeScope(results)) - return m_startPosition; - break; - - case T_SIGNAL: - if (completeSignal(results)) - return m_startPosition; - break; - - case T_SLOT: - if (completeSlot(results)) - return m_startPosition; - break; - - default: - break; - } // end of switch - - // nothing to do. - return -1; -} - -void CppCodeCompletion::globalCompletion(Scope *currentScope) -{ - const LookupContext &context = typeOfExpression.context(); - - if (m_completionOperator == T_COLON_COLON) { - completeNamespace(context.globalNamespace()); - return; - } - - QList usingBindings; - ClassOrNamespace *currentBinding = 0; - - for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) { - if (scope->isBlock()) { - if (ClassOrNamespace *binding = context.lookupType(scope)) { - for (unsigned i = 0; i < scope->memberCount(); ++i) { - Symbol *member = scope->memberAt(i); - if (! member->name()) - continue; - else if (UsingNamespaceDirective *u = member->asUsingNamespaceDirective()) { - if (ClassOrNamespace *b = binding->lookupType(u->name())) - usingBindings.append(b); - } - } - } - } else if (scope->isFunction() || scope->isClass() || scope->isNamespace()) { - currentBinding = context.lookupType(scope); - break; - } - } - - for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) { - if (scope->isBlock()) { - for (unsigned i = 0; i < scope->memberCount(); ++i) { - addCompletionItem(scope->memberAt(i)); - } - } else if (scope->isFunction()) { - Function *fun = scope->asFunction(); - for (unsigned i = 0; i < fun->argumentCount(); ++i) { - addCompletionItem(fun->argumentAt(i)); - } - break; - } else { - break; - } - } - - for (; currentBinding; currentBinding = currentBinding->parent()) { - const QList symbols = currentBinding->symbols(); - - if (! symbols.isEmpty()) { - if (symbols.first()->isNamespace()) - completeNamespace(currentBinding); - else - completeClass(currentBinding); - } - } - - foreach (ClassOrNamespace *b, usingBindings) - completeNamespace(b); - - addKeywords(); - addMacros(QLatin1String(""), context.snapshot()); - addMacros(context.thisDocument()->fileName(), context.snapshot()); - 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 &results, - int endOfExpression, bool toolTipOnly) -{ - const LookupContext &context = typeOfExpression.context(); - QList 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. + } else { + // nothing to do. + return -1; - // get current line and column - TextEditor::BaseTextEditorWidget *edit = qobject_cast(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 + switch (m_model->m_completionOperator) { + case T_LPAREN: + if (completeConstructorOrFunction(results, endOfExpression, false)) + return m_startPosition; + break; - Scope *sc = context.thisDocument()->scopeAt(line, column); + case T_DOT: + case T_ARROW: + if (completeMember(results)) + return m_startPosition; + break; - 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; + case T_COLON_COLON: + if (completeScope(results)) + return m_startPosition; + break; - 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("();"); + case T_SIGNAL: + if (completeSignal(results)) + return m_startPosition; + break; - Document::Ptr doc = Document::create(QLatin1String("")); - 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()) { + case T_SLOT: + if (completeSlot(results)) + return m_startPosition; + break; - autocompleteSignature = true; + default: + break; + } // end of switch - 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; - } - } - } - } - } + // nothing to do. + return -1; +} - if (autocompleteSignature && !isDestructor) { - // set up signature autocompletion - foreach (Function *f, functions) { - Overview overview; - overview.setShowArgumentNames(true); - overview.setShowDefaultArguments(false); +void CppCompletionAssistProcessor::globalCompletion(CPlusPlus::Scope *currentScope) +{ + const LookupContext &context = typeOfExpression.context(); - // gets: "parameter list) cv-spec", - QString completion = overview(f->type()).mid(1); + if (m_model->m_completionOperator == T_COLON_COLON) { + completeNamespace(context.globalNamespace()); + return; + } + + QList usingBindings; + ClassOrNamespace *currentBinding = 0; - TextEditor::CompletionItem item(this); - item.text = completion; - item.data = QVariant::fromValue(CompleteFunctionDeclaration(f)); - m_completions.append(item); + for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) { + if (scope->isBlock()) { + if (ClassOrNamespace *binding = context.lookupType(scope)) { + for (unsigned i = 0; i < scope->memberCount(); ++i) { + Symbol *member = scope->memberAt(i); + if (! member->name()) + continue; + else if (UsingNamespaceDirective *u = member->asUsingNamespaceDirective()) { + if (ClassOrNamespace *b = binding->lookupType(u->name())) + usingBindings.append(b); + } } - return true; } + } else if (scope->isFunction() || scope->isClass() || scope->isNamespace()) { + currentBinding = context.lookupType(scope); + break; } } - if (! functions.empty() && !isDestructor) { - // set up function call tooltip + for (Scope *scope = currentScope; scope; scope = scope->enclosingScope()) { + if (scope->isBlock()) { + for (unsigned i = 0; i < scope->memberCount(); ++i) { + addCompletionItem(scope->memberAt(i)); + } + } else if (scope->isFunction()) { + Function *fun = scope->asFunction(); + for (unsigned i = 0; i < fun->argumentCount(); ++i) { + addCompletionItem(fun->argumentAt(i)); + } + break; + } else { + break; + } + } - // Recreate if necessary - if (!m_functionArgumentWidget) - m_functionArgumentWidget = new FunctionArgumentWidget; + for (; currentBinding; currentBinding = currentBinding->parent()) { + const QList symbols = currentBinding->symbols(); - m_functionArgumentWidget->showFunctionHint(functions, - typeOfExpression.context(), - m_startPosition); + if (! symbols.isEmpty()) { + if (symbols.first()->isNamespace()) + completeNamespace(currentBinding); + else + completeClass(currentBinding); + } } - return false; + foreach (ClassOrNamespace *b, usingBindings) + completeNamespace(b); + + addKeywords(); + addMacros(QLatin1String(""), context.snapshot()); + addMacros(context.thisDocument()->fileName(), context.snapshot()); + addSnippets(); } -bool CppCodeCompletion::completeMember(const QList &baseResults) +bool CppCompletionAssistProcessor::completeMember(const QList &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 &results) +bool CppCompletionAssistProcessor::completeScope(const QList &results) { const LookupContext &context = typeOfExpression.context(); if (results.isEmpty()) @@ -1461,162 +1444,7 @@ bool CppCodeCompletion::completeScope(const QList &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 processed; - QSet 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 *processed, - QSet *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 bindingsVisited; QList 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 bindingsVisited; QList bindingsToVisit; @@ -1709,15 +1537,14 @@ void CppCodeCompletion::completeClass(ClassOrNamespace *b, bool staticLookup) } } -bool CppCodeCompletion::completeQtMethod(const QList &results, - bool wantSignals) +bool CppCompletionAssistProcessor::completeQtMethod(const QList &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 &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 &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 &results, return ! m_completions.isEmpty(); } -void CppCodeCompletion::completions(QList *completions) +void CppCompletionAssistProcessor::addSnippets() { - const int length = m_editor->position() - m_startPosition; - if (length < 0) - return; + m_completions.append(m_snippetCollector.collect()); +} - 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); - } - } - } - } +void CppCompletionAssistProcessor::addKeywords() +{ + int keywordLimit = T_FIRST_OBJC_AT_KEYWORD; + if (objcKeywordsWanted()) + keywordLimit = T_LAST_OBJC_AT_KEYWORD + 1; - if (m_automaticCompletion && completions->size() == 1 && key == completions->first().text) { - completions->clear(); - } + // keyword completion items. + for (int i = T_FIRST_KEYWORD; i < keywordLimit; ++i) + addCompletionItem(QLatin1String(Token::name(i)), m_icons.keywordIcon()); } -QList CppCodeCompletion::removeDuplicates(const QList &items) +void CppCompletionAssistProcessor::addMacros(const QString &fileName, const CPlusPlus::Snapshot &snapshot) { - // Duplicates are kept only if they are snippets. - QList uniquelist; QSet 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(item.data)) { - if (Function *funTy = symbol->type()->asFunctionType()) { - if (funTy->hasArguments()) - ++uniquelist.back().duplicateCount; - } - } - } - } - } - - return uniquelist; -} + QSet definedMacros; -QList CppCodeCompletion::getCompletions() -{ - QList completionItems; - completions(&completionItems); + 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 *processed, + QSet *definedMacros) { - if (m_automaticCompletion) - return false; - - if (item.data.canConvert()) // 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()) - return typedChar == QLatin1Char(':') - || typedChar == QLatin1Char(';') - || typedChar == QLatin1Char('.') - || typedChar == QLatin1Char(',') - || typedChar == QLatin1Char('('); + processed->insert(doc->fileName()); - if (item.data.canConvert()) - 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 &results, + int endOfExpression, + bool toolTipOnly) { - m_shouldRestartCompletion = false; // Enabled for specific cases + const LookupContext &context = typeOfExpression.context(); + QList functions; - Symbol *symbol = 0; + foreach (const LookupItem &result, results) { + FullySpecifiedType exprTy = result.type().simplified(); - if (item.data.isValid()) { - if (item.data.canConvert()) { - TextEditor::BaseTextEditorWidget *edit = qobject_cast(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(); - } - } + 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()) { - 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 &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("")); + 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 +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +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 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 &results, + int endOfExpression, + bool toolTipOnly); + bool completeMember(const QList &results); + bool completeScope(const QList &results); + void completeNamespace(CPlusPlus::ClassOrNamespace *binding); + void completeClass(CPlusPlus::ClassOrNamespace *b, bool staticLookup = true); + bool completeQtMethod(const QList &results, bool wantSignals); + bool completeSignal(const QList &results) + { return completeQtMethod(results, true); } + bool completeSlot(const QList &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 *processed, + QSet *definedMacros); + + int m_startPosition; + bool m_objcEnabled; + QScopedPointer m_interface; + QList m_completions; + TextEditor::SnippetAssistCollector m_snippetCollector; + const CppCompletionAssistProvider *m_provider; + CPlusPlus::Icons m_icons; + CPlusPlus::TypeOfExpression typeOfExpression; + QStringList preprocessorCompletions; + QScopedPointer 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 @@ -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 #include #include -#include #include #include #include #include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -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(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(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(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(m_provider)->setInactive(); } - void setInactive() +private: + FakeVimCompletionAssistProvider *m_provider; +}; + + +class FakeVimAssistProposalModel : public BasicProposalItemListModel +{ +public: + FakeVimAssistProposalModel(const QList &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 *completions) +class FakeVimCompletionAssistProcessor : public IAssistProcessor +{ +public: + FakeVimCompletionAssistProcessor(const TextEditor::IAssistProvider *provider) + : m_provider(static_cast(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 items; QSet 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 &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 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(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(sender())); +// m_wordCompletion->setActive(needle, forward, qobject_cast(sender())); + m_wordProvider->setActive(needle, forward, qobject_cast(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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 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 m_items; -}; - - -FunctionArgumentWidget::FunctionArgumentWidget(): - m_startpos(-1), - m_current(0), - m_escapePressed(false) -{ - QObject *editorObject = Core::EditorManager::instance()->currentEditor(); - m_editor = qobject_cast(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 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 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(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - if (m_items.size() > 1) { - QKeyEvent *ke = static_cast(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(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(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(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(editor->widget()); - - QList 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 signatures; - if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) - signatures.append(const_cast(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 *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 &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 CodeCompletion::getCompletions() -{ - QList completionItems; - - completions(&completionItems); - - qStableSort(completionItems.begin(), completionItems.end(), glslCompletionItemLessThan); - - // Remove duplicates - QString lastKey; - QVariant lastData; - QList 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 -#include - -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 *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 &completionItems); - - virtual QList getCompletions(); - virtual bool shouldRestartCompletion(); - - /* Called when it's safe to clean up the completion items. - */ - virtual void cleanup(); - -private: - QList m_completions; - QList m_keywordCompletions; - TextEditor::ITextEditor *m_editor; - int m_startPosition; - bool m_restartCompletion; - QPointer 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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(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(editor->widget()); + + QList 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 signatures; + if (const GLSL::Function *funTy = exprTy.type->asFunctionType()) + signatures.append(const_cast(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 &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 +#include +#include +#include + +#include +#include + +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 &symbols); + bool acceptsIdleEditor() const; + void addCompletion(const QString &text, const QIcon &icon, int order = 0); + + int m_startPosition; + QScopedPointer m_interface; + QList 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 #include @@ -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 #include @@ -55,7 +55,6 @@ #include #include #include -#include #include #include @@ -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 + +#include + +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 + +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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -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 _properties; - -public: - QList 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 _processed; - QHash _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 operator ()(const Interpreter::Value *value) - { - _processed.clear(); - _properties.clear(); - _currentObject = Interpreter::value_cast(value); - - enumerateProperties(value); - - return _properties; - } - - QHash 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(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 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(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyPress: - if (static_cast(e)->key() == Qt::Key_Escape) { - m_escapePressed = true; - } - break; - case QEvent::KeyRelease: - if (static_cast(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(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(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(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 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)) - return true; - else if (AST::cast(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 &newCompletions, - const QIcon &icon, int order) -{ - QHashIterator 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 &newCompletions, - const QIcon &icon, int order, bool afterOn) -{ - QHashIterator 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(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(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 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(node) || AST::cast(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(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 *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()) // 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(m_editor->widget())) { - if (item.data.canConvert()) { - 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() && item.data.value().isDirectory) - m_restartCompletion = true; -} - -bool CodeCompletion::partiallyComplete(const QList &completionItems) -{ - if (completionItems.count() == 1) { - const TextEditor::CompletionItem item = completionItems.first(); - - if (!item.data.canConvert()) { - 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 &completionItems) -{ - qStableSort(completionItems.begin(), completionItems.end(), qmlCompletionItemLessThan); -} - -QList CodeCompletion::getCompletions() -{ - QList completionItems; - - completions(&completionItems); - - sortCompletion(completionItems); - - // Remove duplicates - QString lastKey; - QVariant lastData; - QList 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 -#include -#include -#include -#include - -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 *completions); - virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); - virtual bool partiallyComplete(const QList &completionItems); - virtual QList getCompletions(); - virtual void sortCompletion(QList &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 &newCompletions, - const QIcon &icon, int relevance); - void addCompletions(const QStringList &newCompletions, - const QIcon &icon, int relevance); - void addCompletionsPropertyLhs( - const QHash &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 m_completions; - QPointer 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 + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 _processed; + QHash _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 operator ()(const Interpreter::Value *value) + { + _processed.clear(); + _properties.clear(); + _currentObject = Interpreter::value_cast(value); + + enumerateProperties(value); + + return _properties; + } + + QHash 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)) + return true; + else if (AST::cast(ast)) + return true; + else + return false; +} + +} // Anonymous + +// ----------------------- +// QmlJSAssistProposalItem +// ----------------------- +bool QmlJSAssistProposalItem::prematurelyApplies(const QChar &c) const +{ + if (data().canConvert()) // 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 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(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(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 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(node) || AST::cast(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(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 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 &newCompletions, + const QIcon &icon, + int order, + bool afterOn) +{ + QHashIterator it(newCompletions); + while (it.hasNext()) { + it.next(); + + QString itemText = it.key(); + QLatin1String postfix(": "); + if (afterOn) + postfix = QLatin1String(" {"); + if (const Interpreter::QmlObjectValue *qmlValue = + dynamic_cast(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 &newCompletions, + const QIcon &icon, + int order) +{ + QHashIterator 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 &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 &newCompletions, + const QIcon &icon, + int order); + void addCompletions(const QStringList &newCompletions, const QIcon &icon, int order); + void addCompletionsPropertyLhs(const QHash &newCompletions, + const QIcon &icon, + int order, + bool afterOn); + + int m_startPosition; + QScopedPointer m_interface; + QList 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 @@ -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 &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 ComponentFromObjectDef::match(const QmlJSQuickFixState &state) + +QList ComponentFromObjectDef::match( + const QSharedPointer &interface) { - const int pos = state.currentFile().cursor().position(); + const int pos = interface->currentFile().cursor().position(); - QList path = state.semanticInfo().astPath(pos); + QList path = interface->semanticInfo().astPath(pos); for (int i = path.size() - 1; i >= 0; --i) { Node *node = path.at(i); if (UiObjectDefinition *objDef = cast(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(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 match(const QmlJSQuickFixState &state); + virtual QList match( + const QSharedPointer &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 #include @@ -70,6 +71,8 @@ #include #include #include +#include +#include #include #include #include @@ -78,6 +81,7 @@ #include #include #include +#include #include #include @@ -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 processor( + QmlJSEditorPlugin::instance()->quickFixAssistProvider()->createProcessor()); + QScopedPointer proposal(processor->perform(interface)); + if (!proposal.isNull()) { + TextEditor::BasicProposalItemListModel *model = + static_cast(proposal->model()); + for (int index = 0; index < model->size(); ++index) { + TextEditor::BasicProposalItem *item = + static_cast(model->proposalItem(index)); + TextEditor::QuickFixOperation::Ptr op = + item->data().value(); + 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(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 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 #include @@ -69,7 +69,6 @@ #include #include #include -#include #include #include @@ -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(em->currentEditor()->widget()); - - if (QmlJSTextEditorWidget *editor = qobject_cast(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 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 #include @@ -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 &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(), - _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 QmlJSQuickFixFactory::matchingOperations(QuickFixState *state) +QList QmlJSQuickFixFactory::matchingOperations( + const QSharedPointer &interface) { - if (QmlJSQuickFixState *qmljsState = static_cast(state)) - return match(*qmljsState); - else - return QList(); + return match(interface.staticCast()); } QList QmlJSQuickFixFactory::noResult() @@ -131,49 +108,3 @@ QList QmlJSQuickFixFactory::singleResult(QmlJSQuick result.append(QmlJSQuickFixOperation::Ptr(operation)); return result; } - -QmlJSQuickFixCollector::QmlJSQuickFixCollector() -{ -} - -QmlJSQuickFixCollector::~QmlJSQuickFixCollector() -{ -} - -bool QmlJSQuickFixCollector::supportsEditor(TextEditor::ITextEditor *editable) const -{ - return qobject_cast(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(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 QmlJSQuickFixCollector::quickFixFactories() const -{ - QList results; - ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); - foreach (QmlJSQuickFixFactory *f, pm->getObjects()) - 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 #include +#include + 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 &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 m_interface; }; class QmlJSQuickFixFactory: public TextEditor::QuickFixFactory @@ -129,39 +101,20 @@ public: QmlJSQuickFixFactory(); virtual ~QmlJSQuickFixFactory(); - virtual QList matchingOperations(TextEditor::QuickFixState *state); + virtual QList + matchingOperations(const QSharedPointer &interface); /*! Implement this method to match and create the appropriate QmlJSQuickFixOperation objects. */ - virtual QList match(const QmlJSQuickFixState &state) = 0; + virtual QList match( + const QSharedPointer &interface) = 0; static QList noResult(); static QList 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 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 + +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 QmlJSQuickFixAssistProvider::quickFixFactories() const +{ + QList results; + ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance(); + foreach (QmlJSQuickFixFactory *f, pm->getObjects()) + 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 + +#include +#include +#include + +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 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 #include @@ -64,13 +65,14 @@ namespace { class SplitInitializerOp: public QmlJSQuickFixFactory { public: - virtual QList match(const QmlJSQuickFixState &state) + virtual QList match( + const QSharedPointer &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(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 &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 +#include + +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 +#include + +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 -#include -#include -#include - -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 ProFileCompletion::getCompletions() -{ - QList 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(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 *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 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 &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 - -namespace Qt4ProjectManager { - -namespace Internal { - -class ProFileCompletion : public TextEditor::ICompletionCollector -{ - Q_OBJECT -public: - ProFileCompletion(QObject *parent = 0); - - virtual ~ProFileCompletion(); - - virtual QList 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 *completions); - virtual bool typedCharCompletes(const TextEditor::CompletionItem &item, QChar typedChar); - virtual void complete(const TextEditor::CompletionItem &item, QChar typedChar); - virtual bool partiallyComplete(const QList &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 +#include +#include +#include +#include + +#include + +#include + +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 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 &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 +#include +#include +#include + +#include + +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 m_interface; + const QIcon m_variableIcon; + const QIcon m_functionIcon; +}; + + +class ProFileAssistProposalModel : public TextEditor::BasicProposalItemListModel +{ +public: + ProFileAssistProposalModel(const QList &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 #include #include -#include #include #include 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 #include @@ -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(this)->createEditor(); + d->m_editor = const_cast(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 @@ -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 m_codeAssistant; + bool m_assistRelevantContentAdded; QPointer 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 +#include + +#include + +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() && !data().canConvert(); +} + +bool BasicProposalItem::prematurelyApplies(const QChar &c) const +{ + Q_UNUSED(c); + return false; +} + +void BasicProposalItem::apply(BaseTextEditor *editor, int basePosition) const +{ + if (data().canConvert()) + applySnippet(editor, basePosition); + else if (data().canConvert()) + 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(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(); + 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 + +#include +#include + +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 +#include +#include +#include + +#include + +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 &items) + : m_originalItems(items) + , m_currentItems(items) +{ + mapPersistentIds(); +} + +BasicProposalItemListModel::~BasicProposalItemListModel() +{ + qDeleteAll(m_originalItems); +} + +void BasicProposalItemListModel::loadContent(const QList &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 unique; + QList::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::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::iterator, + QList::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 + +#include +#include +#include + +namespace TextEditor { + +class BasicProposalItem; + +class TEXTEDITOR_EXPORT BasicProposalItemListModel : public IGenericProposalModel +{ +public: + BasicProposalItemListModel(); + BasicProposalItemListModel(const QList &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 &items); + +protected: + typedef QList::iterator ItemIterator; + QPair currentItems(); + +private: + void mapPersistentIds(); + + QHash m_idByText; + QList m_originalItems; + QList 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace TextEditor; +using namespace Internal; + +namespace { + +template +void filterEditorSpecificProviders(QList *providers, const QString &editorId) +{ + typename QList::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 m_completionProviders; + QList m_quickFixProviders; + Internal::ProcessorRunner *m_requestRunner; + CompletionAssistProvider *m_requestProvider; + AssistKind m_assistKind; + IAssistProposalWidget *m_proposalWidget; + QScopedPointer 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(); + filterEditorSpecificProviders(&m_completionProviders, m_textEditor->id()); + m_quickFixProviders = + ExtensionSystem::PluginManager::instance()->getObjects(); + 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(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 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(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(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 similarity index 62% rename from src/plugins/cppeditor/cppquickfixcollector.h rename to 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 +#include "assistenums.h" -namespace ExtensionSystem { -class IPlugin; -} +#include + +#include 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 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 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 + +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 + +#include +#include +#include + +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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(e)->key() == Qt::Key_Escape) { + m_d->m_escapePressed = true; + } + break; + case QEvent::KeyPress: + if (static_cast(e)->key() == Qt::Key_Escape) { + m_d->m_escapePressed = true; + } + if (m_d->m_model->size() > 1) { + QKeyEvent *ke = static_cast(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(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(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 + +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 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 +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 + (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(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(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(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(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(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 + +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 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 + +#include + +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 + +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 + +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 + +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 + +#include + +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 + +#include + +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 similarity index 64% rename from src/plugins/texteditor/completionsupport.h rename to 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 -#include #include 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 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 + +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 + +#include + +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 + +#include +#include + +using namespace TextEditor; + +QuickFixAssistProcessor::QuickFixAssistProcessor() +{} + +QuickFixAssistProcessor::~QuickFixAssistProcessor() +{} + +IAssistProposal *QuickFixAssistProcessor::perform(const IAssistInterface *interface) +{ + if (!interface) + return 0; + + QSharedPointer assistInterface(interface); + + const QuickFixAssistProvider *quickFixProvider = + static_cast(provider()); + QMap > matchedOps; + foreach (QuickFixFactory *factory, quickFixProvider->quickFixFactories()) { + QList ops = factory->matchingOperations(assistInterface); + + foreach (QuickFixOperation::Ptr op, ops) { + const int priority = op->priority(); + if (priority != -1) + matchedOps[priority].append(op); + } + } + + QList quickFixes; + QMapIterator > it(matchedOps); + it.toBack(); + if (it.hasPrevious()) { + it.previous(); + quickFixes = it.value(); + } + + if (!quickFixes.isEmpty()) { + QList 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 QuickFixAssistProvider::quickFixFactories() const +{ + return QList(); +} 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 + +namespace TextEditor { + +class QuickFixFactory; + +class TEXTEDITOR_EXPORT QuickFixAssistProvider : public IAssistProvider +{ + Q_OBJECT + +public: + QuickFixAssistProvider(); + virtual ~QuickFixAssistProvider(); + + virtual QList 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 + +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 -#include -#include -#include -#include - -#include -#include - -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 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 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()), - m_completionCollector(0), - m_policy(SemanticCompletion) -{ -} - -QList CompletionSupportPrivate::getCompletions() const -{ - if (m_completionCollector) - return m_completionCollector->getCompletions(); - return QList(); -} - - -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 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 - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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 &items); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - -private: - QList 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 &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 &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(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(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(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 &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 -#include -#include - -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 &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 &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 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 +#include + +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 +#include + +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 -#include - -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 ICompletionCollector::getCompletions() -{ - QList completionItems; - - completions(&completionItems); - - qStableSort(completionItems.begin(), completionItems.end(), completionItemLessThan); - - // Remove duplicates - QString lastKey; - QList 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 &items) -{ - if (! m_d->m_completionSettings.m_partiallyComplete) - return false; - if (items.size() >= 100) - return false; - - QList 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 &completionItems) -{ - qStableSort(completionItems.begin(), completionItems.end(), - &ICompletionCollector::completionItemLessThan); -} - -void ICompletionCollector::filter(const QList &items, - QList *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 -#include -#include - -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 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 *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 &completionItems); - - virtual void sortCompletion(QList &completionItems); - - /* Called when it's safe to clean up the completion items. - */ - virtual void cleanup() = 0; - - // helpers - - void filter(const QList &items, - QList *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 &) - { 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 -#include -#include -#include - -#include 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(editable->widget()); - Q_ASSERT(editor != 0); - - if (TextEditor::QuickFixState *state = initializeCompletion(editor)) { - QMap > matchedOps; - - foreach (QuickFixFactory *factory, quickFixFactories()) { - QList ops = factory->matchingOperations(state); - - foreach (QuickFixOperation::Ptr op, ops) { - const int priority = op->priority(); - if (priority != -1) - matchedOps[priority].append(op); - } - } - - QMapIterator > 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 *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 +#include #include 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 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 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 *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 quickFixFactories() const = 0; - -private: - TextEditor::ITextEditor *m_editor; - QList m_quickFixes; + virtual QList + matchingOperations(const QSharedPointer &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 similarity index 64% rename from src/plugins/texteditor/snippets/snippetcollector.cpp rename to 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 +#include using namespace TextEditor; using namespace Internal; namespace { -void appendSnippets(ICompletionCollector *collector, - QList *completionItems, +void appendSnippets(QList *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 SnippetCollector::getSnippets(ICompletionCollector *collector) const +QList SnippetAssistCollector::collect() const { - QList 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 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 similarity index 81% rename from src/plugins/texteditor/snippets/snippetcollector.h rename to 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 -#include #include #include @@ -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 getSnippets(ICompletionCollector *collector) const; + QList 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 #include @@ -215,17 +215,15 @@ void TextEditorPlugin::initializeEditor(PlainTextEditorWidget *editor) void TextEditorPlugin::invokeCompletion() { Core::IEditor *iface = Core::EditorManager::instance()->currentEditor(); - ITextEditor *editor = qobject_cast(iface); - if (editor) - CompletionSupport::instance()->complete(editor, SemanticCompletion, true); + if (BaseTextEditorWidget *w = qobject_cast(iface->widget())) + w->invokeAssist(Completion); } void TextEditorPlugin::invokeQuickFix() { Core::IEditor *iface = Core::EditorManager::instance()->currentEditor(); - ITextEditor *editor = qobject_cast(iface); - if (editor) - CompletionSupport::instance()->complete(editor, QuickFixCompletion, true); + if (BaseTextEditorWidget *w = qobject_cast(iface->widget())) + w->invokeAssist(QuickFix); } void TextEditorPlugin::updateSearchResultsFont(const FontSettings &settings)