From 4653df951ef9f5828e6743ea8cc4ecac133c02da Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 7 Sep 2015 15:34:56 +0300 Subject: [PATCH] PY-16828 Insert docstring stub on enter even if opening quotes have e.g. unicode prefix Also I removed PythonDocCommentUtil and moved its only method to PythonEnterHandler --- .../python/documentation/DocStringUtil.java | 32 +++--- .../python/editor/PythonDocCommentUtil.java | 99 ------------------- .../python/editor/PythonEnterHandler.java | 29 +++++- .../python/editor/PythonSpaceHandler.java | 2 +- ...nterDocStringStubWithStringPrefix.after.py | 6 ++ .../enterDocStringStubWithStringPrefix.py | 2 + .../com/jetbrains/python/PyEditingTest.java | 5 + 7 files changed, 61 insertions(+), 114 deletions(-) delete mode 100644 python/src/com/jetbrains/python/editor/PythonDocCommentUtil.java create mode 100644 python/testData/editing/enterDocStringStubWithStringPrefix.after.py create mode 100644 python/testData/editing/enterDocStringStubWithStringPrefix.py diff --git a/python/src/com/jetbrains/python/documentation/DocStringUtil.java b/python/src/com/jetbrains/python/documentation/DocStringUtil.java index 6fa1a3c415cb..3247e4802cee 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringUtil.java +++ b/python/src/com/jetbrains/python/documentation/DocStringUtil.java @@ -25,7 +25,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; @@ -216,13 +215,26 @@ public class DocStringUtil { return value == null ? null : parse(value, owner); } - public static boolean isDocStringExpression(@Nullable PyExpression expression) { - final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(expression, PyDocStringOwner.class); + /** + * Returns containing docstring expression of class definition, function definition or module. + * Useful to test whether particular PSI element is or belongs to such docstring. + */ + @Nullable + public static PyStringLiteralExpression getParentDefinitionDocString(@NotNull PsiElement element) { + final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); if (docStringOwner != null) { - if (docStringOwner.getDocStringExpression() == expression) { - return true; + final PyStringLiteralExpression docString = docStringOwner.getDocStringExpression(); + if (PsiTreeUtil.isAncestor(docString, element, false)) { + return docString; } } + return null; + } + + public static boolean isDocStringExpression(@NotNull PyExpression expression) { + if (getParentDefinitionDocString(expression) == expression) { + return true; + } if (expression instanceof PyStringLiteralExpression) { return isVariableDocString((PyStringLiteralExpression)expression); } @@ -233,10 +245,7 @@ public class DocStringUtil { public static String getAttributeDocComment(@NotNull PyTargetExpression attr) { if (attr.getParent() instanceof PyAssignmentStatement) { final PyAssignmentStatement assignment = (PyAssignmentStatement)attr.getParent(); - PsiElement prevSibling = assignment.getPrevSibling(); - while (prevSibling != null && (prevSibling instanceof PsiWhiteSpace)) { - prevSibling = prevSibling.getPrevSibling(); - } + final PsiElement prevSibling = PyPsiUtils.getPrevNonWhitespaceSibling(assignment); if (prevSibling instanceof PsiComment && prevSibling.getText().startsWith("#:")) { return prevSibling.getText().substring(2); } @@ -249,10 +258,7 @@ public class DocStringUtil { if (!(parent instanceof PyExpressionStatement)) { return false; } - PsiElement prevElement = parent.getPrevSibling(); - while (prevElement instanceof PsiWhiteSpace || prevElement instanceof PsiComment) { - prevElement = prevElement.getPrevSibling(); - } + final PsiElement prevElement = PyPsiUtils.getPrevNonCommentSibling(parent, true); if (prevElement instanceof PyAssignmentStatement) { if (expr.getText().contains("type:")) return true; diff --git a/python/src/com/jetbrains/python/editor/PythonDocCommentUtil.java b/python/src/com/jetbrains/python/editor/PythonDocCommentUtil.java deleted file mode 100644 index 1b0d966d2429..000000000000 --- a/python/src/com/jetbrains/python/editor/PythonDocCommentUtil.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.python.editor; - -import com.intellij.openapi.util.text.LineTokenizer; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiErrorElement; -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; - -/** - * User : catherine - */ -public class PythonDocCommentUtil { - - private PythonDocCommentUtil() { - } - - static public boolean atDocCommentStart(PsiElement element, int offset) { - PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(element, PyStringLiteralExpression.class); - if (string != null) { - PyElement func = PsiTreeUtil.getParentOfType(element, PyFunction.class, PyClass.class, PyFile.class); - if (func != null) { - final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, - PyDocStringOwner.class); - if (docStringOwner == func) { - PyStringLiteralExpression str = docStringOwner.getDocStringExpression(); - String text = element.getText(); - final int prefix = PyStringLiteralExpressionImpl.getPrefixLength(text); - text = text.substring(prefix); - if (str != null && text.equals(str.getText()) && - (text.startsWith("\"\"\"") || text.startsWith("'''"))) { - if (offset == str.getTextRange().getStartOffset()) { - PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(string, PsiErrorElement.class); - if (error != null) - return true; - error = PsiTreeUtil.getNextSiblingOfType(string.getParent(), PsiErrorElement.class); - if (error != null) - return true; - - if (text.length() < 6 || (!text.endsWith("\"\"\"") && !text.endsWith("'''"))) - return true; - } - } - } - } - } - return false; - } - - static public String removeParamFromDocstring(String text, String prefix, String paramName) { - StringBuilder newText = new StringBuilder(); - String[] lines = LineTokenizer.tokenize(text, true); - boolean skipNext = false; - for (String line : lines) { - if (line.contains(prefix)) { - String[] subLines = line.split(" "); - boolean lookNext = false; - boolean add = true; - for (String s : subLines) { - final String trimmedLine = s.trim(); - if (trimmedLine.equals(prefix + "param") || trimmedLine.equals(prefix + "type")) { - lookNext = true; - } - if (lookNext && trimmedLine.endsWith(":")) { - String tmp = trimmedLine.substring(0, trimmedLine.length() - 1); - if (paramName.equals(tmp)) { - lookNext = false; - skipNext = true; - add = false; - } - } - } - if (add) { - newText.append(line); - skipNext = false; - } - } - else if (!skipNext || line.contains("\"\"\"") || line.contains("'''")) { - newText.append(line); - } - } - return newText.toString(); - } -} diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index cf7754570c5e..64677f0f23e4 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -35,6 +35,7 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; +import com.jetbrains.python.documentation.DocStringUtil; import com.jetbrains.python.documentation.PyDocstringGenerator; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl; @@ -104,7 +105,7 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { comment = file.findElementAt(offset - 1); } int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or ''' - if (PythonDocCommentUtil.atDocCommentStart(comment, expectedStringStart)) { + if (atDocCommentStart(comment, expectedStringStart)) { insertDocStringStub(editor, comment); return Result.Continue; } @@ -353,4 +354,30 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { return super.postProcessEnter(file, editor, dataContext); } + + public static boolean atDocCommentStart(@NotNull PsiElement element, int offset) { + final PyStringLiteralExpression pyString = DocStringUtil.getParentDefinitionDocString(element); + if (pyString != null) { + String text = element.getText(); + final int prefixLength = PyStringLiteralExpressionImpl.getPrefixLength(text); + text = text.substring(prefixLength); + if (pyString.getText().endsWith(text) && (text.startsWith("\"\"\"") || text.startsWith("'''"))) { + if (offset == pyString.getTextOffset() + prefixLength) { + PsiErrorElement error = PsiTreeUtil.getNextSiblingOfType(pyString, PsiErrorElement.class); + if (error != null) { + return true; + } + error = PsiTreeUtil.getNextSiblingOfType(pyString.getParent(), PsiErrorElement.class); + if (error != null) { + return true; + } + + if (text.length() < 6 || (!text.endsWith("\"\"\"") && !text.endsWith("'''"))) { + return true; + } + } + } + } + return false; + } } diff --git a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java index 4c659ac358be..822a7323ffed 100644 --- a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java @@ -44,7 +44,7 @@ public class PythonSpaceHandler extends TypedHandlerDelegate { } if (element == null) return Result.CONTINUE; int expectedStringStart = offset - 4; // """ or ''' plus space char - if (PythonDocCommentUtil.atDocCommentStart(element, expectedStringStart)) { + if (PythonEnterHandler.atDocCommentStart(element, expectedStringStart)) { final PyDocStringOwner docOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class); if (docOwner != null) { final Document document = editor.getDocument(); diff --git a/python/testData/editing/enterDocStringStubWithStringPrefix.after.py b/python/testData/editing/enterDocStringStubWithStringPrefix.after.py new file mode 100644 index 000000000000..76474ec51cf0 --- /dev/null +++ b/python/testData/editing/enterDocStringStubWithStringPrefix.after.py @@ -0,0 +1,6 @@ +def f(x): + u""" + + :param x: + :return: + """ \ No newline at end of file diff --git a/python/testData/editing/enterDocStringStubWithStringPrefix.py b/python/testData/editing/enterDocStringStubWithStringPrefix.py new file mode 100644 index 000000000000..79e093ff9328 --- /dev/null +++ b/python/testData/editing/enterDocStringStubWithStringPrefix.py @@ -0,0 +1,2 @@ +def f(x): + u""" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java index 2d07f84fc546..9d3c5fadef69 100644 --- a/python/testSrc/com/jetbrains/python/PyEditingTest.java +++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java @@ -226,6 +226,11 @@ public class PyEditingTest extends PyTestCase { doDocStringTypingTest('\n', DocStringFormat.REST); } + // PY-16828 + public void testEnterDocStringStubWithStringPrefix() { + doDocStringTypingTest('\n', DocStringFormat.REST); + } + // PY-3421 public void testSpaceDocStringStubInFunction() { doDocStringTypingTest(' ', DocStringFormat.REST);