diff --git a/codecompletion/CMakeLists.txt b/codecompletion/CMakeLists.txt index 1231c12..2cdef3d 100644 --- a/codecompletion/CMakeLists.txt +++ b/codecompletion/CMakeLists.txt @@ -4,6 +4,8 @@ include_directories( ) set(completion_SRCS + keyworditem.cpp + functiondeclarationcompletionitem.cpp importfileitem.cpp pythoncodecompletioncontext.cpp pythoncodecompletionmodel.cpp diff --git a/codecompletion/functiondeclarationcompletionitem.cpp b/codecompletion/functiondeclarationcompletionitem.cpp new file mode 100644 index 0000000..bd8aecd --- /dev/null +++ b/codecompletion/functiondeclarationcompletionitem.cpp @@ -0,0 +1,38 @@ + +#include +#include +#include + +#include +#include + +#include "functiondeclarationcompletionitem.h" +#include "navigation/navigationwidget.h" + +using namespace KDevelop; +using namespace KTextEditor; + +namespace Python { + +FunctionDeclarationCompletionItem::FunctionDeclarationCompletionItem(DeclarationPointer decl) : NormalDeclarationCompletionItem(decl) { } + +void FunctionDeclarationCompletionItem::executed(KTextEditor::Document* document, const KTextEditor::Range& word) +{ + kDebug() << "FunctionDeclarationCompletionItem executed"; + DUChainPointer decl = declaration().dynamicCast(); + Q_ASSERT(decl.data()); + kDebug() << "declaration data: " << decl.data(); + const QString suffix = "()"; + int skip = 2; // place cursor behind bracktes + if ( decl.data()->defaultParametersSize() != 0 ) { + skip = 1; // place cursor in brackets if there's parameters + } + document->replaceText(word, decl.data()->identifier().toString() + suffix); + if ( View* view = document->activeView() ) { + view->setCursorPosition( Cursor(word.end().line(), word.end().column() + skip) ); + } +} + +FunctionDeclarationCompletionItem::~FunctionDeclarationCompletionItem() { } + +} \ No newline at end of file diff --git a/codecompletion/functiondeclarationcompletionitem.h b/codecompletion/functiondeclarationcompletionitem.h new file mode 100644 index 0000000..2efa1a8 --- /dev/null +++ b/codecompletion/functiondeclarationcompletionitem.h @@ -0,0 +1,23 @@ +#ifndef FUNCTIONDECLARATIONCOMPLETIONITEM_H +#define FUNCTIONDECLARATIONCOMPLETIONITEM_H + +#include +#include + +using namespace KDevelop; + +namespace Python { + +class FunctionDeclarationCompletionItem : public KDevelop::NormalDeclarationCompletionItem +{ + +public: + FunctionDeclarationCompletionItem(DeclarationPointer decl); + virtual ~FunctionDeclarationCompletionItem(); + + virtual void executed(KTextEditor::Document* document, const KTextEditor::Range& word); +}; + +} + +#endif // FUNCTIONDECLARATIONCOMPLETIONITEM_H diff --git a/codecompletion/keyworditem.cpp b/codecompletion/keyworditem.cpp new file mode 100644 index 0000000..0ed0c13 --- /dev/null +++ b/codecompletion/keyworditem.cpp @@ -0,0 +1,47 @@ +#include "keyworditem.h" +#include +#include +#include +#include +#include + +using namespace KDevelop; +using namespace KTextEditor; + +namespace Python { + +Python::KeywordItem::KeywordItem(KDevelop::CodeCompletionContext::Ptr context, QString keyword) : NormalDeclarationCompletionItem ( DeclarationPointer(), context, 0 ) +{ + m_keyword = keyword; +} + +void Python::KeywordItem::execute ( KTextEditor::Document* document, const KTextEditor::Range& word ) +{ + document->replaceText(word, m_keyword); +} + +QVariant KeywordItem::data ( const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model ) const +{ + switch (role) { + case KDevelop::CodeCompletionModel::IsExpandable: + return QVariant(false); + case Qt::DisplayRole: + if (index.column() == KTextEditor::CodeCompletionModel::Name) { + return QVariant(m_keyword); + } else { + return QVariant(""); + } + break; + case KTextEditor::CodeCompletionModel::ItemSelected: + return QVariant(""); + case KTextEditor::CodeCompletionModel::InheritanceDepth: + return QVariant(0); + default: + //pass + break; + } + + return NormalDeclarationCompletionItem::data(index, role, model); +} + +} diff --git a/codecompletion/keyworditem.h b/codecompletion/keyworditem.h new file mode 100644 index 0000000..488153d --- /dev/null +++ b/codecompletion/keyworditem.h @@ -0,0 +1,22 @@ +#ifndef KEYWORDITEM_H +#define KEYWORDITEM_H + +#include + +using namespace KDevelop; + +namespace Python { + +class KeywordItem : public NormalDeclarationCompletionItem +{ + +public: + KeywordItem(KDevelop::CodeCompletionContext::Ptr context, QString keyword); + virtual void execute ( KTextEditor::Document* document, const KTextEditor::Range& word ); + virtual QVariant data ( const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model ) const; + QString m_keyword; +}; + +} + +#endif // KEYWORDITEM_H diff --git a/codecompletion/pythoncodecompletioncontext.cpp b/codecompletion/pythoncodecompletioncontext.cpp index 3336495..61b0a25 100644 --- a/codecompletion/pythoncodecompletioncontext.cpp +++ b/codecompletion/pythoncodecompletioncontext.cpp @@ -6,9 +6,9 @@ #include "pythoncodecompletioncontext.h" -#include #include #include +#include #include #include @@ -16,6 +16,8 @@ #include "navigation/navigationwidget.h" #include "importfileitem.h" +#include "functiondeclarationcompletionitem.h" + #include #include #include @@ -23,6 +25,10 @@ #include #include +#include +#include +#include "keyworditem.h" + using namespace KDevelop; typedef QPair DeclarationDepthPair; @@ -31,6 +37,9 @@ namespace Python { QList PythonCodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { + if ( abort ) + return QList(); + QList items; DUChainReadLocker lock(DUChain::lock()); @@ -51,16 +60,40 @@ QList PythonCodeCompletionContext::completionItems(bo items << CompletionTreeItemPointer( item ); } } + else if ( m_operation == PythonCodeCompletionContext::MemberAccessCompletion ) { + // we don't have type support, so we cannot support completing mebers yet. But we can at least prevent kdevelop from opening a pointless + // popup with completion items you don't want + } else { - QList declarations = m_duContext->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext()); + // it's stupid to display a 3-letter completion item on manually invoked code completion and makes everything look crowded + if ( m_operation == PythonCodeCompletionContext::NewStatementCompletion && ! fullCompletion ) { + QStringList keywordItems; + keywordItems << "def" << "class" << "lambda" << "global" << "print"; + foreach ( const QString& current, keywordItems ) { + items << CompletionTreeItemPointer(new KeywordItem(KDevelop::CodeCompletionContext::Ptr(this), current)); + } + } + if ( abort ) { + return QList(); + } + QList declarations = m_duContext->allDeclarations(m_position, m_duContext->topContext()); - Declaration* currentDeclaration; + DeclarationPointer currentDeclaration; int count = declarations.length(); for ( int i = 0; i < count; i++ ) { - currentDeclaration = declarations.at(i).first; - kDebug() << "Adding item: " << currentDeclaration->identifier().identifier().str(); - DeclarationPointer ptr(currentDeclaration); - NormalDeclarationCompletionItem* item = new NormalDeclarationCompletionItem(ptr, KDevelop::CodeCompletionContext::Ptr(this)); + if ( abort ) { + return items; + } + currentDeclaration = DeclarationPointer(declarations.at(i).first); + kDebug() << "Adding item: " << currentDeclaration.data()->identifier().identifier().str(); + NormalDeclarationCompletionItem* item; + if ( currentDeclaration.data()->abstractType() && currentDeclaration.data()->abstractType().constData()->whichType() == AbstractType::TypeFunction ) { + kDebug() << "Adding function declaration item"; + item = new FunctionDeclarationCompletionItem(currentDeclaration); + } + else { + item = new NormalDeclarationCompletionItem(currentDeclaration, KDevelop::CodeCompletionContext::Ptr(this)); + } kDebug() << item->declaration().data()->identifier().identifier().str(); items << CompletionTreeItemPointer(item); } @@ -207,6 +240,14 @@ PythonCodeCompletionContext::PythonCodeCompletionContext(DUContextPointer contex return; } + QRegExp newStatementCompletion("(.*)\n[\\s]*$"); + newStatementCompletion.setMinimal(true); + bool isNewStatementCompletion = newStatementCompletion.exactMatch(currentLine); + if ( isNewStatementCompletion ) { + m_operation = PythonCodeCompletionContext::NewStatementCompletion; + return; + } + QRegExp importfile("(.*)\n[\\s]*import[\\s]*$"); importfile.setMinimal(true); bool is_importfile = importfile.exactMatch(currentLine); @@ -218,6 +259,14 @@ PythonCodeCompletionContext::PythonCodeCompletionContext(DUContextPointer contex return; } + QRegExp attributeAccess("(.*)\n[\\s]*(.*)\\.$"); + attributeAccess.setMinimal(true); + bool is_attributeAccess = attributeAccess.exactMatch(currentLine); + if ( is_attributeAccess ) { + m_operation = PythonCodeCompletionContext::MemberAccessCompletion; + return; + } + QRegExp noCompletionPossible("(.*)\n[\\s]*(class|def)[\\s]*$"); noCompletionPossible.setMinimal(true); bool is_noCompletionPossible = noCompletionPossible.exactMatch(currentLine); diff --git a/codecompletion/pythoncodecompletioncontext.h b/codecompletion/pythoncodecompletioncontext.h index 5af62a3..45123f8 100644 --- a/codecompletion/pythoncodecompletioncontext.h +++ b/codecompletion/pythoncodecompletioncontext.h @@ -24,7 +24,8 @@ class KDEVPYTHONCOMPLETION_EXPORT PythonCodeCompletionContext : public KDevelop: MemberAccessCompletion, DefaultCompletion, ImportSubCompletion, - NoCompletion + NoCompletion, + NewStatementCompletion }; PythonCodeCompletionContext(DUContextPointer context, const QString& text, const KDevelop::CursorInRevision& position, int depth); diff --git a/codecompletion/pythoncodecompletionworker.cpp b/codecompletion/pythoncodecompletionworker.cpp index e367eb9..bd6abbd 100644 --- a/codecompletion/pythoncodecompletionworker.cpp +++ b/codecompletion/pythoncodecompletionworker.cpp @@ -17,7 +17,7 @@ PythonCodeCompletionWorker::PythonCodeCompletionWorker(PythonCodeCompletionModel } -KDevelop::CodeCompletionContext* PythonCodeCompletionWorker::createCompletionContext(KDevelop::DUContextPointer context, const QString& contextText, const QString& followingText, const KDevelop::CursorInRevision& position) const +KDevelop::CodeCompletionContext* PythonCodeCompletionWorker::createCompletionContext(KDevelop::DUContextPointer context, const QString& contextText, const QString& /*followingText*/, const KDevelop::CursorInRevision& position) const { PythonCodeCompletionContext* completionContext = new PythonCodeCompletionContext(context, contextText, position, 0); return completionContext; diff --git a/duchain/contextbuilder.cpp b/duchain/contextbuilder.cpp index ed27bc6..da166a9 100644 --- a/duchain/contextbuilder.cpp +++ b/duchain/contextbuilder.cpp @@ -103,7 +103,7 @@ void ContextBuilder::setEditor(PythonEditorIntegrator* editor) ContextBuilder::m_editor = editor; } -void ContextBuilder::setEditor(ParseSession* session) +void ContextBuilder::setEditor(ParseSession* /*session*/) { PythonEditorIntegrator* e = new PythonEditorIntegrator(/*session*/); //m_identifierCompiler = new IdentifierCompiler(e->parseSession()); @@ -147,7 +147,7 @@ void ContextBuilder::addImportedContexts() } } -void ContextBuilder::openContextForStatementList( const QList& l, DUContext::ContextType type) +void ContextBuilder::openContextForStatementList( const QList& l, DUContext::ContextType /*type*/) { if ( l.count() > 0 ) { @@ -229,7 +229,7 @@ void ContextBuilder::visitImport(ImportAst* node) { foreach ( AliasAst* name, node->names ) { // for "import ... as", use the as thingy, use the module name otherwise - Identifier* variableDeclarationName = name->asName ? name->asName->identifier : name->name; +// Identifier* variableDeclarationName = name->asName ? name->asName->identifier : name->name; # TODO check this KUrl moduleFilePath = findModulePath(name->name->value); if ( ! moduleFilePath.isValid() ) continue; diff --git a/duchain/declarationbuilder.cpp b/duchain/declarationbuilder.cpp index 1d53452..f77ced2 100644 --- a/duchain/declarationbuilder.cpp +++ b/duchain/declarationbuilder.cpp @@ -216,7 +216,7 @@ void DeclarationBuilder::visitAssignment(AssignmentAst* node) void DeclarationBuilder::visitClassDefinition( ClassDefinitionAst* node ) { kDebug() << "opening class definition"; - ClassDeclaration* classDec = new ClassDeclaration(editorFindRange(node->body.first(), node->body.last()), currentContext()); +// ClassDeclaration* classDec = new ClassDeclaration(editorFindRange(node->body.first(), node->body.last()), currentContext()); openDeclaration( node->name, node ); eventuallyAssignInternalContext(); diff --git a/example_ast.py b/example_ast.py new file mode 100644 index 0000000..68b3113 --- /dev/null +++ b/example_ast.py @@ -0,0 +1,180 @@ +a = a and a +a = a or b + +def some_class(foo, bar): + attr1 = 3 + attr2 = 5 + attr3 = 'str' + +some_instance = some_class() +some_instance.attr1 +some_instance.attr2 +some_instance.some_method() + +#comment +""" +multiline comment +foo +bar +""" + +import sys +import random + +import PyQt4.QtCore + +print sys + +def simple_func(foo): + # usage comment, bla, param:foo + pass + +def function(foo): + """ docstring + more + more more + >>> test + >>> test + """ + return foo + +try: + pass +except Exception as e: + print e + +def func(foo, bar, baz, bang, foobang, foobar, foobazbar, foobazbarbang): + return foobang + print foo + print foobazbarbang + + if foobazbar < 5: + pass + +func(sys) +simple_func() + +def func_without_param(): + pass + +func_without_param() + +def another_function(param): + print param + +a = 5 + +bar = a == a +a != a +a < a +a <= a +a > a +a >= a +a is a +a is not a +a not in a +a in a + +a = a + 1 +a = a - 1 +a = a * 1 +a = a / 1 +a = a % 1 +a = a ^ 1 +a = a & 1 +a = a | 1 +a = a ** 1 +a = a >> 1 +a = a << 1 + +a = not a +a = +a +a = -a +a = ~a + +a = b[1:2:3][2] +extended = a[1:2, 2:3] + +i += 3 +i += j + +print 3 if 5 < 7 else 4 + +from random import random + +print random + +random(foo=3) + +a = lambda x: x**2 + +@staticmethod +@classmethod +def genfunc(): + yield foo + +for target1, target2 in some_dict.iteritems(): + print target1, target2 + +pi = 3.1415 + +foo = 1, 2 +bar = (1, 2) + +with open('f') as foo: + pass + +global IMAGLOBALVARIABLE +IMAGLOBALVARIABLE = 0 +try: + a = 3 / 0 +except ZeroDivisionError as err: + raise ValueError +else: + do_something() +finally: + BAM + +if 3 and 5: + pass + +for i in xrange(20): + pass + +while True: + break + continue + +del foo + +import random +random.random(3, 5) + +somelist = [1, 2, 3, 4, 5] +somedict = { 'key1' : 'value1', key2: value2 } + +print somelist[...] +print somelist[1:] +print somelist[:20] +print somelist[1:20] +print somelist[1:20:2] + +class bar(parent): + pass + +if foo in bar and 3 < 5: + pass + +a = [x*2 for x in xrange(20)] + +variable.variable2.variable3 = 15 +def function(param1, param2, param3, *paramstar, **paramdstar): + pass + return param1 * param2 + +assert False +if not 3: + pass + +if 3 * 5 == 7: + pass diff --git a/example_ast.xml b/example_ast.xml new file mode 100644 index 0000000..fb10791 --- /dev/null +++ b/example_ast.xml @@ -0,0 +1,990 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/parser/ast.cpp b/parser/ast.cpp index ae04f59..6eeeca3 100644 --- a/parser/ast.cpp +++ b/parser/ast.cpp @@ -136,7 +136,7 @@ ExceptionHandlerAst::ExceptionHandlerAst(Ast* parent): Ast(parent, Ast::Exceptio } -ExecAst::ExecAst(Ast* parent): StatementAst(parent, Ast::ExecAstType), body(0), locals(0), globals(0) +ExecAst::ExecAst(Ast* parent): StatementAst(parent, Ast::ExecAstType), body(0), globals(0), locals(0) { } diff --git a/parser/astbuilder.cpp b/parser/astbuilder.cpp index 49b60b8..f5303eb 100644 --- a/parser/astbuilder.cpp +++ b/parser/astbuilder.cpp @@ -138,7 +138,7 @@ CodeAst* AstBuilder::parseXmlAst(QString xml) return codeAst; } -void AstBuilder::parseXmlAstNode(QXmlStreamReader* xmlast, QXmlStreamReader::TokenType token = QXmlStreamReader::Invalid) { +void AstBuilder::parseXmlAstNode(QXmlStreamReader* xmlast, QXmlStreamReader::TokenType /*token = QXmlStreamReader::Invalid*/) { bool nodeAdded = false; while ( ! xmlast->atEnd() && ! xmlast->hasError() ) { @@ -165,7 +165,7 @@ void AstBuilder::parseXmlAstNode(QXmlStreamReader* xmlast, QXmlStreamReader::Tok } // this will push a parent onto the stack - nodeAdded = parseAstNode(currentElementName, currentElementText, currentElementAttributes); + nodeAdded = parseAstNode(currentElementName, /*currentElementText,*/ currentElementAttributes); // we might need ElementText some day if ( ! nodeAdded ) { m_isRealNodeMap.append(false); continue; @@ -198,7 +198,7 @@ void AstBuilder::parseXmlAstNode(QXmlStreamReader* xmlast, QXmlStreamReader::Tok } } -bool AstBuilder::parseAstNode(QString name, QString text, const QList< QXmlStreamAttribute >& attributes) +bool AstBuilder::parseAstNode(QString name, /*QString text, */ const QList< QXmlStreamAttribute >& attributes) { Ast* ast; diff --git a/parser/astbuilder.h b/parser/astbuilder.h index 4441405..478d444 100644 --- a/parser/astbuilder.h +++ b/parser/astbuilder.h @@ -53,7 +53,7 @@ class AstBuilder CodeAst* parseXmlAst(QString xml); QString getXmlForFile(KUrl filename, const QString& contents); void parseXmlAstNode(QXmlStreamReader* xmlast, QXmlStreamReader::TokenType token); - bool parseAstNode(QString name, QString text, const QList& attributes); + bool parseAstNode(QString name, /*QString text, */const QList& attributes); KDevelop::TopDUContext* m_topContext; diff --git a/parser/pythondriver.cpp b/parser/pythondriver.cpp index 15c4556..9bfe9c4 100644 --- a/parser/pythondriver.cpp +++ b/parser/pythondriver.cpp @@ -70,7 +70,7 @@ void Driver::setCurrentDocument(KUrl url) m_currentDocument = url; } -QPair Driver::parse( Python::CodeAst* ast ) +QPair Driver::parse( Python::CodeAst* /* ast */) { AstBuilder pythonparser; QPair matched; diff --git a/pythonparsejob.cpp b/pythonparsejob.cpp index 7287502..2d12b63 100644 --- a/pythonparsejob.cpp +++ b/pythonparsejob.cpp @@ -148,18 +148,18 @@ void ParseJob::run() m_duContext = builder.build(filename, m_ast); setDuChain(m_duContext); - { + UseBuilder usebuilder( &editor ); + usebuilder.buildUses(m_ast); + + { DUChainWriteLocker lock(DUChain::lock()); +// m_duContext->clearProblems(); ParsingEnvironmentFilePointer parsingEnvironmentFile = m_duContext->parsingEnvironmentFile(); parsingEnvironmentFile->clearModificationRevisions(); parsingEnvironmentFile->setModificationRevision(contents().modification); DUChain::self()->updateContextEnvironment(m_duContext, parsingEnvironmentFile.data()); - //m_duContext->clearProblems(); } - UseBuilder usebuilder( &editor ); - usebuilder.buildUses(m_ast); - kDebug() << "----Parsing Succeded---***"; if ( m_parent && m_parent->codeHighlighting() ) { @@ -173,29 +173,31 @@ void ParseJob::run() { kWarning() << "===Failed==="; DUChainWriteLocker lock; - { - m_duContext = DUChain::self()->chainForDocument(document()); + m_duContext = DUChain::self()->chainForDocument(document()); + if ( m_duContext ) { + m_duContext->parsingEnvironmentFile()->clearModificationRevisions(); + m_duContext->clearProblems(); } - if ( ! m_duContext ) { + else { ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document()); static const IndexedString langString("python"); file->setLanguage(langString); m_duContext = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(m_duContext); } - { - m_duContext->parsingEnvironmentFile()->clearModificationRevisions(); - m_duContext->parsingEnvironmentFile()->setModificationRevision(contents().modification); - m_duContext->clearProblems(); - DUChain::self()->updateContextEnvironment(m_duContext, m_duContext->parsingEnvironmentFile().data()); - } - + foreach ( ProblemPointer p, m_session->m_problems ) { kDebug() << "Added problem to context"; m_duContext->addProblem(p); } setDuChain(m_duContext); } + +// DUChainWriteLocker lock(DUChain::lock()); +// if ( ! DUChain::self()->chainForDocument(document()) && m_duContext ) { +// DUChain::self()->addDocumentChain(m_duContext); +// } + } ParseSession *ParseJob::parseSession() const