diff --git a/app_templates/django_project/django_project.kdevtemplate b/app_templates/django_project/django_project.kdevtemplate index 3427428f..6a4a9d0c 100644 --- a/app_templates/django_project/django_project.kdevtemplate +++ b/app_templates/django_project/django_project.kdevtemplate @@ -1,41 +1,41 @@ # KDE Config File [General] Name=New Django project Name[ca]=Projecte nou del Django Name[ca@valencia]=Projecte nou del Django Name[cs]=Nový projekt Django Name[de]=Neues Django-Projekt Name[el]=Νέο έργο Django Name[en_GB]=New Django project Name[es]=Nuevo proyecto de Django Name[et]=Uus Django projekt Name[it]=Nuovo progetto Django Name[nl]=Nieuw Django-project Name[pl]=Nowy projekt Django Name[pt]=Novo projecto de Django Name[sk]=Nový projekt Django Name[sl]=Nov projekt Django Name[sv]=Nytt Django-projekt Name[uk]=Новий проект Django Name[x-test]=xxNew Django projectxx Name[zh_CN]=新建 Django 项目 Category=Python Comment=Generate filestructure to start a Django project Comment[ca]=Genera l'estructura de fitxers per a iniciar un projecte Django Comment[ca@valencia]=Genera l'estructura de fitxers per a iniciar un projecte Django Comment[cs]=Vygenerovat strukturu souborů pro započetí projektu Django Comment[de]=Dateistruktur erstellen, auf deren Basis eine Django-Anwendung erstellt werden kann Comment[el]=Παραγωγή δομής αρχείων για την έναρξη έργου Django Comment[en_GB]=Generate filestructure to start a Django project Comment[es]=Generar la estructura de archivos para empezar un proyecto de Django Comment[et]=Failistruktuuri genereerimine Django projekti tarbeks Comment[it]=Genera una struttura di file per inizializzare un progetto Django Comment[nl]=Genereer een bestandsstructuur om een Django-project te starten Comment[pl]=Tworzy strukturę plików do rozpoczęcia projektu Django Comment[pt]=Gerar a estrutura de ficheiros para iniciar um projecto em Django Comment[sk]=Vygenerovať štruktúru súborov na spustenie projektu Django Comment[sl]=Ustvari datotečno zgradbo za začetek projekta Django Comment[sv]=Skapa filstruktur för att påbörja ett Django-program Comment[uk]=Створити початкову структуру файлів проекту Django Comment[x-test]=xxGenerate filestructure to start a Django projectxx -ShowFilesAfterGeneration=%{PROJECTDIR}/urls.py +ShowFilesAfterGeneration=urls.py diff --git a/app_templates/qtdesigner_app/qtdesigner_app.kdevtemplate b/app_templates/qtdesigner_app/qtdesigner_app.kdevtemplate index bae0cc9f..ae48a7a9 100644 --- a/app_templates/qtdesigner_app/qtdesigner_app.kdevtemplate +++ b/app_templates/qtdesigner_app/qtdesigner_app.kdevtemplate @@ -1,37 +1,37 @@ # KDE Config File [General] Name=Simple Qt GUI application using Designer Name[ca]=Aplicació senzilla amb IGU Qt que usa el Designer Name[ca@valencia]=Aplicació senzilla amb IGU Qt que usa el Designer Name[cs]=Jednoduchá aplikace Qt GUI pomocí Designeru Name[el]=Απλή Qt GUI εφαρμογή με χρήση του Designer Name[en_GB]=Simple Qt GUI application using Designer Name[es]=Sencilla aplicación con interfaz gráfica de usuario que usa Designer Name[et]=Lihtne Qt GUI rakendus Designeri abil Name[it]=Semplice applicazione grafica Qt che usa Designer Name[nl]=Eenvoudige Qt-GUI-toepassing met gebruik van Designer Name[pl]=Prosta aplikacja z interfejsem Qt przy użyciu Designera Name[pt]=Aplicação gráfica simples o Qt que usa o Designer Name[sk]=Jednoduchá Qt GUI aplikácia používajúca Designer Name[sl]=Preprost Qt program z grafičnim vmesnikom z uporabo Snovalnika Name[sv]=Enkelt Qt-program med grafiskt användargränssnitt som använder Designer Name[uk]=Проста програма з графічним інтерфейсом Qt, створеним за допомогою Designer Name[x-test]=xxSimple Qt GUI application using Designerxx Category=Python Comment=An example application using Qt Designer. If you edit mainwindow.ui, regenerate mainwindow.py by calling pyuic5. Comment[ca]=Un exemple d'aplicació que usa el Designer de Qt. Si editeu el «mainwindow.ui», regenereu el «mainwindow.py» invocant el «pyuic5». Comment[ca@valencia]=Un exemple d'aplicació que usa el Designer de Qt. Si editeu el «mainwindow.ui», regenereu el «mainwindow.py» invocant el «pyuic5». Comment[el]=Μια εφαρμογή παράδειγμα με χρήση του Qt Designer. Αν ανοίξετε το mainwindow.ui για επεξεργασία, παραγάγετε ξανά το mainwindow.py καλώντας το pyuic5. Comment[en_GB]=An example application using Qt Designer. If you edit mainwindow.ui, regenerate mainwindow.py by calling pyuic5. Comment[es]=Un ejemplo de aplicación que usa Qt Designer. Si edita mainwindow.ui, vuelva a generar mainwindow.py llamando a pyuic5. Comment[et]=Näidisrakendus Qt Designeri abil. Kui redigeerid faili mainwindow.ui, regenereeri mainwindow.py, kutsudes välja pyuic5. Comment[it]=Un'applicazione di esempio che usa Qt Designer. Se modifichi mainwindow.ui, rigenera mainwindow.py eseguendo pyuic5. Comment[nl]=Een voorbeeld toepassing met gebruik van Qt Designer. Als u mainwindow.ui bewerkt, genereer dan opnieuw mainwindow.py door pyuic5 aan te roepen. Comment[pl]=Przykładowa aplikacja wykorzystująca Qt Designera. Po zmianie mainwindow.ui, odśwież mainwindow.py ponownie wywołując pyuic5. Comment[pt]=Uma aplicação de exemplo que usa o Qt Designer. Se editar o 'mainwindow.ui', volte a gerar o 'mainwindow.py' ao invocar o 'pyuic5'. Comment[sk]=Ukážková aplikácia používajúca Qt Designer. Ak upravíte mainwindow.ui, pregenerujte mainwindow.py volaním pyuic5. Comment[sl]=Preprost Qt program z grafičnim vmesnikom z uporabo Snovalnika. Če boste urejali mainwindow.ui, znova ustvarite mainwindow.py s klicem pyuic5. Comment[sv]=Ett exempelprogram som använder Qt-designer. Om du redigerar mainwindo.ui, skapa om mainwindow.py genom att anropa pyuic5. Comment[uk]=Приклад програми із використанням Qt Designer. Після редагування mainwindow.ui вам слід повторно створити mainwindow.py за допомогою виклику pyuic5. Comment[x-test]=xxAn example application using Qt Designer. If you edit mainwindow.ui, regenerate mainwindow.py by calling pyuic5.xx -ShowFilesAfterGeneration=%{PROJECTDIR}/%{APPNAME}.py +ShowFilesAfterGeneration=%{APPNAME}.py diff --git a/app_templates/simple_pythonapp/simple_pythonapp.kdevtemplate b/app_templates/simple_pythonapp/simple_pythonapp.kdevtemplate index 8d9b62e2..fd239001 100644 --- a/app_templates/simple_pythonapp/simple_pythonapp.kdevtemplate +++ b/app_templates/simple_pythonapp/simple_pythonapp.kdevtemplate @@ -1,40 +1,40 @@ # KDE Config File [General] Name=Simple Python Application Name[ca]=Una aplicació Python senzilla Name[ca@valencia]=Una aplicació Python senzilla Name[cs]=Jednoduchá aplikace v Pythonu Name[de]=Eine einfache Python-Anwendung Name[el]=Απλή εφαρμογή Python Name[en_GB]=Simple Python Application Name[es]=Aplicación sencilla en Python Name[et]=Lihtne Pythoni rakendus Name[it]=Semplice applicazione Python Name[nl]=Eenvoudige Python-toepassing Name[pl]=Prosta aplikacja Pythona Name[pt]=Aplicação Simples de Python Name[sk]=Jednoduchá Python aplikácia Name[sl]=Preprost program v Python-u Name[sv]=Enkelt Python-program Name[uk]=Проста програма мовою Python Name[x-test]=xxSimple Python Applicationxx Category=Python Comment=Generate filestructure to start a Python application Comment[ca]=Genera l'estructura de fitxers per a iniciar una aplicació Python Comment[ca@valencia]=Genera l'estructura de fitxers per a iniciar una aplicació Python Comment[cs]=Vygenerovat strukturu souborů pro započetí aplikace v Pythonu Comment[de]=Dateistruktur erstellen, auf deren Basis eine Python-Anwendung erstellt werden kann Comment[el]=Παραγωγή δομής αρχείων για την έναρξη μιας εφαρμογής Python Comment[en_GB]=Generate filestructure to start a Python application Comment[es]=Generar la estructura de archivos para empezar una aplicación en Python Comment[et]=Failistruktuuri genereerimine Pythoni rakenduse tarbeks Comment[it]=Genera una struttura file per avviare un'applicazione in Python Comment[nl]=Genereer een bestandsstructuur om een Python-toepassing te starten Comment[pl]=Tworzy strukturę plików do rozpoczęcia projektu Pythona Comment[pt]=Gerar a estrutura de ficheiros para iniciar uma aplicação em Python Comment[sk]=Vygenerovať štruktúru súborov na spustenie aplikácie Python Comment[sl]=Ustvari datotečno zgradbo za začetek programa v Python-u Comment[sv]=Skapa filstruktur för att påbörja ett Python-program Comment[uk]=Створити початкову файлову структуру програми мовою Python Comment[x-test]=xxGenerate filestructure to start a Python applicationxx -ShowFilesAfterGeneration=%{PROJECTDIR}/%{APPNAMELC}.py +ShowFilesAfterGeneration=%{APPNAME}.py diff --git a/duchain/helpers.cpp b/duchain/helpers.cpp index aca1c2f6..c7346df8 100644 --- a/duchain/helpers.cpp +++ b/duchain/helpers.cpp @@ -1,533 +1,533 @@ /***************************************************************************** * This file is part of KDevelop * * Copyright 2011-2013 Sven Brauch * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************** */ #include "helpers.h" #include #include #include #include #include #include "duchaindebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ast.h" #include "types/hintedtype.h" #include "types/unsuretype.h" #include "types/indexedcontainer.h" #include "kdevpythonversion.h" #include "expressionvisitor.h" using namespace KDevelop; namespace Python { QMap> Helper::cachedCustomIncludes; QMap> Helper::cachedSearchPaths; QVector Helper::projectSearchPaths; QStringList Helper::dataDirs; QString Helper::documentationFile; DUChainPointer Helper::documentationFileContext = DUChainPointer(nullptr); QStringList Helper::correctionFileDirs; QString Helper::localCorrectionFileDir; QMutex Helper::cacheMutex; QMutex Helper::projectPathLock; void Helper::scheduleDependency(const IndexedString& dependency, int betterThanPriority) { BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); bool needsReschedule = true; if ( bgparser->isQueued(dependency) ) { const auto priority= bgparser->priorityForDocument(dependency); if ( priority > betterThanPriority - 1 ) { bgparser->removeDocument(dependency); } else { needsReschedule = false; } } if ( needsReschedule ) { bgparser->addDocument(dependency, TopDUContext::ForceUpdate, betterThanPriority - 1, nullptr, ParseJob::FullSequentialProcessing); } } IndexedDeclaration Helper::declarationUnderCursor(bool allowUse) { KDevelop::IDocument* doc = ICore::self()->documentController()->activeDocument(); const auto view = static_cast(ICore::self()->partController())->activeView(); if ( doc && doc->textDocument() && view ) { DUChainReadLocker lock; const auto cursor = view->cursorPosition(); if ( allowUse ) { return IndexedDeclaration(DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration); } else { return DUChainUtils::declarationInLine(cursor, DUChainUtils::standardContextForUrl(doc->url())); } } return KDevelop::IndexedDeclaration(); } Declaration* Helper::accessAttribute(const AbstractType::Ptr accessed, const IndexedIdentifier& attribute, const TopDUContext* topContext) { if ( ! accessed ) { return nullptr; } // if the type is unsure, search all the possibilities (but return the first match) auto structureTypes = Helper::filterType(accessed, [](AbstractType::Ptr toFilter) { auto type = Helper::resolveAliasType(toFilter); return type && type->whichType() == AbstractType::TypeStructure; }, [](AbstractType::Ptr toMap) { return StructureType::Ptr::staticCast(Helper::resolveAliasType(toMap)); } ); auto docFileContext = Helper::getDocumentationFileContext(); for ( const auto& type: structureTypes ) { auto searchContexts = Helper::internalContextsForClass(type, topContext); for ( const auto ctx: searchContexts ) { auto found = ctx->findDeclarations(attribute, CursorInRevision::invalid(), topContext, DUContext::DontSearchInParent); if ( !found.isEmpty() && ( found.last()->topContext() != docFileContext || ctx->topContext() == docFileContext) ) { // never consider decls from the builtins return found.last(); } } } return nullptr; } AbstractType::Ptr Helper::resolveAliasType(const AbstractType::Ptr eventualAlias) { return TypeUtils::resolveAliasType(eventualAlias); } AbstractType::Ptr Helper::extractTypeHints(AbstractType::Ptr type) { return Helper::foldTypes(Helper::filterType(type, [](AbstractType::Ptr t) -> bool { auto hint = t.cast(); return !hint || hint->isValid(); })); } Helper::FuncInfo Helper::functionForCalled(Declaration* called, bool isAlias) { if ( ! called ) { return { nullptr, false }; } else if ( called->isFunctionDeclaration() ) { return { static_cast( called ), false }; } // If we're calling a type object (isAlias == true), look for a constructor. static const IndexedIdentifier initId(KDevelop::Identifier("__init__")); // Otherwise look for a `__call__()` method. static const IndexedIdentifier callId(KDevelop::Identifier("__call__")); auto attr = accessAttribute(called->abstractType(), (isAlias ? initId : callId), called->topContext()); return { dynamic_cast(attr), isAlias }; } Declaration* Helper::declarationForName(const QualifiedIdentifier& identifier, const RangeInRevision& nodeRange, KDevelop::DUChainPointer context) { QList declarations; QList localDeclarations; QList importedLocalDeclarations; { DUChainReadLocker lock(DUChain::lock()); if ( context.data() == context->topContext() && nodeRange.isValid() ) { declarations = context->topContext()->findDeclarations(identifier, nodeRange.end); } else { declarations = context->topContext()->findDeclarations(identifier, CursorInRevision::invalid()); } localDeclarations = context->findLocalDeclarations(identifier.last(), nodeRange.end, nullptr, AbstractType::Ptr(nullptr), DUContext::DontResolveAliases); importedLocalDeclarations = context->findDeclarations(identifier.last(), nodeRange.end); } Declaration* declaration = nullptr; if ( localDeclarations.length() ) { declaration = localDeclarations.last(); } else if ( importedLocalDeclarations.length() ) { // don't use declarations from class decls, they must be referenced through "self." do { declaration = importedLocalDeclarations.last(); importedLocalDeclarations.pop_back(); if ( !declaration || (declaration->context()->type() == DUContext::Class && context->type() != DUContext::Function) ) { declaration = nullptr; } if ( importedLocalDeclarations.isEmpty() ) { break; } } while ( ! importedLocalDeclarations.isEmpty() ); } if ( !declaration && declarations.length() ) { declaration = declarations.last(); } return declaration; } QVector Helper::internalContextsForClass(const StructureType::Ptr classType, const TopDUContext* context, ContextSearchFlags flags, int depth) { QVector searchContexts; if ( ! classType ) { return searchContexts; } if ( auto c = classType->internalContext(context) ) { searchContexts << c; } auto decl = Helper::resolveAliasDeclaration(classType->declaration(context)); if ( auto classDecl = dynamic_cast(decl) ) { FOREACH_FUNCTION ( const auto& base, classDecl->baseClasses ) { if ( flags == PublicOnly && base.access == KDevelop::Declaration::Private ) { continue; } auto baseClassType = base.baseClass.type(); // recursive call, because the base class will have more base classes eventually if ( depth < 10 ) { searchContexts.append(Helper::internalContextsForClass(baseClassType, context, flags, depth + 1)); } } } return searchContexts; } Declaration* Helper::resolveAliasDeclaration(Declaration* decl) { AliasDeclaration* alias = dynamic_cast(decl); if ( alias ) { DUChainReadLocker lock; return alias->aliasedDeclaration().data(); } else return decl; } QStringList Helper::getDataDirs() { if ( Helper::dataDirs.isEmpty() ) { Helper::dataDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files", QStandardPaths::LocateDirectory); } return Helper::dataDirs; } QString Helper::getDocumentationFile() { if ( Helper::documentationFile.isNull() ) { Helper::documentationFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files/builtindocumentation.py"); } return Helper::documentationFile; } ReferencedTopDUContext Helper::getDocumentationFileContext() { if ( Helper::documentationFileContext ) { return ReferencedTopDUContext(Helper::documentationFileContext.data()); } else { DUChainReadLocker lock; auto file = IndexedString(Helper::getDocumentationFile()); ReferencedTopDUContext ctx = ReferencedTopDUContext(DUChain::self()->chainForDocument(file)); Helper::documentationFileContext = DUChainPointer(ctx.data()); return ctx; } } // stolen from KUrl. duh. static QString _relativePath(const QString &base_dir, const QString &path) { QString _base_dir(QDir::cleanPath(base_dir)); QString _path(QDir::cleanPath(path.isEmpty() || QDir::isRelativePath(path) ? _base_dir+QLatin1Char('/')+path : path)); if (_base_dir.isEmpty()) return _path; if (_base_dir[_base_dir.length()-1] != QLatin1Char('/')) _base_dir.append(QLatin1Char('/') ); const QStringList list1 = _base_dir.split(QLatin1Char('/'), QString::SkipEmptyParts); const QStringList list2 = _path.split(QLatin1Char('/'), QString::SkipEmptyParts); // Find where they meet int level = 0; int maxLevel = qMin(list1.count(), list2.count()); while((level < maxLevel) && (list1[level] == list2[level])) level++; QString result; // Need to go down out of the first path to the common branch. for(int i = level; i < list1.count(); i++) result.append(QLatin1String("../")); // Now up up from the common branch to the second path. for(int i = level; i < list2.count(); i++) result.append(list2[i]).append(QLatin1Char('/')); if ((level < list2.count()) && (path[path.length()-1] != QLatin1Char('/'))) result.truncate(result.length()-1); return result; } QUrl Helper::getCorrectionFile(const QUrl& document) { if ( Helper::correctionFileDirs.isEmpty() ) { Helper::correctionFileDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevpythonsupport/correction_files/", QStandardPaths::LocateDirectory); } foreach (QString correctionFileDir, correctionFileDirs) { foreach ( const QUrl& basePath, Helper::getSearchPaths(QUrl()) ) { if ( ! basePath.isParentOf(document) ) { continue; } auto base = basePath.path(); auto doc = document.path(); auto relative = _relativePath(base, doc); auto fullPath = correctionFileDir + "/" + relative; if ( QFile::exists(fullPath) ) { return QUrl::fromLocalFile(fullPath).adjusted(QUrl::NormalizePathSegments); } } } return {}; } QUrl Helper::getLocalCorrectionFile(const QUrl& document) { if ( Helper::localCorrectionFileDir.isNull() ) { Helper::localCorrectionFileDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "kdevpythonsupport/correction_files/"; } auto absolutePath = QUrl(); foreach ( const auto& basePath, Helper::getSearchPaths(QUrl()) ) { if ( ! basePath.isParentOf(document) ) { continue; } auto path = QDir(basePath.path()).relativeFilePath(document.path()); absolutePath = QUrl::fromLocalFile(Helper::localCorrectionFileDir + path); break; } return absolutePath; } QString Helper::getPythonExecutablePath(IProject* project) { if ( project ) { auto interpreter = project->projectConfiguration()->group("pythonsupport").readEntry("interpreter"); if ( !interpreter.isEmpty() ) { // we have a user-configured interpreter, try using it QFile f(interpreter); if ( f.exists() ) { return interpreter; } qCWarning(KDEV_PYTHON_DUCHAIN) << "Custom python interpreter" << interpreter << "configured for project" << project->name() << "is invalid, using default"; } } // Find python 3 (https://www.python.org/dev/peps/pep-0394/) auto result = QStandardPaths::findExecutable("python" PYTHON_VERSION_STR); if ( ! result.isEmpty() ) { return result; } result = QStandardPaths::findExecutable("python" PYTHON_VERSION_MAJOR_STR); if ( ! result.isEmpty() ) { return result; } result = QStandardPaths::findExecutable("python"); if ( ! result.isEmpty() ) { return result; } #ifdef Q_OS_WIN QStringList extraPaths; // Check for default CPython installation path, because the // installer does not add the path to $PATH. QStringList keys = { "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath", "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath" }; auto version = QString(PYTHON_VERSION_STR); foreach ( QString key, keys ) { key.replace("PYTHON_VERSION", version); QSettings base(key.left(key.indexOf("Python")), QSettings::NativeFormat); if ( ! base.childGroups().contains("Python") ) { continue; } QSettings keySettings(key, QSettings::NativeFormat); auto path = keySettings.value("Default").toString(); if ( ! path.isEmpty() ) { extraPaths << path; break; } } result = QStandardPaths::findExecutable("python", extraPaths); if ( ! result.isEmpty() ) { return result; } #endif // fallback return PYTHON_EXECUTABLE; } QVector Helper::getSearchPaths(const QUrl& workingOnDocument) { QMutexLocker lock(&Helper::cacheMutex); QVector searchPaths; // search in the projects, as they're packages and likely to be installed or added to PYTHONPATH later // and also add custom include paths that are defined in the projects auto project = ICore::self()->projectController()->findProjectForUrl(workingOnDocument); { QMutexLocker lock(&Helper::projectPathLock); searchPaths << Helper::projectSearchPaths; searchPaths << Helper::cachedCustomIncludes.value(project); } foreach ( const QString& path, getDataDirs() ) { searchPaths.append(QUrl::fromLocalFile(path)); } if ( !cachedSearchPaths.contains(project) ) { QVector cachedForProject; qCDebug(KDEV_PYTHON_DUCHAIN) << "*** Collecting search paths..."; QStringList getpath; getpath << "-c" << "import sys; sys.stdout.write('$|$'.join(sys.path))"; QProcess python; python.start(getPythonExecutablePath(project), getpath); python.waitForFinished(1000); QString pythonpath = QString::fromUtf8(python.readAllStandardOutput()); auto paths = pythonpath.split("$|$"); paths.removeAll(""); if ( ! pythonpath.isEmpty() ) { foreach ( const QString& path, paths ) { cachedForProject.append(QUrl::fromLocalFile(path)); } } else { qCWarning(KDEV_PYTHON_DUCHAIN) << "Could not get search paths! Defaulting to stupid stuff."; - searchPaths.append(QUrl::fromLocalFile("/usr/lib/python3.5")); - searchPaths.append(QUrl::fromLocalFile("/usr/lib/python3.5/site-packages")); + searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR)); + searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR "/site-packages")); QString path = qgetenv("PYTHONPATH"); QStringList paths = path.split(':'); foreach ( const QString& path, paths ) { cachedForProject.append(QUrl::fromLocalFile(path)); } } qCDebug(KDEV_PYTHON_DUCHAIN) << " *** Done. Got search paths: " << cachedSearchPaths; cachedSearchPaths.insert(project, cachedForProject); } searchPaths.append(cachedSearchPaths.value(project)); auto dir = workingOnDocument.adjusted(QUrl::RemoveFilename); if ( ! dir.isEmpty() ) { // search in the current packages searchPaths.append(dir); } return searchPaths; } bool Helper::isUsefulType(AbstractType::Ptr type) { return TypeUtils::isUsefulType(type); } AbstractType::Ptr Helper::contentOfIterable(const AbstractType::Ptr iterable, const TopDUContext* topContext) { auto types = filterType(iterable, [](AbstractType::Ptr t) { return t->whichType() == AbstractType::TypeStructure; } ); static const IndexedIdentifier iterId(KDevelop::Identifier("__iter__")); static const IndexedIdentifier nextId(KDevelop::Identifier("__next__")); AbstractType::Ptr content(new IntegralType(IntegralType::TypeMixed)); for ( const auto& type: types ) { if ( auto map = type.cast() ) { // Iterating over dicts gets keys, not values content = mergeTypes(content, map->keyType().abstractType()); continue; } else if ( auto list = type.cast() ) { content = mergeTypes(content, list->contentType().abstractType()); continue; } else if ( auto indexed = type.cast() ) { content = mergeTypes(content, indexed->asUnsureType()); continue; } DUChainReadLocker lock; // Content of an iterable object is iterable.__iter__().__next__(). if ( auto iterFunc = dynamic_cast(accessAttribute(type, iterId, topContext)) ) { if ( auto iterator = iterFunc->type()->returnType().cast() ) { if ( auto nextFunc = dynamic_cast(accessAttribute(iterator, nextId, topContext)) ) { content = mergeTypes(content, nextFunc->type()->returnType()); } } } } return content; } AbstractType::Ptr Helper::mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr newType) { UnsureType::Ptr ret; return TypeUtils::mergeTypes(type, newType); } } diff --git a/parser/astbuilder.cpp b/parser/astbuilder.cpp index 8cbb1c8f..1d02d659 100644 --- a/parser/astbuilder.cpp +++ b/parser/astbuilder.cpp @@ -1,766 +1,766 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * Copyright 2010-2011 Sven Brauch * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "astbuilder.h" #include "ast.h" #include #include #include #include #include #include #include #include #include #include "python_header.h" #include "astdefaultvisitor.h" #include "cythonsyntaxremover.h" #include #include #include #include #include #include "parserdebug.h" using namespace KDevelop; extern grammar _PyParser_Grammar; namespace Python { class NextAstFindVisitor : public AstDefaultVisitor { public: KTextEditor::Cursor findNext(Python::Ast* node) { m_root = node; auto parent = node; while ( parent->parent && parent->parent->isExpression() ) { parent = parent->parent; } visitNode(parent); while ( ! m_next.isValid() && parent->parent ) { // no next expression found in that statement, advance to the next statement parent = parent->parent; visitNode(parent); } return m_next; }; void visitNode(Python::Ast* node) override { if ( ! node ) { return; } AstDefaultVisitor::visitNode(node); if ( node->start() > m_root->start() && ! node->isChildOf(m_root) ) { m_next = (m_next < node->start() && m_next.isValid()) ? m_next : node->start(); } } private: KTextEditor::Cursor m_next{-1, -1}; Ast* m_root; }; // This class is used to fix some of the remaining issues // with the ranges of objects obtained from the python parser. // Issues addressed are: // 1) decorators and "def" / "class" statements for classes / functions // 2) ranges of aliases // Both issues are easy to correct since the possible syntax is very restricted // (no bracket matching, no strings, no nesting, ...) // For the aliases, fortunately only imports and excepthandlers are affected; // the "with" statement, which has more complicated syntax, provides // the necessary information already. // 3) attribute rangees // Not so easy, but when starting from the end of the expression it works okay class RangeFixVisitor : public AstDefaultVisitor { public: RangeFixVisitor(const QString& contents) : lines(contents.split('\n')) { }; void visitNode(Ast* node) override { AstDefaultVisitor::visitNode(node); if ( node && node->parent && node->parent->astType != Ast::AttributeAstType ) { if ( ( node->parent->endLine <= node->endLine && node->parent->endCol <= node->endCol ) || node->parent->endLine < node->endLine ) { node->parent->endLine = node->endLine; node->parent->endCol = node->endCol; } } }; void visitFunctionDefinition(FunctionDefinitionAst* node) override { cutDefinitionPreamble(node->name, node->async ? "asyncdef" : "def"); AstDefaultVisitor::visitFunctionDefinition(node); }; void visitClassDefinition(ClassDefinitionAst* node) override { cutDefinitionPreamble(node->name, "class"); AstDefaultVisitor::visitClassDefinition(node); }; void visitAttribute(AttributeAst* node) override { // Work around the weird way to count columns in Python's AST module. // Find where the next expression (of any kind) behind this one starts NextAstFindVisitor v; auto next_start = v.findNext(node); if ( ! next_start.isValid() ) { // use end of document as reference next_start = {lines.size() - 1, lines.last().size() - 1}; } // take only the portion of the line up to that next expression auto endLine = next_start.line(); auto endCol = next_start.column(); if ( ! (next_start > node->start()) ) { endLine = node->startLine; endCol = -1; } const QString& name(node->attribute->value); QString line; for ( int n = node->startLine, pos = node->value->endCol + 1, dotFound = false, nameFound = false; n <= endLine; ++n, pos = 0 ) { line = lines.at(n); if ( n == endLine && endCol != -1 ) { // Never look at the next expression. line = line.left(endCol); } if ( !dotFound ) { // The real attr name can never be before a dot. // Nor can the start of a comment. // (Don't be misled by `foo["bar"].bar` or `foo["#"].bar`) pos = line.indexOf('.', pos); if ( pos == -1 ) continue; dotFound = true; } if ( !nameFound ) { // Track if the attr name has appeared at least once. // This helps avoid interpreting '#'s in strings as comments - // there can never be a comment before the real attr name. pos = line.indexOf(name, pos + 1); if ( pos == -1 ) continue; nameFound = true; } if ( dotFound && nameFound && (pos = line.indexOf('#', pos + name.length())) != -1) { // Remove the comment after a '#' iff we're certain it can't // be inside a string literal (e.g. `foo["#"].bar`). line = line.left(pos); } // Take the last occurrence, any others are in string literals. pos = line.lastIndexOf(name); if ( pos != -1 ) { node->startLine = n; node->startCol = pos; } // N.B. we do this for all lines, the last non-comment occurrence // is the real one. } // This fails (only, AFAIK) in a very limited case: // If the value expression (`foo` in `foo.bar`) contains a dot, the // attr name, _and_ a hash in that order (may not be consecutive), // and the hash is on the same line as the real attr name, // we wrongly interpret the hash as the start of a comment. // e.g `foo["...barrier#"].bar` will highlight part of the string. node->endLine = node->startLine; node->endCol = node->startCol + name.length() - 1; node->attribute->copyRange(node); AstDefaultVisitor::visitAttribute(node); }; // alias for imports (import foo as bar, baz as bang) // no strings, brackets, or whatever are allowed here, so the "parser" // can be very straightforward. void visitImport(ImportAst* node) override { AstDefaultVisitor::visitImport(node); int aliasIndex = 0; foreach ( AliasAst* alias, node->names ) { fixAlias(alias->name, alias->asName, node->startLine, aliasIndex); aliasIndex += 1; } }; // alias for exceptions (except FooBarException as somethingterriblehappened: ...) void visitExceptionHandler(ExceptionHandlerAst* node) override { AstDefaultVisitor::visitExceptionHandler(node); if ( ! node->name ) { return; } const QString& line = lines.at(node->startLine); const int end = line.count() - 1; int back = backtrackDottedName(line, end); node->name->startCol = end - back; node->name->endCol = end; } void visitString(Python::StringAst* node) override { AstDefaultVisitor::visitString(node); auto match = findString.match(lines.at(node->startLine), node->startCol); if ( match.capturedLength() > 0 ) { node->endCol += match.capturedLength() - 1; // Ranges are inclusive. } } void visitBytes(Python::BytesAst* node) override { AstDefaultVisitor::visitBytes(node); auto match = findString.match(lines.at(node->startLine), node->startCol + 1); if ( match.capturedLength() > 0 ) { node->endCol += match.capturedLength(); // -1 then +1, because of the 'b'. } } void visitFormattedValue(Python::FormattedValueAst * node) override { AstDefaultVisitor::visitFormattedValue(node); auto match = findString.match(lines.at(node->startLine), node->startCol + 1); if ( match.capturedLength() > 0 ) { node->endCol += match.capturedLength(); } } void visitNumber(Python::NumberAst* node) override { AstDefaultVisitor::visitNumber(node); auto match = findNumber.match(lines.at(node->startLine), node->startCol); if ( match.capturedLength() > 0 ) { node->endCol += match.capturedLength() - 1; // Ranges are inclusive. } } // Add one column after the last child to cover the closing bracket: `[1,2,3]` // TODO This is still wrong if the last child is followed by parens or whitespace. // endCol matters most in single-line expressions, so this isn't a huge problem. void visitSubscript(Python::SubscriptAst* node) override { AstDefaultVisitor::visitSubscript(node); node->endCol++; } void visitComprehension(Python::ComprehensionAst* node) override { AstDefaultVisitor::visitComprehension(node); node->endCol++; } void visitList(Python::ListAst* node) override { AstDefaultVisitor::visitList(node); node->endCol++; } void visitTuple(Python::TupleAst* node) override { AstDefaultVisitor::visitTuple(node); node->endCol++; } private: const QStringList lines; QVector dots; KTextEditor::Cursor attributeStart; static const QRegularExpression findString; static const QRegularExpression findNumber; // skip the decorators and the "def" at the beginning // of a class or function declaration and modify @arg node // example: // @decorate(foo) // @decorate(bar) // class myclass(parent): pass // before: start of class->name is [0, 0] // after: start of class->name is [2, 5] // line continuation characters are not supported, // because code needing those in this case is not worth being supported. void cutDefinitionPreamble(Ast* fixNode, const QString& defKeyword) { if ( ! fixNode ) { return; } int currentLine = fixNode->startLine; // cut away decorators while ( currentLine < lines.size() ) { if ( lines.at(currentLine).trimmed().remove(' ').remove('\t').startsWith(defKeyword) ) { // it's not a decorator, so stop skipping lines. break; } currentLine += 1; } // qDebug() << "FIX:" << fixNode->range(); fixNode->startLine = currentLine; fixNode->endLine = currentLine; // qDebug() << "FIXED:" << fixNode->range() << fixNode->astType; // cut away the "def" / "class" int currentColumn = -1; if ( currentLine > lines.size() ) { // whops? return; } const QString& lineData = lines.at(currentLine); bool keywordFound = false; while ( currentColumn < lineData.size() - 1 ) { currentColumn += 1; if ( lineData.at(currentColumn).isSpace() ) { // skip space at the beginning of the line continue; } else if ( keywordFound ) { // if the "def" / "class" was already found, and the current char is // non space, then this is indeed the start of the identifier we're looking for. break; } else { keywordFound = true; currentColumn += defKeyword.size(); } } const int previousLength = fixNode->endCol - fixNode->startCol; fixNode->startCol = currentColumn; fixNode->endCol = currentColumn + previousLength; }; int backtrackDottedName(const QString& data, const int start) { bool haveDot = true; bool previousWasSpace = true; for ( int i = start - 1; i >= 0; i-- ) { if ( data.at(i).isSpace() ) { previousWasSpace = true; continue; } if ( data.at(i) == ':' ) { // excepthandler continue; } if ( data.at(i) == '.' ) { haveDot = true; } else if ( haveDot ) { haveDot = false; previousWasSpace = false; continue; } if ( previousWasSpace && ! haveDot ) { return start-i-2; } previousWasSpace = false; } return 0; } void fixAlias(Ast* dotted, Ast* asname, const int startLine, int aliasIndex) { if ( ! asname && ! dotted ) { return; } QString line = lines.at(startLine); int lineno = startLine; for ( int i = 0; i < line.size(); i++ ) { const QChar& current = line.at(i); if ( current == '\\' ) { // line continuation character // splitting like "import foo as \ \n bar" is not supported. lineno += 1; line = lines.at(lineno); i = 0; continue; } if ( current == ',' ) { if ( aliasIndex == 0 ) { // nothing found, continue below line = line.left(i); break; } // next alias expression aliasIndex -= 1; } if ( i > line.length() - 3 ) { continue; } if ( current.isSpace() && line.mid(i+1).startsWith("as") && ( line.at(i+3).isSpace() || line.at(i+3) == '\\' ) ) { // there's an "as" if ( aliasIndex == 0 ) { // it's the one we're looking for // find the expression if ( dotted ) { int dottedNameLength = backtrackDottedName(line, i); dotted->startLine = lineno; dotted->endLine = lineno; dotted->startCol = i-dottedNameLength; dotted->endCol = i; } // find the asname if ( asname ) { bool atStart = true; int textStart = i+3; for ( int j = i+3; j < line.size(); j++ ) { if ( atStart && ! line.at(j).isSpace() ) { atStart = false; textStart = j; } if ( ! atStart && ( line.at(j).isSpace() || j == line.size() - 1 ) ) { // found it asname->startLine = lineno; asname->endLine = lineno; asname->startCol = textStart - 1; asname->endCol = j; } } } return; } } } // no "as" found, use last dotted name in line const int end = line.count() - whitespaceAtEnd(line); int back = backtrackDottedName(line, end); dotted->startLine = lineno; dotted->endLine = lineno; dotted->startCol = end - back; dotted->endCol = end; }; int whitespaceAtEnd(const QString& line) { - for ( int i = 0; i <= line.size(); i++ ) { + for ( int i = 0; i < line.size(); i++ ) { if ( ! line.at(line.size() - i - 1).isSpace() ) { return i; } } return 0; }; }; // FIXME This doesn't work for triple-quoted strings // (it gives length 2, which is no worse than before). const QRegularExpression RangeFixVisitor::findString = QRegularExpression("\\G(['\"]).*?(?(PyObject_Str(obj), pyObjectCleanup); const auto str = strOwner.get(); if (PyUnicode_READY(str) < 0) { qWarning("PyUnicode_READY(%p) returned false!", (void*)str); return QString(); } const auto length = PyUnicode_GET_LENGTH(str); switch(PyUnicode_KIND(str)) { case PyUnicode_1BYTE_KIND: return QString::fromLatin1((const char*)PyUnicode_1BYTE_DATA(str), length); case PyUnicode_2BYTE_KIND: return QString::fromUtf16(PyUnicode_2BYTE_DATA(str), length); case PyUnicode_4BYTE_KIND: return QString::fromUcs4(PyUnicode_4BYTE_DATA(str), length); case PyUnicode_WCHAR_KIND: qWarning("PyUnicode_KIND(%p) returned PyUnicode_WCHAR_KIND, this should not happen!", (void*)str); return QString::fromWCharArray(PyUnicode_AS_UNICODE(str), length); } Q_UNREACHABLE(); } QPair fileHeaderHack(QString& contents, const QUrl& filename) { IProject* proj = ICore::self()->projectController()->findProjectForUrl(filename); // the file is not in a project, don't apply hack if ( ! proj ) { return QPair(contents, 0); } const QUrl headerFileUrl = QUrl::fromLocalFile(proj->path().path() + "/.kdev_python_header"); QFile headerFile(headerFileUrl.path()); QString headerFileContents; if ( headerFile.exists() ) { headerFile.open(QIODevice::ReadOnly); headerFileContents = headerFile.readAll(); headerFile.close(); qCDebug(KDEV_PYTHON_PARSER) << "Found header file, applying hack"; int insertAt = 0; bool endOfCommentsReached = false; bool commentSignEncountered = false; // bool atLineBeginning = true; int lastLineBeginning = 0; int newlineCount = 0; int l = contents.length(); do { if ( insertAt >= l ) { qCDebug(KDEV_PYTHON_PARSER) << "File consist only of comments, not applying hack"; return QPair(contents, 0); } if ( contents.at(insertAt) == '#' ) { commentSignEncountered = true; } if ( !contents.at(insertAt).isSpace() ) { // atLineBeginning = false; if ( !commentSignEncountered ) { endOfCommentsReached = true; } } if ( contents.at(insertAt) == '\n' ) { // atLineBeginning = true; commentSignEncountered = false; lastLineBeginning = insertAt; newlineCount += 1; } if ( newlineCount == 2 ) { endOfCommentsReached = true; } insertAt += 1; } while ( !endOfCommentsReached ); qCDebug(KDEV_PYTHON_PARSER) << "Inserting contents at char" << lastLineBeginning << "of file"; contents = contents.left(lastLineBeginning) + "\n" + headerFileContents + "\n#\n" + contents.right(contents.length() - lastLineBeginning); qCDebug(KDEV_PYTHON_PARSER) << contents; return QPair(contents, - ( headerFileContents.count('\n') + 3 )); } else { return QPair(contents, 0); } } namespace { struct PythonInitializer : private QMutexLocker { PythonInitializer(QMutex& pyInitLock): QMutexLocker(&pyInitLock), arena(nullptr) { Py_InitializeEx(0); Q_ASSERT(Py_IsInitialized()); arena = PyArena_New(); Q_ASSERT(arena); // out of memory } ~PythonInitializer() { if (arena) PyArena_Free(arena); if (Py_IsInitialized()) Py_Finalize(); } PyArena* arena; }; } CodeAst::Ptr AstBuilder::parse(const QUrl& filename, QString &contents) { qCDebug(KDEV_PYTHON_PARSER) << " ====> AST ====> building abstract syntax tree for " << filename.path(); Py_NoSiteFlag = 1; contents.append('\n'); QPair hacked = fileHeaderHack(contents, filename); contents = hacked.first; int lineOffset = hacked.second; PythonInitializer pyIniter(pyInitLock); PyArena* arena = pyIniter.arena; PyCompilerFlags flags = {PyCF_SOURCE_IS_UTF8 | PyCF_IGNORE_COOKIE}; PyObject *exception, *value, *backtrace; PyErr_Fetch(&exception, &value, &backtrace); CythonSyntaxRemover cythonSyntaxRemover; if (filename.fileName().endsWith(".pyx", Qt::CaseInsensitive)) { qCDebug(KDEV_PYTHON_PARSER) << filename.fileName() << "is probably Cython file."; contents = cythonSyntaxRemover.stripCythonSyntax(contents); } mod_ty syntaxtree = PyParser_ASTFromString(contents.toUtf8().data(), "", file_input, &flags, arena); if ( ! syntaxtree ) { qCDebug(KDEV_PYTHON_PARSER) << " ====< parse error, trying to fix"; PyErr_Fetch(&exception, &value, &backtrace); qCDebug(KDEV_PYTHON_PARSER) << "Error objects: " << exception << value << backtrace; if ( ! value ) { qCWarning(KDEV_PYTHON_PARSER) << "Internal parser error: exception value is null, aborting"; return CodeAst::Ptr(); } PyObject_Print(value, stderr, Py_PRINT_RAW); PyObject* errorMessage_str = PyTuple_GetItem(value, 0); PyObject* errorDetails_tuple = PyTuple_GetItem(value, 1); if ( ! errorDetails_tuple ) { qCWarning(KDEV_PYTHON_PARSER) << "Error retrieving error message, not displaying, and not doing anything"; return CodeAst::Ptr(); } PyObject* linenoobj = PyTuple_GetItem(errorDetails_tuple, 1); errorMessage_str = PyTuple_GetItem(value, 0); errorDetails_tuple = PyTuple_GetItem(value, 1); PyObject_Print(errorMessage_str, stderr, Py_PRINT_RAW); PyObject* colnoobj = PyTuple_GetItem(errorDetails_tuple, 2); int lineno = PyLong_AsLong(linenoobj) - 1; int colno = PyLong_AsLong(colnoobj); ProblemPointer p(new Problem()); KTextEditor::Cursor start(lineno + lineOffset, (colno-4 > 0 ? colno-4 : 0)); KTextEditor::Cursor end(lineno + lineOffset, (colno+4 > 4 ? colno+4 : 4)); KTextEditor::Range range(start, end); qCDebug(KDEV_PYTHON_PARSER) << "Problem range: " << range; DocumentRange location(IndexedString(filename.path()), range); p->setFinalLocation(location); p->setDescription(PyUnicodeObjectToQString(errorMessage_str)); p->setSource(IProblem::Parser); m_problems.append(p); // try to recover. // Currently the following is tired: // * If the last non-space char before the error reported was ":", it's most likely an indent error. // The common easy-to-fix and annoying indent error is "for item in foo: ". In that case, just add "pass" after the ":" token. // * If it's not, we will just comment the line with the error, fixing problems like "foo = ". // * If both fails, everything including the first non-empty line before the one with the error will be deleted. int len = contents.length(); int currentLine = 0; QString currentLineContents; QChar c; QChar newline('\n'); int emptySince = 0; int emptySinceLine = 0; int emptyLinesSince = 0; int emptyLinesSinceLine = 0; unsigned short currentLineIndent = 0; bool atLineBeginning = true; QList indents; int errline = qMax(0, lineno); int currentLineBeginning = 0; for ( int i = 0; i < len; i++ ) { c = contents.at(i); if ( ! c.isSpace() ) { emptySince = i; emptySinceLine = currentLine; atLineBeginning = false; if ( indents.length() <= currentLine ) indents.append(currentLineIndent); } else if ( c == newline ) { if ( currentLine == errline ) { atLineBeginning = false; } else { currentLine += 1; currentLineBeginning = i+1; // this line has had content, so reset the "empty lines since" counter if ( ! atLineBeginning ) { // lastNonemptyLineBeginning = emptyLinesSince; emptyLinesSince = i; emptyLinesSinceLine = currentLine; } atLineBeginning = true; if ( indents.length() <= currentLine ) indents.append(currentLineIndent); currentLineIndent = 0; } } else if ( atLineBeginning ) { currentLineIndent += 1; } if ( currentLine == errline && ! atLineBeginning ) { // if the last non-empty char before the error opens a new block, it's likely an "empty block" problem // we can easily fix that by adding in a "pass" statement. However, we want to add that in the next line, if possible // so context ranges for autocompletion stay intact. if ( contents[emptySince] == QChar(':') ) { qCDebug(KDEV_PYTHON_PARSER) << indents.length() << emptySinceLine + 1 << indents; if ( indents.length() > emptySinceLine + 1 && indents.at(emptySinceLine) < indents.at(emptySinceLine + 1) ) { qCDebug(KDEV_PYTHON_PARSER) << indents.at(emptySinceLine) << indents.at(emptySinceLine + 1); contents.insert(emptyLinesSince + 1 + indents.at(emptyLinesSinceLine), "\tpass#"); } else { contents.insert(emptySince + 1, "\tpass#"); } } else if ( indents.length() >= currentLine && currentLine > 0 ) { qCDebug(KDEV_PYTHON_PARSER) << indents << currentLine; contents[i+1+indents.at(currentLine - 1)] = QChar('#'); contents.insert(i+1+indents.at(currentLine - 1), "pass"); } break; } } syntaxtree = PyParser_ASTFromString(contents.toUtf8(), "", file_input, &flags, arena); // 3rd try: discard everything after the last non-empty line, but only until the next block start currentLineBeginning = qMin(contents.length() - 1, currentLineBeginning); errline = qMax(0, qMin(indents.length()-1, errline)); if ( ! syntaxtree ) { qCWarning(KDEV_PYTHON_PARSER) << "Discarding parts of the code to be parsed because of previous errors"; qCDebug(KDEV_PYTHON_PARSER) << indents; int indentAtError = indents.at(errline); QChar c; bool atLineBeginning = true; int currentIndent = -1; int currentLineBeginning_end = currentLineBeginning; int currentLineContentBeginning = currentLineBeginning; for ( int i = currentLineBeginning; i < len; i++ ) { c = contents.at(i); qCDebug(KDEV_PYTHON_PARSER) << c; if ( c == '\n' ) { if ( currentIndent <= indentAtError && currentIndent != -1 ) { qCDebug(KDEV_PYTHON_PARSER) << "Start of error code: " << currentLineBeginning; qCDebug(KDEV_PYTHON_PARSER) << "End of error block (current position): " << currentLineBeginning_end; qCDebug(KDEV_PYTHON_PARSER) << "Length: " << currentLineBeginning_end - currentLineBeginning; qCDebug(KDEV_PYTHON_PARSER) << "indent at error <> current indent:" << indentAtError << "<>" << currentIndent; // contents.remove(currentLineBeginning, currentLineBeginning_end-currentLineBeginning); break; } contents.insert(currentLineContentBeginning - 1, "pass#"); i += 5; i = qMin(i, contents.length()); len = contents.length(); atLineBeginning = true; currentIndent = 0; currentLineBeginning_end = i + 1; currentLineContentBeginning = i + 1; continue; } if ( ! c.isSpace() && atLineBeginning ) { currentLineContentBeginning = i; atLineBeginning = false; } if ( c.isSpace() && atLineBeginning ) currentIndent += 1; } qCDebug(KDEV_PYTHON_PARSER) << "This is what is left: " << contents; syntaxtree = PyParser_ASTFromString(contents.toUtf8(), "", file_input, &flags, arena); } if ( ! syntaxtree ) { return CodeAst::Ptr(); // everything fails, so we abort. } } qCDebug(KDEV_PYTHON_PARSER) << "Got syntax tree from python parser:" << syntaxtree->kind << Module_kind; PythonAstTransformer t(lineOffset); t.run(syntaxtree, filename.fileName().replace(".py", "")); RangeFixVisitor fixVisitor(contents); fixVisitor.visitNode(t.ast); cythonSyntaxRemover.fixAstRanges(t.ast); return CodeAst::Ptr(t.ast); } }