diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index f916bcf78264..dd799b45f763 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -450,6 +450,8 @@ public class PyNames { public static final String IN = "in"; public static final String NOT = "not"; public static final String LAMBDA = "lambda"; + public static final String ASYNC = "async"; + public static final String AWAIT = "await"; /** * Contains keywords as of CPython 2.5. diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyFunction.java b/python/psi-api/src/com/jetbrains/python/psi/PyFunction.java index afd1bfcb543c..01cd6d5234d6 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyFunction.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyFunction.java @@ -79,6 +79,8 @@ extends @Nullable Modifier getModifier(); + boolean isAsync(); + /** * Flags that mark common alterations of a function: decoration by and wrapping in classmethod() and staticmethod(). */ diff --git a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyFunctionStub.java b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyFunctionStub.java index 78cd774e20b9..1b4bcb7bacab 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/stubs/PyFunctionStub.java +++ b/python/psi-api/src/com/jetbrains/python/psi/stubs/PyFunctionStub.java @@ -21,4 +21,5 @@ import com.jetbrains.python.psi.PyFunction; public interface PyFunctionStub extends NamedStub { String getDocString(); String getDeprecationMessage(); + boolean isAsync(); } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java index 387fc05830aa..5873272ae500 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyKeywordCompletionContributor.java @@ -34,6 +34,8 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.codeInsight.PyUnindentingInsertHandler; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil; import com.jetbrains.python.documentation.doctest.PyDocstringFile; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NonNls; @@ -182,13 +184,19 @@ public class PyKeywordCompletionContributor extends CompletionContributor { } } - private static class Py3kFilter implements ElementFilter { + private static class LanguageLevelAtLeastFilter implements ElementFilter { + @NotNull private final LanguageLevel myLevel; + + public LanguageLevelAtLeastFilter(@NotNull LanguageLevel level) { + myLevel = level; + } + public boolean isAcceptable(Object element, PsiElement context) { if (!(element instanceof PsiElement)) { return false; } final PsiFile containingFile = ((PsiElement)element).getContainingFile(); - return containingFile instanceof PyFile && ((PyFile)containingFile).getLanguageLevel().isPy3K(); + return containingFile instanceof PyFile && ((PyFile)containingFile).getLanguageLevel().isAtLeast(myLevel); } public boolean isClassAcceptable(Class hintClass) { @@ -196,6 +204,7 @@ public class PyKeywordCompletionContributor extends CompletionContributor { } } + private static class NotParameterOrDefaultValue implements ElementFilter { @Override @@ -359,7 +368,8 @@ public class PyKeywordCompletionContributor extends CompletionContributor { )); */ - private static final FilterPattern PY3K = new FilterPattern(new Py3kFilter()); + private static final FilterPattern PY3K = new FilterPattern(new PyKeywordCompletionContributor.LanguageLevelAtLeastFilter(LanguageLevel.PYTHON30)); + private static final FilterPattern PY35 = new FilterPattern(new LanguageLevelAtLeastFilter(LanguageLevel.PYTHON35)); // ====== @@ -477,6 +487,28 @@ public class PyKeywordCompletionContributor extends CompletionContributor { , new PyKeywordCompletionProvider(PyNames.NONLOCAL) ); + + extend(CompletionType.BASIC, + psiElement() + .withLanguage(PythonLanguage.getInstance()) + .and(PY35) + .andNot(AFTER_QUALIFIER) + .with(new PatternCondition("insideAsyncDef") { + @Override + public boolean accepts(@NotNull PsiElement element, ProcessingContext context) { + final ScopeOwner owner = ScopeUtil.getScopeOwner(element); + return owner instanceof PyFunction && ((PyFunction)owner).isAsync(); + } + }) + .andOr(IN_BEGIN_STMT, + psiElement() + .inside(false, psiElement(PyAssignmentStatement.class), psiElement(PyTargetExpression.class)) + .afterLeaf(psiElement().withElementType(PyTokenTypes.EQ)), + psiElement() + .inside(false, psiElement(PyAugAssignmentStatement.class), psiElement(PyTargetExpression.class)) + .afterLeaf(psiElement().withElementType(PyTokenTypes.AUG_ASSIGN_OPERATIONS)), + psiElement().inside(true, psiElement(PyParenthesizedExpression.class))), + new PyKeywordCompletionProvider(PyNames.AWAIT)); } private void addWithinIf() { @@ -578,8 +610,23 @@ public class PyKeywordCompletionContributor extends CompletionContributor { .andNot(AFTER_QUALIFIER) .andNot(IN_FUNCTION_HEADER) , - new PyKeywordCompletionProvider(TailType.NONE, PyNames.TRUE, PyNames.FALSE, PyNames.NONE) - ); + new PyKeywordCompletionProvider(TailType.NONE, PyNames.TRUE, PyNames.FALSE, PyNames.NONE)); + extend(CompletionType.BASIC, + psiElement() + .withLanguage(PythonLanguage.getInstance()) + .and(PY35) + .andNot(IN_COMMENT) + .andNot(IN_IMPORT_STMT) + .andNot(IN_PARAM_LIST) + .andNot(AFTER_QUALIFIER) + .andNot(IN_STRING_LITERAL), + new PyKeywordCompletionProvider(PyNames.ASYNC)); + extend(CompletionType.BASIC, + psiElement() + .withLanguage(PythonLanguage.getInstance()) + .and(PY35) + .afterLeaf(psiElement().withElementType(PyTokenTypes.IDENTIFIER).withText(PyNames.ASYNC)), + new PyKeywordCompletionProvider(PyNames.DEF, PyNames.WITH, PyNames.FOR)); } private void addAs() { diff --git a/python/src/com/jetbrains/python/psi/PyFileElementType.java b/python/src/com/jetbrains/python/psi/PyFileElementType.java index d646679ffd18..7e75bc364657 100644 --- a/python/src/com/jetbrains/python/psi/PyFileElementType.java +++ b/python/src/com/jetbrains/python/psi/PyFileElementType.java @@ -63,7 +63,7 @@ public class PyFileElementType extends IStubFileElementType { @Override public int getStubVersion() { // Don't forget to update versions of indexes that use the updated stub-based elements - return 50; + return 51; } @Nullable diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index 66e9248e0761..bab647903f24 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -638,6 +638,15 @@ public class PyFunctionImpl extends PyBaseElementImpl implements return null; } + @Override + public boolean isAsync() { + final PyFunctionStub stub = getStub(); + if (stub != null) { + return stub.isAsync(); + } + return getNode().findChildByType(PyTokenTypes.ASYNC_KEYWORD) != null; + } + @Nullable private Modifier getWrappersFromStub() { final StubElement parentStub = getStub().getParentStub(); diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionElementType.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionElementType.java index aca549a72cad..e1b1ec3158ee 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionElementType.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionElementType.java @@ -56,7 +56,8 @@ public class PyFunctionElementType extends PyStubElementType 0 ? docString : null, deprecationMessage, parentStub, getStubElementType()); + final boolean isAsync = dataStream.readBoolean(); + return new PyFunctionStubImpl(name, docString.length() > 0 ? docString : null, deprecationMessage, isAsync, parentStub, + getStubElementType()); } public void indexStub(@NotNull final PyFunctionStub stub, @NotNull final IndexSink sink) { diff --git a/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionStubImpl.java b/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionStubImpl.java index e1665595dada..524798a6c199 100644 --- a/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionStubImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/stubs/PyFunctionStubImpl.java @@ -27,13 +27,15 @@ public class PyFunctionStubImpl extends StubBase implements PyFuncti private final String myName; private final String myDocString; private final StringRef myDeprecationMessage; + private final boolean myAsync; - public PyFunctionStubImpl(final String name, final String docString, @Nullable final StringRef deprecationMessage, + public PyFunctionStubImpl(final String name, final String docString, @Nullable final StringRef deprecationMessage, boolean isAsync, final StubElement parent, IStubElementType stubElementType) { super(parent, stubElementType); myName = name; myDocString = docString; myDeprecationMessage = deprecationMessage; + myAsync = isAsync; } public String getName() { @@ -49,6 +51,11 @@ public class PyFunctionStubImpl extends StubBase implements PyFuncti return myDeprecationMessage == null ? null : myDeprecationMessage.getString(); } + @Override + public boolean isAsync() { + return myAsync; + } + @Override public String toString() { return "PyFunctionStub(" + myName + ")"; diff --git a/python/testData/completion/async.after.py b/python/testData/completion/async.after.py new file mode 100644 index 000000000000..500848fbe1b1 --- /dev/null +++ b/python/testData/completion/async.after.py @@ -0,0 +1 @@ +async # Comment diff --git a/python/testData/completion/async.py b/python/testData/completion/async.py new file mode 100644 index 000000000000..ff8fb3181275 --- /dev/null +++ b/python/testData/completion/async.py @@ -0,0 +1 @@ +asy # Comment diff --git a/python/testData/completion/await.after.py b/python/testData/completion/await.after.py new file mode 100644 index 000000000000..2de7a7d8d0cd --- /dev/null +++ b/python/testData/completion/await.after.py @@ -0,0 +1,2 @@ +async def foo(): + await # comment diff --git a/python/testData/completion/await.py b/python/testData/completion/await.py new file mode 100644 index 000000000000..110aec23d3cf --- /dev/null +++ b/python/testData/completion/await.py @@ -0,0 +1,2 @@ +async def foo(): + awa # comment diff --git a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java index 64bdf670135e..e20f0f403768 100644 --- a/python/testSrc/com/jetbrains/python/Py3CompletionTest.java +++ b/python/testSrc/com/jetbrains/python/Py3CompletionTest.java @@ -157,6 +157,24 @@ public class Py3CompletionTest extends PyTestCase { doTest(); } + public void testAsync() { + runWithLanguageLevel(LanguageLevel.PYTHON35, new Runnable() { + @Override + public void run() { + doTest(); + } + }); + } + + public void testAwait() { + runWithLanguageLevel(LanguageLevel.PYTHON35, new Runnable() { + @Override + public void run() { + doTest(); + } + }); + } + @Override protected String getTestDataPath() { return super.getTestDataPath() + "/completion";