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
This commit is contained in:
Mikhail Golubev
2015-09-07 16:07:25 +03:00
parent dade15b031
commit 4653df951e
7 changed files with 61 additions and 114 deletions
@@ -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;
@@ -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();
}
}
@@ -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;
}
}
@@ -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();
@@ -0,0 +1,6 @@
def f(x):
u"""
:param x:
:return:
"""
@@ -0,0 +1,2 @@
def f(x):
u"""<caret>
@@ -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);