diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index 2b34f46dd9a9..5acbea33de8b 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -694,6 +694,10 @@
+
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 35f2ed9fe6d7..8755ee1028bb 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -194,6 +194,7 @@ surround.with.try.except.template=try / except
### Inspections: INSP ###
INSP.GROUP.python=Python
+INSP.GROUP.mako=Mako
# PyArgumentListInspection
INSP.NAME.incorrect.call.arguments=Incorrect call arguments
@@ -600,6 +601,8 @@ sdk.error.invalid.interpreter.name.$0=Invalid Python interpeter name '{0}'!
sdk.select.path=Select Python Interpreter
runcfg.unittest.dlg.pattern=Pattern:
+runcfg.testing.no.test.framework=No {0} runner found in selected interpreter
+
# Consoles messages
python.console=Python Console
django.console=Django Console
diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form
index 750d85d328ee..1dfead35678a 100644
--- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form
+++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.form
@@ -1,6 +1,6 @@
diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
index 524f6e3546db..5ddbf2cac530 100644
--- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
+++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java
@@ -1,39 +1,34 @@
package com.jetbrains.python.configuration;
-import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.ex.EditorEx;
-import com.intellij.openapi.editor.highlighter.EditorHighlighter;
-import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
-import com.intellij.openapi.editor.impl.EditorFactoryImpl;
+import com.intellij.facet.impl.ui.FacetErrorPanel;
+import com.intellij.facet.ui.FacetEditorValidator;
+import com.intellij.facet.ui.ValidationResult;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
-import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.NonDefaultProjectConfigurable;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.ContentIterator;
-import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Comparing;
-import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.CollectionComboBoxModel;
-import com.intellij.util.FileContentUtil;
-import com.jetbrains.django.lang.template.DjangoTemplateFileType;
-import com.jetbrains.python.templateLanguages.TemplatesConfigurationsModel;
-import com.jetbrains.python.templateLanguages.TemplatesService;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.documentation.DocStringFormat;
import com.jetbrains.python.documentation.PyDocumentationSettings;
+import com.jetbrains.python.sdk.PythonSdkType;
import com.jetbrains.python.testing.PythonTestConfigurationsModel;
import com.jetbrains.python.testing.TestRunnerService;
+import com.jetbrains.python.testing.VFSTestFrameworkListener;
import com.jetbrains.rest.ReSTService;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
+import java.awt.*;
import java.util.List;
/**
@@ -48,6 +43,7 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable, No
private final PyDocumentationSettings myDocumentationSettings;
private TextFieldWithBrowseButton myWorkDir;
private JCheckBox txtIsRst;
+ private JPanel myErrorPanel;
public PyIntegratedToolsConfigurable(Project project) {
myProject = project;
@@ -59,8 +55,41 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable, No
ReSTService service = ReSTService.getInstance(myProject);
myWorkDir.setText(service.getWorkdir());
txtIsRst.setSelected(service.txtIsRst());
+ initErrorValidation();
}
+ private void initErrorValidation() {
+ FacetErrorPanel facetErrorPanel = new FacetErrorPanel();
+ myErrorPanel.add(facetErrorPanel.getComponent(), BorderLayout.CENTER);
+
+ facetErrorPanel.getValidatorsManager().registerValidator(new FacetEditorValidator() {
+ @Override
+ public ValidationResult check() {
+ Module[] modules = ModuleManager.getInstance(myProject).getModules();
+ if (modules.length == 0) return ValidationResult.OK;
+ final Sdk sdk = PythonSdkType.findPythonSdk(modules[0]);
+ if (sdk != null) {
+ if (myTestRunnerComboBox.getSelectedItem() == PythonTestConfigurationsModel.PY_TEST_NAME) {
+ if (!VFSTestFrameworkListener.getInstance().isPyTestInstalled(sdk.getHomePath()))
+ return new ValidationResult(PyBundle.message("runcfg.testing.no.test.framework", "py.test"));
+ }
+ else if (myTestRunnerComboBox.getSelectedItem() == PythonTestConfigurationsModel.PYTHONS_NOSETEST_NAME) {
+ if (!VFSTestFrameworkListener.getInstance().isNoseTestInstalled(sdk.getHomePath()))
+ return new ValidationResult(PyBundle.message("runcfg.testing.no.test.framework", "nosetest"));
+ }
+ else if (myTestRunnerComboBox.getSelectedItem() == PythonTestConfigurationsModel.PYTHONS_ATTEST_NAME) {
+ if (!VFSTestFrameworkListener.getInstance().isAtTestInstalled(sdk.getHomePath()))
+ return new ValidationResult(PyBundle.message("runcfg.testing.no.test.framework", "attest"));
+ }
+ }
+ return ValidationResult.OK;
+ }
+ }, myTestRunnerComboBox);
+
+ facetErrorPanel.getValidatorsManager().validate();
+ }
+
+
@Nls
@Override
public String getDisplayName() {
diff --git a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java
index d72b44aa3659..67091c4ebed4 100644
--- a/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyArgumentListInspection.java
@@ -6,6 +6,7 @@ import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.cython.CythonLanguageDialect;
+import com.jetbrains.mako.MakoLanguage;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -43,7 +44,7 @@ public class PyArgumentListInspection extends PyInspection {
@Override
public void visitPyArgumentList(final PyArgumentList node) {
- if (CythonLanguageDialect._isDisabledFor(node)) {
+ if (CythonLanguageDialect._isDisabledFor(node) || MakoLanguage._isDisabledFor(node)) {
return;
}
// analyze
@@ -87,14 +88,18 @@ public class PyArgumentListInspection extends PyInspection {
}
- public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context) {
+ public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context, int implicitOffset) {
if (node.getParent() instanceof PyClass) return; // class Foo(object) is also an arg list
- CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context));
+ CallArgumentsMapping result = node.analyzeCall(PyResolveContext.noImplicits().withTypeEvalContext(context), implicitOffset);
highlightIncorrectArguments(holder, result);
highlightMissingArguments(node, holder, result);
highlightStarArgumentTypeMismatch(node, holder, context);
}
+ public static void inspectPyArgumentList(PyArgumentList node, ProblemsHolder holder, final TypeEvalContext context) {
+ inspectPyArgumentList(node, holder, context, 0);
+ }
+
private static void highlightIncorrectArguments(ProblemsHolder holder, CallArgumentsMapping result) {
for (Map.Entry> arg_entry : result.getArgumentFlags().entrySet()) {
EnumSet flags = arg_entry.getValue();
diff --git a/python/src/com/jetbrains/python/lexer/PythonEditorHighlighter.java b/python/src/com/jetbrains/python/lexer/PythonEditorHighlighter.java
index 9cdb214c0a3f..5f1e804b4f8f 100644
--- a/python/src/com/jetbrains/python/lexer/PythonEditorHighlighter.java
+++ b/python/src/com/jetbrains/python/lexer/PythonEditorHighlighter.java
@@ -6,9 +6,13 @@ import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter;
+import com.intellij.openapi.editor.highlighter.HighlighterClient;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,6 +29,9 @@ public class PythonEditorHighlighter extends LexerEditorHighlighter {
scheme);
}
+ private Boolean hadUnicodeImport = false;
+
+ public static final Key KEY = new Key("python.future.import");
@Override
public void documentChanged(DocumentEvent e) {
synchronized (this) {
@@ -34,10 +41,14 @@ public class PythonEditorHighlighter extends LexerEditorHighlighter {
// we should update the whole document
if (l instanceof LayeredLexer) {
Lexer delegate = ((LayeredLexer)l).getDelegate();
+ int offset = e.getOffset();
+ int lineNumber = document.getLineNumber(offset);
+ TextRange tr = new TextRange(document.getLineStartOffset(lineNumber), document.getLineEndOffset(lineNumber));
+ document.putUserData(KEY, document.getText(tr).indexOf(PyNames.UNICODE_LITERALS) == -1);
+ Boolean hasUnicodeImport = document.getUserData(KEY);
if (delegate instanceof PythonHighlightingLexer &&
(((PythonHighlightingLexer)delegate).getImportOffset() > e.getOffset()
- || ((PythonHighlightingLexer)delegate).getImportOffset() == -1)) {
-
+ || hasUnicodeImport != hadUnicodeImport)) {
((PythonHighlightingLexer)delegate).clearState(e.getDocument().getTextLength());
setText(document.getCharsSequence());
}
@@ -46,4 +57,19 @@ public class PythonEditorHighlighter extends LexerEditorHighlighter {
else super.documentChanged(e);
}
}
+
+ @Override
+ public void beforeDocumentChange(DocumentEvent e) {
+ final Document document = e.getDocument();
+ hadUnicodeImport = document.getUserData(KEY);
+ }
+
+ @Override
+ public void setEditor(HighlighterClient editor) {
+ Lexer l = getLexer();
+ if (l instanceof LayeredLexer) {
+ editor.getDocument().putUserData(KEY, editor.getDocument().getText().indexOf(PyNames.UNICODE_LITERALS) == -1);
+ }
+ super.setEditor(editor);
+ }
}
diff --git a/python/src/com/jetbrains/python/psi/PyArgumentList.java b/python/src/com/jetbrains/python/psi/PyArgumentList.java
index fad66e5d654b..8d18aecada73 100644
--- a/python/src/com/jetbrains/python/psi/PyArgumentList.java
+++ b/python/src/com/jetbrains/python/psi/PyArgumentList.java
@@ -30,10 +30,15 @@ public interface PyArgumentList extends PyElement {
* Tries to map the argument list to callee's idea of parameters.
* @return a result object with mappings and diagnostic flags.
* @param resolveContext the reference resolution context
+ * @param implicitOffset known from the context implicit offset
*/
+ @NotNull
+ CallArgumentsMapping analyzeCall(PyResolveContext resolveContext, int implicitOffset);
+
@NotNull
CallArgumentsMapping analyzeCall(PyResolveContext resolveContext);
+
@Nullable
ASTNode getClosingParen();
}
diff --git a/python/src/com/jetbrains/python/psi/PyCallExpression.java b/python/src/com/jetbrains/python/psi/PyCallExpression.java
index 03e84c9d8a28..5547eb09c48f 100644
--- a/python/src/com/jetbrains/python/psi/PyCallExpression.java
+++ b/python/src/com/jetbrains/python/psi/PyCallExpression.java
@@ -55,6 +55,14 @@ public interface PyCallExpression extends PyExpression {
@Nullable
PyMarkedCallee resolveCallee(PyResolveContext resolveContext);
+ /**
+ *
+ * @param resolveContext the reference resolve context
+ * @param implicitOffset known from the context implicit offset
+ */
+ @Nullable
+ PyMarkedCallee resolveCallee(PyResolveContext resolveContext, int implicitOffset);
+
/**
* Checks if the unqualified name of the callee matches any of the specified names
*
diff --git a/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java
index 578b101d85c9..f9baf9e4d174 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyArgumentListImpl.java
@@ -238,6 +238,11 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
@NotNull
public CallArgumentsMapping analyzeCall(PyResolveContext resolveContext) {
+ return analyzeCall(resolveContext, 0);
+ }
+
+ @NotNull
+ public CallArgumentsMapping analyzeCall(PyResolveContext resolveContext, int offset) {
final CallArgumentsMappingImpl ret = new CallArgumentsMappingImpl(this);
// declaration-based checks
// proper arglist is: [positional,...][name=value,...][*tuple,][**dict]
@@ -245,7 +250,7 @@ public class PyArgumentListImpl extends PyElementImpl implements PyArgumentList
// following the spec: http://docs.python.org/ref/calls.html
PyCallExpression call = getCallExpression();
if (call != null) {
- PyCallExpression.PyMarkedCallee resolved_callee = call.resolveCallee(resolveContext);
+ PyCallExpression.PyMarkedCallee resolved_callee = call.resolveCallee(resolveContext, offset);
if (resolved_callee != null) ret.mapArguments(resolved_callee, resolveContext.getTypeEvalContext());
}
return ret;
diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
index 4e17002e5b59..5ecf88777b89 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
@@ -104,6 +104,11 @@ public class PyCallExpressionHelper {
@Nullable
public static PyCallExpression.PyMarkedCallee resolveCallee(PyCallExpression us, PyResolveContext resolveContext) {
+ return resolveCallee(us, resolveContext, 0);
+ }
+
+ @Nullable
+ public static PyCallExpression.PyMarkedCallee resolveCallee(PyCallExpression us, PyResolveContext resolveContext, int implicitOffset) {
PyFunction.Flag wrappedFlag = null;
boolean isConstructorCall = false;
@@ -152,8 +157,8 @@ public class PyCallExpressionHelper {
PyExpression lastQualifier = qualifiers != null && qualifiers.isEmpty() ? null : qualifiers.get(qualifiers.size()-1);
boolean isByClass = lastQualifier == null ? false : isQualifiedByClass((Callable)resolved, lastQualifier, resolveContext.getTypeEvalContext());
final Callable callable = (Callable)resolved;
-
- int implicitOffset = getImplicitArgumentCount(callable, flags, isConstructorCall, isByInstance, isByClass);
+
+ implicitOffset += getImplicitArgumentCount(callable, flags, isConstructorCall, isByInstance, isByClass);
implicitOffset = implicitOffset < 0? 0: implicitOffset; // wrong source can trigger strange behaviour
return new PyCallExpression.PyMarkedCallee(callable, flags, implicitOffset,
resolveResult != null ? resolveResult.isImplicit() : false);
diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
index 3d50652a3ea7..69cab39d7d34 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionImpl.java
@@ -64,6 +64,10 @@ public class PyCallExpressionImpl extends PyElementImpl implements PyCallExpress
return PyCallExpressionHelper.resolveCallee(this, resolveContext);
}
+ public PyMarkedCallee resolveCallee(PyResolveContext resolveContext, int offset) {
+ return PyCallExpressionHelper.resolveCallee(this, resolveContext, offset);
+ }
+
public boolean isCalleeText(@NotNull String... nameCandidates) {
return PyCallExpressionHelper.isCalleeText(this, nameCandidates);
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
index e7c46d8c3a6e..b01be5a05a55 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
@@ -112,6 +112,9 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i
}
public PyMarkedCallee resolveCallee(PyResolveContext resolveContext) {
+ return resolveCallee(resolveContext, 0);
+ }
+ public PyMarkedCallee resolveCallee(PyResolveContext resolveContext, int offset) {
PyMarkedCallee callee = PyCallExpressionHelper.resolveCallee(this, resolveContext);
if (callee == null) return null;
if (!hasArgumentList()) {
diff --git a/python/src/com/jetbrains/python/testing/attest/PythonAtTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/attest/PythonAtTestRunConfiguration.java
index 8807ec8e10c1..268a7088a961 100644
--- a/python/src/com/jetbrains/python/testing/attest/PythonAtTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/attest/PythonAtTestRunConfiguration.java
@@ -7,6 +7,7 @@ import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
import com.jetbrains.python.testing.VFSTestFrameworkListener;
import org.jdom.Element;
@@ -62,6 +63,6 @@ public class PythonAtTestRunConfiguration extends AbstractPythonTestRunConfigura
public void checkConfiguration() throws RuntimeConfigurationException {
super.checkConfiguration();
if (!VFSTestFrameworkListener.getInstance().isAtTestInstalled(getSdkHome()))
- throw new RuntimeConfigurationWarning("No attest runner found in selected interpreter");
+ throw new RuntimeConfigurationWarning(PyBundle.message("runcfg.testing.no.test.framework", "attest"));
}
}
diff --git a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java
index 43aa66ccb3d4..3de3ab8d5b78 100644
--- a/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/nosetest/PythonNoseTestRunConfiguration.java
@@ -8,6 +8,7 @@ import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.WriteExternalException;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
import com.jetbrains.python.testing.VFSTestFrameworkListener;
import org.jdom.Element;
@@ -79,7 +80,7 @@ public class PythonNoseTestRunConfiguration extends AbstractPythonTestRunConfigu
public void checkConfiguration() throws RuntimeConfigurationException {
super.checkConfiguration();
if (!VFSTestFrameworkListener.getInstance().isNoseTestInstalled(getSdkHome()))
- throw new RuntimeConfigurationWarning("No nosetest runner found in selected interpreter");
+ throw new RuntimeConfigurationWarning(PyBundle.message("runcfg.testing.no.test.framework", "nosetest"));
}
public boolean useParam() {
diff --git a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
index 6ece7cb4bc8f..be0c596ebdc2 100644
--- a/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
+++ b/python/src/com/jetbrains/python/testing/pytest/PyTestRunConfiguration.java
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.text.StringUtil;
+import com.jetbrains.python.PyBundle;
import com.jetbrains.python.testing.AbstractPythonTestRunConfiguration;
import com.jetbrains.python.testing.VFSTestFrameworkListener;
import org.jdom.Element;
@@ -115,7 +116,7 @@ public class PyTestRunConfiguration extends AbstractPythonTestRunConfiguration i
throw new RuntimeConfigurationError("Please specify target folder or script");
}
if (!VFSTestFrameworkListener.getInstance().isPyTestInstalled(getSdkHome()))
- throw new RuntimeConfigurationWarning("No py.test runner found in selected interpreter");
+ throw new RuntimeConfigurationWarning(PyBundle.message("runcfg.testing.no.test.framework", "py.test"));
}
@Override