diff --git a/app_templates/django_project/__init__.py b/app_templates/django_project/%{APPNAMELC}/__init__.py similarity index 100% copy from app_templates/django_project/__init__.py copy to app_templates/django_project/%{APPNAMELC}/__init__.py diff --git a/app_templates/django_project/%{APPNAMELC}/settings.py b/app_templates/django_project/%{APPNAMELC}/settings.py new file mode 100644 index 00000000..e0f37f43 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}/settings.py @@ -0,0 +1,120 @@ +""" +Django settings for %{APPNAMELC} project. + +Generated by 'django-admin startproject' using Django 1.11.1. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'zp82u25sz6vwxp$%8rdfirpn#ml+=%-f%059h-54#7@kx7-4jp' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = '%{APPNAMELC}.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = '%{APPNAMELC}.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/app_templates/django_project/%{APPNAMELC}/urls.py b/app_templates/django_project/%{APPNAMELC}/urls.py new file mode 100644 index 00000000..b84226e8 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}/urls.py @@ -0,0 +1,21 @@ +"""%{APPNAMELC} URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), +] diff --git a/app_templates/django_project/%{APPNAMELC}/wsgi.py b/app_templates/django_project/%{APPNAMELC}/wsgi.py new file mode 100644 index 00000000..cd9715c1 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for %{APPNAMELC} project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "%{APPNAMELC}.settings") + +application = get_wsgi_application() diff --git a/app_templates/django_project/__init__.py b/app_templates/django_project/%{APPNAMELC}_site/__init__.py similarity index 100% copy from app_templates/django_project/__init__.py copy to app_templates/django_project/%{APPNAMELC}_site/__init__.py diff --git a/app_templates/django_project/%{APPNAMELC}_site/admin.py b/app_templates/django_project/%{APPNAMELC}_site/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}_site/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app_templates/django_project/%{APPNAMELC}_site/apps.py b/app_templates/django_project/%{APPNAMELC}_site/apps.py new file mode 100644 index 00000000..1e17caf8 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}_site/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class %{APPNAMEID}SiteConfig(AppConfig): + name = '%{APPNAMELC}_site' diff --git a/app_templates/django_project/__init__.py b/app_templates/django_project/%{APPNAMELC}_site/migrations/__init__.py similarity index 100% rename from app_templates/django_project/__init__.py rename to app_templates/django_project/%{APPNAMELC}_site/migrations/__init__.py diff --git a/app_templates/django_project/%{APPNAMELC}_site/models.py b/app_templates/django_project/%{APPNAMELC}_site/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}_site/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/app_templates/django_project/%{APPNAMELC}_site/tests.py b/app_templates/django_project/%{APPNAMELC}_site/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}_site/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app_templates/django_project/%{APPNAMELC}_site/views.py b/app_templates/django_project/%{APPNAMELC}_site/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/app_templates/django_project/%{APPNAMELC}_site/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/app_templates/django_project/django_project.kdevtemplate b/app_templates/django_project/django_project.kdevtemplate index 5de89d2e..dd5bbb55 100644 --- a/app_templates/django_project/django_project.kdevtemplate +++ b/app_templates/django_project/django_project.kdevtemplate @@ -1,40 +1,20 @@ # 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[en_GB]=New Django project -Name[es]=Nuevo proyecto de Django -Name[et]=Uus Django projekt -Name[fr]=Nouveau projet Django -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[sv]=Nytt Django-projekt -Name[tr]=Yeni Django Projesi -Name[uk]=Новий проект Django -Name[x-test]=xxNew Django projectxx +Name=Django 1.11 +Name[ca]=Django 1.11 +Name[ca@valencia]=Django 1.11 +Name[nl]=Django 1.11 +Name[pt]=Django 1.11 +Name[sv]=Django 1.11 +Name[uk]=Django 1.11 +Name[x-test]=xxDjango 1.11xx 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[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[fr]=Générer la structure de fichier pour démarrer un projet Django -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[sv]=Skapa filstruktur för att påbörja ett Django-program -Comment[tr]=bir Django projesi başlatmak için dosya yapısı oluştur -Comment[uk]=Створити початкову структуру файлів проекту Django -Comment[x-test]=xxGenerate filestructure to start a Django projectxx -ShowFilesAfterGeneration=urls.py +Comment=A Django project with one application +Comment[ca]=Un projecte de Django amb una aplicació +Comment[ca@valencia]=Un projecte de Django amb una aplicació +Comment[nl]=Een Django-project met één toepassing +Comment[pt]=um projecto de Django com uma aplicação +Comment[sv]=Ett Django-projekt med ett program +Comment[uk]=Проект Django із однією програмою +Comment[x-test]=xxA Django project with one applicationxx +ShowFilesAfterGeneration=%{PROJECTDIR}/%{APPNAMELC}/urls.py diff --git a/app_templates/django_project/manage.py b/app_templates/django_project/manage.py old mode 100644 new mode 100755 index 3e4eedc9..74fe1e05 --- a/app_templates/django_project/manage.py +++ b/app_templates/django_project/manage.py @@ -1,14 +1,22 @@ #!/usr/bin/env python -from django.core.management import execute_manager -import imp -try: - imp.find_module('settings') # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) - sys.exit(1) - -import settings +import os +import sys if __name__ == "__main__": - execute_manager(settings) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "%{APPNAMELC}.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/app_templates/django_project/settings.py b/app_templates/django_project/settings.py deleted file mode 100644 index 1bee9d76..00000000 --- a/app_templates/django_project/settings.py +++ /dev/null @@ -1,145 +0,0 @@ -# Django settings for %{APPNAME} project. - -DEBUG = True -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': '', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# On Unix systems, a value of None will cause Django to use the same -# timezone as the operating system. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale -USE_L10N = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = '' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" -MEDIA_URL = '' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" -STATIC_ROOT = '' - -# URL prefix for static files. -# Example: "http://media.lawrence.com/static/" -STATIC_URL = '/static/' - -# URL prefix for admin static files -- CSS, JavaScript and images. -# Make sure to use a trailing slash. -# Examples: "http://foo.com/static/admin/", "/static/admin/". -ADMIN_MEDIA_PREFIX = '/static/admin/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 's0y^h+^*n=ci-8l1spnka0y&9n6o<9me8c-g21gfi+uflg93' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', -) - -ROOT_URLCONF = '%{APPNAME}.urls' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', -) - -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} diff --git a/app_templates/django_project/urls.py b/app_templates/django_project/urls.py deleted file mode 100644 index 35949b98..00000000 --- a/app_templates/django_project/urls.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.conf.urls.defaults import patterns, include, url - -# Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() - -urlpatterns = patterns('', - # Examples: - # url(r'^$', '%{APPNAME}.views.home', name='home'), - # url(r'^%{APPNAME}/', include('%{APPNAME}.foo.urls')), - - # Uncomment the admin/doc line below to enable admin documentation: - # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - - # Uncomment the next line to enable the admin: - # url(r'^admin/', include(admin.site.urls)), -) diff --git a/codecompletion/items/functiondeclaration.cpp b/codecompletion/items/functiondeclaration.cpp index a686d257..2abccfb9 100644 --- a/codecompletion/items/functiondeclaration.cpp +++ b/codecompletion/items/functiondeclaration.cpp @@ -1,185 +1,186 @@ /***************************************************************************** * Copyright (c) 2011 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 "functiondeclaration.h" #include #include #include #include #include #include #include #include #include #include "duchain/navigation/navigationwidget.h" #include "codecompletion/helpers.h" #include "declaration.h" #include "declarations/functiondeclaration.h" #include "duchain/helpers.h" #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { FunctionDeclarationCompletionItem::FunctionDeclarationCompletionItem(DeclarationPointer decl, CodeCompletionContext::Ptr context) : PythonDeclarationCompletionItem(decl, context) , m_atArgument(-1) , m_depth(0) , m_doNotCall(false) { } int FunctionDeclarationCompletionItem::atArgument() const { return m_atArgument; } void FunctionDeclarationCompletionItem::setDepth(int d) { m_depth = d; } void FunctionDeclarationCompletionItem::setAtArgument(int d) { m_atArgument = d; } int FunctionDeclarationCompletionItem::argumentHintDepth() const { return m_depth; } QVariant FunctionDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock; FunctionDeclaration* dec = dynamic_cast(m_declaration.data()); switch ( role ) { case Qt::DisplayRole: { if ( ! dec ) { break; // use the default } if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if (FunctionType::Ptr functionType = dec->type()) { QString ret; createArgumentList(dec, ret, nullptr, 0, false); return ret; } } if ( index.column() == KDevelop::CodeCompletionModel::Prefix ) { FunctionType::Ptr type = dec->type(); if ( type && type->returnType() ) { return i18n("function") + " -> " + type->returnType()->toString(); } } break; } case KDevelop::CodeCompletionModel::HighlightingMethod: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) return QVariant(KDevelop::CodeCompletionModel::CustomHighlighting); break; } case KDevelop::CodeCompletionModel::CustomHighlight: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if ( ! dec ) return QVariant(); QString ret; QList highlight; if ( atArgument() ) { createArgumentList(dec, ret, &highlight, atArgument(), false); } else { createArgumentList(dec, ret, nullptr, false); } return QVariant(highlight); } + break; } case KDevelop::CodeCompletionModel::MatchQuality: { if ( m_typeHint == PythonCodeCompletionContext::IterableRequested && dec && dec->type() && dynamic_cast(dec->type()->returnType().data()) ) { return 2 + PythonDeclarationCompletionItem::data(index, role, model).toInt(); } return PythonDeclarationCompletionItem::data(index, role, model); } } return Python::PythonDeclarationCompletionItem::data(index, role, model); } void FunctionDeclarationCompletionItem::setDoNotCall(bool doNotCall) { m_doNotCall = doNotCall; } void FunctionDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { qCDebug(KDEV_PYTHON_CODECOMPLETION) << "FunctionDeclarationCompletionItem executed"; KTextEditor::Document* document = view->document(); auto resolvedDecl = Helper::resolveAliasDeclaration(declaration().data()); DUChainReadLocker lock; auto functionDecl = Helper::functionForCalled(resolvedDecl).declaration; lock.unlock(); if ( ! functionDecl && (! resolvedDecl || ! resolvedDecl->abstractType() || resolvedDecl->abstractType()->whichType() != AbstractType::TypeStructure) ) { qCritical(KDEV_PYTHON_CODECOMPLETION) << "ERROR: could not get declaration data, not executing completion item!"; return; } QString suffix = "()"; KTextEditor::Range checkPrefix(word.start().line(), 0, word.start().line(), word.start().column()); KTextEditor::Range checkSuffix(word.end().line(), word.end().column(), word.end().line(), document->lineLength(word.end().line())); if ( m_doNotCall || document->text(checkSuffix).trimmed().startsWith('(') || document->text(checkPrefix).trimmed().endsWith('@') || (functionDecl && functionDecl->isProperty()) ) { // don't insert brackets if they're already there, // the item is a decorator, or if it's an import item. suffix.clear(); } // place cursor behind bracktes by default int skip = 2; if ( functionDecl ) { bool needsArguments = false; int argumentCount = functionDecl->type()->arguments().length(); if ( functionDecl->context()->type() == KDevelop::DUContext::Class ) { // it's a member function, so it has the implicit self // TODO static methods needsArguments = argumentCount > 1; } else { // it's a free function needsArguments = argumentCount > 0; } if ( needsArguments ) { // place cursor in brackets if there's parameters skip = 1; } } document->replaceText(word, declaration()->identifier().toString() + suffix); view->setCursorPosition( Cursor(word.end().line(), word.end().column() + skip) ); } FunctionDeclarationCompletionItem::~FunctionDeclarationCompletionItem() { } } diff --git a/codecompletion/items/implementfunction.cpp b/codecompletion/items/implementfunction.cpp index 5c619b43..0fb14c6d 100644 --- a/codecompletion/items/implementfunction.cpp +++ b/codecompletion/items/implementfunction.cpp @@ -1,86 +1,87 @@ /***************************************************************************** * Copyright (c) 2011 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 "implementfunction.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { ImplementFunctionCompletionItem::ImplementFunctionCompletionItem(const QString& name, const QStringList& arguments, const QString& previousIndent) : m_arguments(arguments), m_name(name), m_previousIndent(previousIndent) { } void ImplementFunctionCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { auto document = view->document(); const QString finalText = m_name + "(" + m_arguments.join(", ") + "):"; document->replaceText(word, finalText); // 4 spaces is indentation for python. everyone does it like this. you must, too. // TODO use kate settings document->insertLine(word.start().line() + 1, m_previousIndent + " "); if ( View* view = static_cast(ICore::self()->partController())->activeView() ) { view->setCursorPosition(Cursor(word.end().line() + 1, m_previousIndent.length() + 4)); } } QVariant ImplementFunctionCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { switch ( role ) { case KDevelop::CodeCompletionModel::MatchQuality: { return QVariant(m_name.startsWith("__") ? 0 : 10); } case KDevelop::CodeCompletionModel::BestMatchesCount: { return QVariant(5); } case Qt::DisplayRole: switch ( index.column() ) { case KDevelop::CodeCompletionModel::Name: return m_name + "(" + m_arguments.join(", ") + ")"; case KDevelop::CodeCompletionModel::Postfix: return ""; case KDevelop::CodeCompletionModel::Prefix: return "Override method"; default: return ""; } case Qt::DecorationRole: if( index.column() == KDevelop::CodeCompletionModel::Icon ) { KDevelop::CodeCompletionModel::CompletionProperties p(KDevelop::CodeCompletionModel::Function); return DUChainUtils::iconForProperties(p); } + // Fall through default: return CompletionTreeItem::data(index, role, model); } } } // namespace Python diff --git a/codegen/correctionfilegenerator.cpp b/codegen/correctionfilegenerator.cpp index 75229716..bb79a25d 100644 --- a/codegen/correctionfilegenerator.cpp +++ b/codegen/correctionfilegenerator.cpp @@ -1,506 +1,507 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2013 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 "correctionfilegenerator.h" #include #include #include #include #include #include #include #include #include #include #include #include "duchain/helpers.h" #include "parser/codehelpers.h" #include #include #include "codegendebug.h" using namespace KDevelop; namespace Python { TypeCorrection::TypeCorrection() : m_ui(new Ui::CorrectionWidget) { } TypeCorrection &TypeCorrection::self() { static TypeCorrection instance; return instance; } void TypeCorrection::doContextMenu(ContextMenuExtension &extension, Context *context) { if ( DeclarationContext* declContext = dynamic_cast(context) ) { qRegisterMetaType("KDevelop::IndexedDeclaration"); DUChainReadLocker lock; KDevelop::Declaration* declaration = declContext->declaration().data(); if ( declaration && (declaration->kind() == Declaration::Instance || (declaration->kind() == Declaration::Type && declaration->abstractType()->whichType() == AbstractType::TypeFunction)) ) { QAction* action = new QAction(i18n("Specify type for \"%1\"...", declaration->qualifiedIdentifier().toString()), nullptr); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme("code-class")); connect(action, &QAction::triggered, this, &TypeCorrection::executeSpecifyTypeAction); extension.addAction(ContextMenuExtension::ExtensionGroup, action); } } } void TypeCorrection::executeSpecifyTypeAction() { QAction* action = qobject_cast(sender()); if ( ! action ) { qCWarning(KDEV_PYTHON_CODEGEN) << "slot not invoked by triggering a QAction, should not happen"; // :) return; } DUChainReadLocker lock; IndexedDeclaration decl = action->data().value(); if ( ! decl.isValid() ) { decl = Helper::declarationUnderCursor(); } if ( ! decl.isValid() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "No declaration found!"; return; } CorrectionFileGenerator::HintType hintType; if ( decl.data()->isFunctionDeclaration() ) { hintType = CorrectionFileGenerator::FunctionReturnHint; } else if ( decl.data()->kind() == Declaration::Instance ) { hintType = CorrectionFileGenerator::LocalVariableHint; } else { qCWarning(KDEV_PYTHON_CODEGEN) << "Correction requested for something that's not a local variable or function."; return; } CorrectionAssistant *dialog = new CorrectionAssistant(decl, hintType); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle("Specify type for " + decl.data()->identifier().toString()); connect(dialog, &QDialog::accepted, this, &TypeCorrection::accepted); m_ui->setupUi(dialog); connect(m_ui->buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if ( hintType == CorrectionFileGenerator::FunctionReturnHint ) { m_ui->kindLabel->setText(i18n("Function return type")); } else if ( hintType == CorrectionFileGenerator::LocalVariableHint ) { m_ui->kindLabel->setText(i18n("Local variable")); } m_ui->identifierLabel->setText(decl.data()->qualifiedIdentifier().toString()); m_ui->typeText->setFocus(); dialog->resize(560, 180); lock.unlock(); dialog->show(); } void TypeCorrection::accepted() { CorrectionAssistant *dialog = qobject_cast(sender()); Q_ASSERT(dialog); if ( ! dialog ) { qCWarning(KDEV_PYTHON_CODEGEN) << "accepted() called without a sender"; return; } DUChainReadLocker lock; IndexedDeclaration decl; decl = dialog->declaration(); if ( ! decl.isValid() ) { decl = Helper::declarationUnderCursor(); } if ( ! decl.isValid() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "No declaration found!"; return; } auto correctionFile = Helper::getLocalCorrectionFile(decl.data()->topContext()->url().toUrl()); if ( correctionFile.isEmpty() ) { KMessageBox::error(nullptr, i18n("Sorry, cannot create hints for files which are not part of a project.")); return; } CorrectionFileGenerator generator(correctionFile.path()); CorrectionFileGenerator::HintType hintType = dialog->hintType(); generator.addHint(m_ui->typeText->text(), m_ui->importsText->text().split(',', QString::SkipEmptyParts), decl.data(), hintType); qCDebug(KDEV_PYTHON_CODEGEN) << "Forcing a reparse on " << decl.data()->topContext()->url(); ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(decl.data()->topContext()->url()), TopDUContext::ForceUpdate); ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(correctionFile), TopDUContext::ForceUpdate); } CorrectionFileGenerator::CorrectionFileGenerator(const QString &filePath) : m_file(filePath) , m_filePath(filePath) { Q_ASSERT(! filePath.isEmpty()); qCDebug(KDEV_PYTHON_CODEGEN) << "Correction file path: " << filePath; QFileInfo info(m_file); if ( ! info.absoluteDir().exists() ) { qCDebug(KDEV_PYTHON_CODEGEN) << "Directory does not exist. Creating..."; info.absoluteDir().mkpath(info.absolutePath()); } m_file.open(QFile::ReadWrite); m_code = QString(m_file.readAll()).split('\n'); m_oldContents = m_code; m_fileIndents.reset(new FileIndentInformation(m_code)); } void CorrectionFileGenerator::addHint(const QString &typeCode, const QStringList &modules, Declaration *forDeclaration, CorrectionFileGenerator::HintType hintType) { if ( ! forDeclaration || ! forDeclaration->context() ) { qCWarning(KDEV_PYTHON_CODEGEN) << "Declaration does not have context!" << (forDeclaration ? forDeclaration->toString() : ""); return; } DUContext* context = forDeclaration->context(); if ( context->type() == DUContext::Function ) { auto otherImporters = context->importers(); if ( otherImporters.isEmpty() ) { return; } context = otherImporters.first(); } // We're in a class if the context of the declaration is a Class or if its // parent context is a class. This is because a function body has a context // of type Other. bool inClass = context->type() == DUContext::Class || (context->parentContext() && context->parentContext()->type() == DUContext::Class); // If the declaration is part of the function's arguments or it's parent // context is one of a function. bool inFunction = context->type() == DUContext::Function || (context->owner() && context->owner()->abstractType()->whichType() == AbstractType::TypeFunction); qCDebug(KDEV_PYTHON_CODEGEN) << "Are we in a class: " << inClass; qCDebug(KDEV_PYTHON_CODEGEN) << "Are we in a function: " << inFunction; QString enclosingClassIdentifier, enclosingFunctionIdentifier; if ( context->owner() ) { if ( inClass && inFunction ) { Declaration *functionDeclaration = context->owner(); enclosingClassIdentifier = functionDeclaration->context()->owner()->identifier().identifier().str(); enclosingFunctionIdentifier = functionDeclaration->identifier().identifier().str(); } else if ( inClass ) { enclosingClassIdentifier = context->owner()->identifier().identifier().str(); } else if ( inFunction ) { enclosingFunctionIdentifier = context->owner()->identifier().identifier().str(); } } qCDebug(KDEV_PYTHON_CODEGEN) << "Enclosing class: " << enclosingClassIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Enclosing function: " << enclosingFunctionIdentifier; QString declarationIdentifier = forDeclaration->identifier().identifier().str(); bool foundClassDeclaration = false; bool foundFunctionDeclaration = false; QString functionIdentifier; if ( hintType == FunctionReturnHint ) { functionIdentifier = declarationIdentifier; } else if ( hintType == LocalVariableHint ) { functionIdentifier = enclosingFunctionIdentifier; } int line = findStructureFor(enclosingClassIdentifier, functionIdentifier); if ( line == -1 ) { line = findStructureFor(enclosingClassIdentifier, QString()); } else if ( inFunction || hintType == FunctionReturnHint ) { foundFunctionDeclaration = true; } if ( line == -1 ) { line = findStructureFor(QString(), QString()); } else if ( inClass ) { foundClassDeclaration = true; } qCDebug(KDEV_PYTHON_CODEGEN) << "Found class declaration: " << foundClassDeclaration << enclosingClassIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Found function declaration: " << foundFunctionDeclaration << functionIdentifier; qCDebug(KDEV_PYTHON_CODEGEN) << "Line: " << line; int indentsForNextStatement = m_fileIndents->indentForLine(line); if ( foundClassDeclaration ) { indentsForNextStatement += DEFAULT_INDENT_LEVEL; } QStringList newCode; if ( inClass ) { if ( ! foundClassDeclaration ) { QString classDeclaration = createStructurePart(enclosingClassIdentifier, ClassType); classDeclaration.prepend(QString(indentsForNextStatement, ' ')); newCode.append(classDeclaration); indentsForNextStatement += DEFAULT_INDENT_LEVEL; } else { line++; } } if ( inFunction || hintType == FunctionReturnHint ) { if ( ! foundFunctionDeclaration ) { QString functionDeclaration; if ( inClass ) { functionDeclaration = createStructurePart(functionIdentifier, MemberFunctionType); } else { functionDeclaration = createStructurePart(functionIdentifier, FunctionType); } functionDeclaration.prepend(QString(indentsForNextStatement, ' ')); newCode.append(functionDeclaration); indentsForNextStatement += DEFAULT_INDENT_LEVEL; } else { line++; } } if ( foundFunctionDeclaration && ! foundClassDeclaration ) { indentsForNextStatement += DEFAULT_INDENT_LEVEL; } QString hintCode; if ( hintType == FunctionReturnHint ) { hintCode = "returns = " + typeCode; } else if ( hintType == LocalVariableHint ) { hintCode = "l_" + declarationIdentifier + " = " + typeCode; } qCDebug(KDEV_PYTHON_CODEGEN) << "Hint code: " << hintCode; hintCode.prepend(QString(indentsForNextStatement, ' ')); newCode.append(hintCode); for ( int i = 0; i < newCode.length(); i++ ) { m_code.insert(line + i, newCode.at(i)); } // We safely insert any import declaration at the top foreach ( const QString &moduleName, modules ) { bool importExists = false; foreach (const QString &line, m_code) { if ( ! line.startsWith("import") && ! line.startsWith("from") && ! line.isEmpty() ) { break; } // In both import ... and from ... import ..., the second part is what we want if ( line.section(' ', 1, 1, QString::SectionSkipEmpty) == moduleName.trimmed() ) { importExists = true; } } if ( ! importExists ) { m_code.prepend("import " + moduleName.trimmed()); } } QTemporaryFile temp; if ( checkForValidSyntax() && temp.open() ) { qCDebug(KDEV_PYTHON_CODEGEN) << "File path: " << m_file.fileName(); qCDebug(KDEV_PYTHON_CODEGEN) << "Temporary file path: " << temp.fileName(); QTextStream stream(&temp); stream << m_code.join("\n"); m_fileIndents.reset(new FileIndentInformation(m_code)); stream.flush(); bool success = m_file.remove(); success = success ? QFile::rename(temp.fileName(), m_file.fileName()) : false; if ( success && m_file.open(QFile::ReadWrite) ) { qCDebug(KDEV_PYTHON_CODEGEN) << "Successfully saved correction file."; m_oldContents = m_code; } else { m_code = m_oldContents; } } else { qCDebug(KDEV_PYTHON_CODEGEN) << "Something went wrong, reverting changes to correction file"; m_code = m_oldContents; } } class StructureFindVisitor : public Python::AstDefaultVisitor { public: StructureFindVisitor(const QString &klass, const QString &function) : m_line(-1) { if ( ! klass.isNull() ) { m_searchedDeclaration.push(klass); } if ( ! function.isNull() ) { m_searchedDeclaration.push(function); } } void visitFunctionDefinition(FunctionDefinitionAst* node) override { m_declaration.push(node->name->value); if ( m_declaration == m_searchedDeclaration ) { m_line = node->startLine; } AstDefaultVisitor::visitFunctionDefinition(node); m_declaration.pop(); } void visitClassDefinition(ClassDefinitionAst* node) override { m_declaration.push(node->name->value); if ( m_declaration == m_searchedDeclaration ) { m_line = node->startLine; } AstDefaultVisitor::visitClassDefinition(node); m_declaration.pop(); } int line() const { return m_line; } private: QStack m_searchedDeclaration; QStack m_declaration; int m_line; }; int CorrectionFileGenerator::findStructureFor(const QString &klass, const QString &function) { // If we're not looking for a specific thing, return the end of the file if ( klass.isNull() && function.isNull() ) { return m_code.length() - 1; } ParseSession parseSession; parseSession.setContents(m_code.join("\n")); parseSession.setCurrentDocument(IndexedString(m_filePath)); QPair parsed = parseSession.parse(); QString classIdentifier = ( ! klass.isNull() ) ? "class_" + klass : QString(); QString functionIdentifier = ( ! function.isNull() ) ? "function_" + function : QString(); StructureFindVisitor visitor(classIdentifier, functionIdentifier); visitor.visitCode(parsed.first.data()); return visitor.line(); } QString CorrectionFileGenerator::createStructurePart(const QString &identifierSuffix, CorrectionFileGenerator::StructureType type) { QString code; QString params; switch ( type ) { case ClassType: code = "class class_" + identifierSuffix + ":"; break; case MemberFunctionType: params = "self"; + // Fall through case FunctionType: code = "def function_" + identifierSuffix + "(" + params + "):"; break; } return code; } bool CorrectionFileGenerator::checkForValidSyntax() { ParseSession parseSession; parseSession.setContents(m_code.join("\n")); parseSession.setCurrentDocument(IndexedString(m_filePath)); QPair parsed = parseSession.parse(); return parsed.second && parseSession.m_problems.isEmpty(); } CorrectionAssistant::CorrectionAssistant(IndexedDeclaration declaration, CorrectionFileGenerator::HintType hintType, QWidget *parent) : QDialog(parent), m_declaration(declaration), m_hintType(hintType) { } IndexedDeclaration CorrectionAssistant::declaration() const { return m_declaration; } CorrectionFileGenerator::HintType CorrectionAssistant::hintType() const { return m_hintType; } } diff --git a/duchain/usebuilder.cpp b/duchain/usebuilder.cpp index c52f16f5..e7807f3d 100644 --- a/duchain/usebuilder.cpp +++ b/duchain/usebuilder.cpp @@ -1,180 +1,178 @@ /***************************************************************************** * Copyright (c) 2007 Piyush verma * * Copyright 2010-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 "usebuilder.h" #include #include "duchaindebug.h" #include #include #include #include #include #include #include "parsesession.h" #include "pythoneditorintegrator.h" #include "ast.h" #include "expressionvisitor.h" #include "helpers.h" using namespace KTextEditor; using namespace KDevelop; namespace Python { UseBuilder::UseBuilder(PythonEditorIntegrator* editor, QVector ignoreVariables) : UseBuilderBase() , m_errorReportingEnabled(true) , m_ignoreVariables(ignoreVariables) { setEditor(editor); } DUContext* UseBuilder::contextAtOrCurrent(const CursorInRevision& pos) { DUContext* context = nullptr; { DUChainReadLocker lock; context = topContext()->findContextAt(pos, true); } if ( ! context ) { context = currentContext(); } return context; } void UseBuilder::useHiddenMethod(ExpressionAst* value, Declaration* function) { RangeInRevision useRange; // TODO fixme! this does not necessarily use the opening bracket as it should useRange.start = CursorInRevision(value->endLine, value->endCol + 1); useRange.end = CursorInRevision(value->endLine, value->endCol + 2); if ( function && function->isFunctionDeclaration() ) { UseBuilderBase::newUse(value, useRange, DeclarationPointer(function)); } } void UseBuilder::visitName(NameAst* node) { DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node)); Declaration* declaration = Helper::declarationForName(identifierForNode(node->identifier), editorFindRange(node, node), DUChainPointer(context)); - static const QStringList keywords = {"None", "True", "False"}; - Q_ASSERT(node->identifier); RangeInRevision useRange = rangeForNode(node->identifier, true); if ( declaration && declaration->range() == useRange ) return; - if ( ! declaration && ! keywords.contains(node->identifier->value) && m_errorReportingEnabled ) { + if ( ! declaration && m_errorReportingEnabled ) { if ( ! m_ignoreVariables.contains(IndexedString(node->identifier->value)) ) { KDevelop::Problem *p = new KDevelop::Problem(); p->setFinalLocation(DocumentRange(currentlyParsedDocument(), useRange.castToSimpleRange())); // TODO ok? p->setSource(KDevelop::IProblem::SemanticAnalysis); p->setSeverity(KDevelop::IProblem::Hint); p->setDescription(i18n("Undefined variable: %1", node->identifier->value)); { DUChainWriteLocker wlock(DUChain::lock()); ProblemPointer ptr(p); topContext()->addProblem(ptr); } } } UseBuilderBase::newUse(node, useRange, DeclarationPointer(declaration)); } void UseBuilder::visitCall(CallAst* node) { UseBuilderBase::visitCall(node); DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node)); ExpressionVisitor v(context); v.visitNode(node->function); if ( auto classType = v.lastType().cast() ) { DUChainReadLocker lock; // This is either __init__() or __call__(): `a = Foo()` or `b = a()`. auto function = Helper::functionForCalled(classType->declaration(topContext()), v.isAlias()); lock.unlock(); useHiddenMethod(node->function, function.declaration); } } void UseBuilder::visitAttribute(AttributeAst* node) { UseBuilderBase::visitAttribute(node); DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node)); ExpressionVisitor v(context); v.visitNode(node); RangeInRevision useRange(node->attribute->startLine, node->attribute->startCol, node->attribute->endLine, node->attribute->endCol + 1); DeclarationPointer declaration = v.lastDeclaration(); DUChainWriteLocker wlock; if ( declaration && declaration->range() == useRange ) { // this is the declaration, don't build a use for it return; } if ( ! declaration && v.isConfident() && ( ! v.lastType() || Helper::isUsefulType(v.lastType()) ) ) { KDevelop::Problem *p = new KDevelop::Problem(); p->setFinalLocation(DocumentRange(currentlyParsedDocument(), useRange.castToSimpleRange())); p->setSource(KDevelop::IProblem::SemanticAnalysis); p->setSeverity(KDevelop::IProblem::Hint); p->setDescription(i18n("Attribute \"%1\" not found on accessed object", node->attribute->value)); ProblemPointer ptr(p); topContext()->addProblem(ptr); } UseBuilderBase::newUse(node, useRange, declaration); } void UseBuilder::visitSubscript(SubscriptAst* node) { UseBuilderBase::visitSubscript(node); DUContext* context = contextAtOrCurrent(editorFindPositionSafe(node->value)); ExpressionVisitor v(context); v.visitNode(node->value); static const IndexedIdentifier getitemIdentifier(KDevelop::Identifier("__getitem__")); static const IndexedIdentifier setitemIdentifier(KDevelop::Identifier("__setitem__")); bool isAugTarget = (node->parent->astType == Ast::AugmentedAssignmentAstType && static_cast(node->parent)->target == node); // e.g `a[0] += 2` uses both __getitem__ and __setitem__. if (isAugTarget || node->context == ExpressionAst::Context::Load) { DUChainReadLocker lock; auto getItemFunc = Helper::accessAttribute(v.lastType(), getitemIdentifier, context->topContext()); lock.unlock(); useHiddenMethod(node->value, getItemFunc); } if ( node->context == ExpressionAst::Context::Store ) { DUChainReadLocker lock; auto setItemFunc = Helper::accessAttribute(v.lastType(), setitemIdentifier, context->topContext()); lock.unlock(); useHiddenMethod(node->value, setItemFunc); } } ParseSession *UseBuilder::parseSession() const { return m_session; } } // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on