diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyGenerateDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyGenerateDocstringIntention.java index 882ce0a89309..004dd87f2d89 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/PyGenerateDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyGenerateDocstringIntention.java @@ -67,7 +67,7 @@ public class PyGenerateDocstringIntention extends BaseIntentionAction { private boolean isAvailableForFunction(PyFunction function) { if (function.getDocStringValue() != null) { - final PyDocstringGenerator docstringGenerator = new PyDocstringGenerator(function); + final PyDocstringGenerator docstringGenerator = PyDocstringGenerator.forDocStringOwner(function); if (docstringGenerator.hasParametersToAdd()) { myText = PyBundle.message("INTN.add.parameters.to.docstring"); return true; @@ -101,7 +101,7 @@ public class PyGenerateDocstringIntention extends BaseIntentionAction { if (!DocStringUtil.ensureNotPlainDocstringFormat(docStringOwner)) { return; } - final PyDocstringGenerator docstringGenerator = new PyDocstringGenerator(docStringOwner); + final PyDocstringGenerator docstringGenerator = PyDocstringGenerator.forDocStringOwner(docStringOwner); docstringGenerator.addFirstEmptyLine(); final PyStringLiteralExpression updated = docstringGenerator.buildAndInsert().getDocStringExpression(); if (updated != null && editor != null) { diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java index 7b2f12857a93..2e88396afa54 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java @@ -78,7 +78,7 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention { final boolean isReturn = "rtype".equals(kind); - final PyDocstringGenerator docstringGenerator = new PyDocstringGenerator(pyFunction); + final PyDocstringGenerator docstringGenerator = PyDocstringGenerator.forDocStringOwner(pyFunction); docstringGenerator.addFirstEmptyLine(); final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction); final String name = isReturn ? "" : StringUtil.notNullize(problemElement.getName()); diff --git a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java index 878745ab59c9..7a9f00dc06dd 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.documentation; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -33,6 +34,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; @@ -53,26 +55,65 @@ import java.util.Set; * @author traff */ public class PyDocstringGenerator { - + public static final String TRIPLE_DOUBLE_QUOTES = "\"\"\""; + public static final String TRIPLE_SINGLE_QUOTES = "'''"; + private final List myAddedParams = Lists.newArrayList(); private final List myRemovedParams = Lists.newArrayList(); - // Updated after buildAndInsert - @NotNull - private PyDocStringOwner myDocStringOwner; + private final String myDocStringText; + + // Updated after buildAndInsert() + @Nullable private PyDocStringOwner myDocStringOwner; + private final String myDocStringIndent; + private final DocStringFormat myDocStringFormat; private boolean myUseTypesFromDebuggerSignature = true; private boolean myNewMode = false; // true - generate new string, false - update existing private boolean myAddFirstEmptyLine = false; private boolean myParametersPrepared = false; private boolean myAlwaysGenerateReturn; - private String myQuotes = "\"\"\""; + private String myQuotes = TRIPLE_DOUBLE_QUOTES; - public PyDocstringGenerator(@NotNull PyDocStringOwner docStringOwner) { + private PyDocstringGenerator(@Nullable PyDocStringOwner docStringOwner, + @NotNull DocStringFormat format, + @NotNull String indentation, + @NotNull String docStringText) { myDocStringOwner = docStringOwner; - final PyStringLiteralExpression docStringExpression = myDocStringOwner.getDocStringExpression(); - myNewMode = docStringExpression == null; + myDocStringIndent = indentation; + myDocStringFormat = format; + myDocStringText = docStringText; + myNewMode = StringUtil.isEmpty(myDocStringText); } + @NotNull + public static PyDocstringGenerator forDocStringOwner(@NotNull PyDocStringOwner owner) { + String indentation = ""; + if (owner instanceof PyStatementListContainer) { + indentation = PyIndentUtil.getElementIndent(((PyStatementListContainer)owner).getStatementList()); + } + final String docStringText = owner.getDocStringExpression() == null ? "" : owner.getDocStringExpression().getText(); + return new PyDocstringGenerator(owner, DocStringUtil.getDocStringFormat(owner), indentation, docStringText); + } + + @NotNull + public static PyDocstringGenerator create(@NotNull DocStringFormat format, @NotNull String indentation) { + return new PyDocstringGenerator(null, format, indentation, ""); + } + + @NotNull + public static PyDocstringGenerator update(@NotNull PyStringLiteralExpression docString) { + return new PyDocstringGenerator(PsiTreeUtil.getParentOfType(docString, PyDocStringOwner.class), + DocStringUtil.getDocStringFormat(docString), + PyIndentUtil.getElementIndent(docString), docString.getText()); + } + + @NotNull + public static PyDocstringGenerator update(@NotNull DocStringFormat format, + @NotNull String indentation, + @NotNull String text) { + return new PyDocstringGenerator(null, format, indentation, text); + } + @NotNull public PyDocstringGenerator withParam(@NotNull String name) { return withParamTypedByName(name, null); @@ -177,7 +218,7 @@ public class PyDocstringGenerator { if (myDocStringOwner instanceof PyFunction && myUseTypesFromDebuggerSignature) { signature = PySignatureCacheManager.getInstance(myDocStringOwner.getProject()).findSignature((PyFunction)myDocStringOwner); } - final DocStringFormat format = getDocStringFormat(); + final DocStringFormat format = myDocStringFormat; final ArrayList filtered = Lists.newArrayList(); final Set> processed = Sets.newHashSet(); for (DocstringParam param : myAddedParams) { @@ -231,25 +272,17 @@ public class PyDocstringGenerator { @Nullable private PyStringLiteralExpression getDocStringExpression() { + Preconditions.checkNotNull(myDocStringOwner, "For this action docstring owner must be supplied"); return myDocStringOwner.getDocStringExpression(); } - @NotNull - private DocStringFormat getDocStringFormat() { - return DocStringUtil.getDocStringFormat(myDocStringOwner); - } - @Nullable private StructuredDocString getStructuredDocString() { - final PyStringLiteralExpression expression = getDocStringExpression(); - final DocStringFormat format = getDocStringFormat(); - if (format == DocStringFormat.PLAIN || expression == null) { - return null; - } - return DocStringUtil.parseDocString(format, expression); + return myDocStringText.isEmpty() ? null : DocStringUtil.parseDocString(myDocStringFormat, myDocStringText); } public void startTemplate() { + Preconditions.checkNotNull(myDocStringOwner, "For this action docstring owner must be supplied"); final PyStringLiteralExpression docStringExpression = getDocStringExpression(); assert docStringExpression != null; @@ -260,7 +293,7 @@ public class PyDocstringGenerator { } final DocstringParam paramToEdit = getParamToEdit(); - final DocStringFormat format = getDocStringFormat(); + final DocStringFormat format = myDocStringFormat; if (format == DocStringFormat.PLAIN) { return; } @@ -298,15 +331,6 @@ public class PyDocstringGenerator { } } - @NotNull - private String getDocStringIndentation() { - String indentation = ""; - if (myDocStringOwner instanceof PyStatementListContainer) { - indentation = PyIndentUtil.getElementIndent(((PyStatementListContainer)myDocStringOwner).getStatementList()); - } - return indentation; - } - @NotNull public String buildDocString() { prepareParameters(); @@ -320,8 +344,8 @@ public class PyDocstringGenerator { @NotNull private String createDocString() { - final String indentation = getDocStringIndentation(); - final DocStringFormat format = getDocStringFormat(); + final String indentation = myDocStringIndent; + final DocStringFormat format = myDocStringFormat; DocStringBuilder builder = null; if (format == DocStringFormat.EPYTEXT || format == DocStringFormat.REST) { builder = new TagBasedDocStringBuilder(format == DocStringFormat.EPYTEXT ? "@" : ":"); @@ -396,21 +420,19 @@ public class PyDocstringGenerator { @NotNull private String updateDocString() { - final DocStringFormat format = getDocStringFormat(); DocStringUpdater updater = null; - final String docStringIndent = getDocStringIndentation(); - if (format == DocStringFormat.EPYTEXT || format == DocStringFormat.REST) { - final String prefix = format == DocStringFormat.EPYTEXT ? "@" : ":"; + if (myDocStringFormat == DocStringFormat.EPYTEXT || myDocStringFormat == DocStringFormat.REST) { + final String prefix = myDocStringFormat == DocStringFormat.EPYTEXT ? "@" : ":"; //noinspection unchecked,ConstantConditions - updater = new TagBasedDocStringUpdater((TagBasedDocString)getStructuredDocString(), prefix, docStringIndent); + updater = new TagBasedDocStringUpdater((TagBasedDocString)getStructuredDocString(), prefix, myDocStringIndent); } - else if (format == DocStringFormat.GOOGLE) { + else if (myDocStringFormat == DocStringFormat.GOOGLE) { //noinspection ConstantConditions - updater = new GoogleCodeStyleDocStringUpdater((SectionBasedDocString)getStructuredDocString(), docStringIndent); + updater = new GoogleCodeStyleDocStringUpdater((SectionBasedDocString)getStructuredDocString(), myDocStringIndent); } - else if (format == DocStringFormat.NUMPY) { + else if (myDocStringFormat == DocStringFormat.NUMPY) { //noinspection ConstantConditions - updater = new NumpyDocStringUpdater((SectionBasedDocString)getStructuredDocString(), docStringIndent); + updater = new NumpyDocStringUpdater((SectionBasedDocString)getStructuredDocString(), myDocStringIndent); } if (updater != null) { for (DocstringParam param : myAddedParams) { @@ -440,6 +462,7 @@ public class PyDocstringGenerator { @NotNull public PyDocStringOwner buildAndInsert() { + Preconditions.checkNotNull(myDocStringOwner, "For this action docstring owner must be supplied"); final String replacementText = buildDocString(); final Project project = myDocStringOwner.getProject(); diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index 717d5f3cde90..3b9ed287b24f 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -261,7 +261,7 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter { if (docOwner != null) { final int caretOffset = editor.getCaretModel().getOffset(); final String quotes = editor.getDocument().getText(TextRange.from(caretOffset - 3, 3)); - final String docString = new PyDocstringGenerator(docOwner) + final String docString = PyDocstringGenerator.forDocStringOwner(docOwner) .forceAddReturn() .withQuotes(quotes) .forceNewMode() diff --git a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java index 09e1833e582f..364bac9d0ef5 100644 --- a/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonSpaceHandler.java @@ -49,7 +49,7 @@ public class PythonSpaceHandler extends TypedHandlerDelegate { if (docOwner != null) { final Document document = editor.getDocument(); final String quotes = document.getText(TextRange.from(expectedStringStart, 3)); - final String docString = new PyDocstringGenerator(docOwner) + final String docString = PyDocstringGenerator.forDocStringOwner(docOwner) .forceNewMode() .withQuotes(quotes) .forceAddReturn() diff --git a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java index b46b0c2091c8..853b18760351 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java @@ -88,7 +88,7 @@ public class DocstringQuickFix implements LocalQuickFix { return; } if (docStringExpression != null) { - final PyDocstringGenerator generator = new PyDocstringGenerator(docStringOwner); + final PyDocstringGenerator generator = PyDocstringGenerator.forDocStringOwner(docStringOwner); if (myMissingText != null) { generator.withParam(myMissingText); } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveParameterQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveParameterQuickFix.java index 6ef60e59d375..bd872fdee632 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveParameterQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyRemoveParameterQuickFix.java @@ -73,7 +73,7 @@ public class PyRemoveParameterQuickFix implements LocalQuickFix { final PyStringLiteralExpression expression = pyFunction.getDocStringExpression(); final String paramName = ((PyParameter)element).getName(); if (expression != null && paramName != null) { - new PyDocstringGenerator(pyFunction).withoutParam(paramName).buildAndInsert(); + PyDocstringGenerator.forDocStringOwner(pyFunction).withoutParam(paramName).buildAndInsert(); } } element.delete(); diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java index 3dc62b258a28..890f74f9dbb4 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java @@ -335,7 +335,7 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc } final Module module = ModuleUtilCore.findModuleForPsiElement(function); if (module == null) return; - final PyDocstringGenerator generator = new PyDocstringGenerator(function); + final PyDocstringGenerator generator = PyDocstringGenerator.forDocStringOwner(function); for (PyParameter p : function.getParameterList().getParameters()) { final String paramName = p.getName(); if (!names.contains(paramName) && paramName != null) { @@ -353,7 +353,9 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc final PyStringLiteralExpression docstring = baseMethod.getDocStringExpression(); final PyParameter[] oldParameters = baseMethod.getParameterList().getParameters(); final PyElementGenerator generator = PyElementGenerator.getInstance(baseMethod.getProject()); - for (int i = 0; i != parameters.length; ++i) { + final PyDocstringGenerator docStringGenerator = PyDocstringGenerator.forDocStringOwner(baseMethod); + boolean newParameterInDocString = false; + for (int i = 0; i < parameters.length; ++i) { final PyParameterInfo info = parameters[i]; final int oldIndex = info.getOldIndex(); @@ -362,7 +364,8 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc } if (docstring != null && oldIndex < 0) { - new PyDocstringGenerator(baseMethod).withParam(info.getName()).buildAndInsert(); + newParameterInDocString = true; + docStringGenerator.withParam(info.getName()); } if (oldIndex < oldParameters.length) { @@ -381,9 +384,12 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc if (defaultValue != null && info.getDefaultInSignature() && StringUtil.isNotEmpty(defaultValue)) { builder.append(" = ").append(defaultValue); } - } builder.append("): pass"); + + if (newParameterInDocString) { + docStringGenerator.buildAndInsert(); + } final PyParameterList newParameterList = generator.createFromText(LanguageLevel.forElement(baseMethod), PyFunction.class,