From 7c46c74e19c55e582155d4f6cef58bf47cb81b9f Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Thu, 20 Aug 2015 15:38:37 +0300 Subject: [PATCH] Support updates of tag bases docstrings. All existing tests pass --- .../jetbrains/python/toolbox/Substring.java | 2 +- .../documentation/DocStringLineParser.java | 13 +- .../GoogleCodeStyleDocString.java | 2 +- .../python/documentation/NumpyDocString.java | 2 +- .../documentation/PyDocstringGenerator.java | 124 ++++++++++-------- .../documentation/SectionBasedDocString.java | 9 +- .../docstrings/DocStringBuilder.java | 2 +- .../docstrings/DocStringUpdater.java | 95 +++++--------- .../docstrings/EpydocDocStringProvider.java | 3 +- .../docstrings/SphinxDocstringProvider.java | 3 +- .../docstrings/TagBasedDocStringUpdater.java | 101 +++++++------- .../jetbrains/python/psi/PyIndentUtil.java | 2 +- ...TypeInDocStringDifferentIndentationSize.py | 2 +- 13 files changed, 173 insertions(+), 187 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java b/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java index db332c1efb0f..8ff9787296b5 100644 --- a/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java +++ b/python/psi-api/src/com/jetbrains/python/toolbox/Substring.java @@ -174,7 +174,7 @@ public class Substring implements CharSequence { } public boolean endsWith(@NotNull String prefix) { - return myString.lastIndexOf(prefix) == length(); + return myString.lastIndexOf(prefix) == length() - prefix.length(); } public int indexOf(@NotNull String s) { diff --git a/python/src/com/jetbrains/python/documentation/DocStringLineParser.java b/python/src/com/jetbrains/python/documentation/DocStringLineParser.java index 3f387664db0d..ba99a253dc1e 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringLineParser.java +++ b/python/src/com/jetbrains/python/documentation/DocStringLineParser.java @@ -36,8 +36,13 @@ public abstract class DocStringLineParser { myLines = Collections.unmodifiableList(content.splitLines()); } - protected static int getIndent(@NotNull CharSequence line) { - return PyIndentUtil.getLineIndentSize(line); + public int getLineIndentSize(int lineNum) { + return PyIndentUtil.getLineIndentSize(getLine(lineNum)); + } + + @NotNull + public String getLineIndent(int lineNum) { + return PyIndentUtil.getLineIndent(getLine(lineNum)).toString(); } public boolean isEmptyOrDoesNotExist(int lineNum) { @@ -53,10 +58,6 @@ public abstract class DocStringLineParser { return myLines.get(lineNum); } - public int getLineIndent(int lineNum) { - return getIndent(myLines.get(lineNum)); - } - public int getLineByOffset(int offset) { return StringUtil.countNewLines(myDocStringContent.subSequence(0, offset)); } diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index 73f11197df1a..affdc3ebc9ce 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -99,7 +99,7 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString { } description = colonSeparatedParts.get(1); // parse line with indentation at least one space greater than indentation of the field - final Pair, Integer> pair = parseIndentedBlock(lineNum + 1, getIndent(line), sectionIndent); + final Pair, Integer> pair = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum), sectionIndent); final List nestedBlock = pair.getFirst(); if (!nestedBlock.isEmpty()) { //noinspection ConstantConditions diff --git a/python/src/com/jetbrains/python/documentation/NumpyDocString.java b/python/src/com/jetbrains/python/documentation/NumpyDocString.java index d830c9f1e757..e42300fcfb98 100644 --- a/python/src/com/jetbrains/python/documentation/NumpyDocString.java +++ b/python/src/com/jetbrains/python/documentation/NumpyDocString.java @@ -78,7 +78,7 @@ public class NumpyDocString extends SectionBasedDocString { type = name; name = null; } - final Pair, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getIndent(line), sectionIndent); + final Pair, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum), sectionIndent); final List descriptionLines = parsedDescription.getFirst(); if (!descriptionLines.isEmpty()) { description = mergeSubstrings(descriptionLines.get(0), descriptionLines.get(descriptionLines.size() - 1)); diff --git a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java index 8f8e6dae42c1..5cb0ddb45f8f 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java @@ -28,11 +28,11 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; import com.jetbrains.python.PyNames; import com.jetbrains.python.codeInsight.PyCodeInsightSettings; import com.jetbrains.python.documentation.docstrings.DocStringProvider; import com.jetbrains.python.documentation.docstrings.TagBasedDocStringBuilder; +import com.jetbrains.python.documentation.docstrings.TagBasedDocStringUpdater; import com.jetbrains.python.psi.*; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NotNull; @@ -46,36 +46,25 @@ import java.util.List; */ public class PyDocstringGenerator { - @NotNull - private PyDocStringOwner myDocStringOwner; - @Nullable - private PyFunction myFunction; - private final List myParams = Lists.newArrayList(); - private final Project myProject; - private PyStringLiteralExpression myDocStringExpression; - - private final PsiFile myFile; private boolean myGenerateReturn; - private final StructuredDocString myStructuredDocString; + // Updated after buildAndInsert + @NotNull + private PyDocStringOwner myDocStringOwner; + private final StructuredDocString myOriginalDocString; private final DocStringFormat myDocStringFormat; public PyDocstringGenerator(@NotNull PyDocStringOwner docStringOwner) { myDocStringOwner = docStringOwner; - if (docStringOwner instanceof PyFunction) { - myFunction = (PyFunction)docStringOwner; - } - myProject = myDocStringOwner.getProject(); - myFile = myDocStringOwner.getContainingFile(); myDocStringFormat = DocStringUtil.getDocStringFormat(docStringOwner); final DocStringProvider provider = myDocStringFormat.getProvider(); final PyStringLiteralExpression docStringExpression = myDocStringOwner.getDocStringExpression(); if (docStringExpression != null) { - myStructuredDocString = provider.parseDocString(docStringExpression); + myOriginalDocString = provider.parseDocString(docStringExpression); } else { - myStructuredDocString = null; + myOriginalDocString = null; } } @@ -102,6 +91,16 @@ public class PyDocstringGenerator { return this; } + @Nullable + private PyStringLiteralExpression getDocStringExpression() { + return myDocStringOwner.getDocStringExpression(); + } + + @Nullable + private PyFunction getOwnerFunction() { + return PyUtil.as(myDocStringOwner, PyFunction.class); + } + public static String generateRaiseOrReturn(@NotNull PyFunction element, String offset, String prefix, boolean checkReturn) { final StringBuilder builder = new StringBuilder(); if (checkReturn) { @@ -131,14 +130,11 @@ public class PyDocstringGenerator { return builder.toString(); } - private PsiFile getFile() { - return myFile; - } - public void startTemplate() { - assert myDocStringExpression != null; + final PyStringLiteralExpression docStringExpression = getDocStringExpression(); + assert docStringExpression != null; - final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(myDocStringExpression); + final TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(docStringExpression); if (myParams.size() > 1) { throw new IllegalArgumentException("TemplateBuilder can be created only for one parameter"); @@ -146,7 +142,7 @@ public class PyDocstringGenerator { final DocstringParam paramToEdit = getParamToEdit(); final String paramName = paramToEdit.getName(); - final String stringContent = myDocStringExpression.getStringValue(); + final String stringContent = docStringExpression.getStringValue(); final StructuredDocString parsed = DocStringUtil.parse(stringContent); if (parsed == null) { return; @@ -161,15 +157,16 @@ public class PyDocstringGenerator { if (substring == null) { return; } - builder.replaceRange(substring.getTextRange().shiftRight(myDocStringExpression.getStringValueTextRange().getStartOffset()), + builder.replaceRange(substring.getTextRange().shiftRight(docStringExpression.getStringValueTextRange().getStartOffset()), getDefaultType()); Template template = ((TemplateBuilderImpl)builder).buildInlineTemplate(); - final VirtualFile virtualFile = myFile.getVirtualFile(); + final VirtualFile virtualFile = myDocStringOwner.getContainingFile().getVirtualFile(); if (virtualFile == null) return; - OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, virtualFile, myDocStringExpression.getTextOffset()); - Editor targetEditor = FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true); + final Project project = myDocStringOwner.getProject(); + OpenFileDescriptor descriptor = new OpenFileDescriptor(project, virtualFile, docStringExpression.getTextOffset()); + Editor targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true); if (targetEditor != null) { - TemplateManager.getInstance(myProject).startTemplate(targetEditor, template); + TemplateManager.getInstance(project).startTemplate(targetEditor, template); } } @@ -185,7 +182,7 @@ public class PyDocstringGenerator { @NotNull private String buildDocString() { - if (myStructuredDocString != null) { + if (myOriginalDocString != null) { return updateDocString(); } else { @@ -198,10 +195,7 @@ public class PyDocstringGenerator { if (myDocStringFormat == DocStringFormat.EPYTEXT || myDocStringFormat == DocStringFormat.REST) { final TagBasedDocStringBuilder builder = new TagBasedDocStringBuilder(myDocStringFormat == DocStringFormat.EPYTEXT ? "@" : ":"); builder.addLine(""); - String indentation = ""; - if (myDocStringOwner instanceof PyStatementListContainer) { - indentation = PyIndentUtil.getElementIndent(((PyStatementListContainer)myDocStringOwner).getStatementList()); - } + String indentation = getDocStringIndentation(); boolean addedReturn = false; for (DocstringParam param : myParams) { if (param.isReturnValue()) { @@ -246,9 +240,32 @@ public class PyDocstringGenerator { } } + @NotNull + private String getDocStringIndentation() { + String indentation = ""; + if (myDocStringOwner instanceof PyStatementListContainer) { + indentation = PyIndentUtil.getElementIndent(((PyStatementListContainer)myDocStringOwner).getStatementList()); + } + return indentation; + } + @NotNull private String updateDocString() { - return ""; + if (myDocStringFormat == DocStringFormat.EPYTEXT || myDocStringFormat == DocStringFormat.REST) { + final String prefix = myDocStringFormat == DocStringFormat.EPYTEXT ? "@" : ":"; + //noinspection unchecked + TagBasedDocStringUpdater updater = new TagBasedDocStringUpdater((TagBasedDocString)myOriginalDocString, prefix, getDocStringIndentation()); + for (DocstringParam param : myParams) { + if (param.isReturnValue()) { + updater.addReturnValue(param.getType()); + } + else { + updater.addParameter(param.getName(), param.getType()); + } + } + return updater.getDocStringText(); + } + return "\"\"\"\"\"\""; } public boolean haveParametersToAdd() { @@ -261,10 +278,10 @@ public class PyDocstringGenerator { @Override public boolean apply(DocstringParam param) { if (param.isReturnValue()) { - return myStructuredDocString.getReturnTypeSubstring() == null; + return myOriginalDocString.getReturnTypeSubstring() == null; } else { - return myStructuredDocString.getParamTypeSubstring(param.myName) == null; + return myOriginalDocString.getParamTypeSubstring(param.myName) == null; } } }); @@ -284,44 +301,41 @@ public class PyDocstringGenerator { public void buildAndInsert() { final String replacement = buildDocString(); - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(myProject); + final Project project = myDocStringOwner.getProject(); + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); // update existing docstring - if (myStructuredDocString != null) { + final PyStringLiteralExpression docStringExpression = getDocStringExpression(); + if (docStringExpression != null) { PyExpression str = elementGenerator.createDocstring(replacement).getExpression(); - myDocStringExpression.replace(str); - myDocStringOwner = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myDocStringOwner); - if (myDocStringOwner instanceof PyFunction) { - myFunction = (PyFunction)myDocStringOwner; - } + docStringExpression.replace(str); } // create brand new docstring else { - if (myFunction == null) { + PyFunction function = PyUtil.as(myDocStringOwner, PyFunction.class); + if (function == null) { throw new IllegalStateException("Should be a function"); } - final PyStatementList statements = myFunction.getStatementList(); + final PyStatementList statements = function.getStatementList(); final String indentation = PyIndentUtil.getExpectedElementIndent(statements); - final Document document = PsiDocumentManager.getInstance(myProject).getDocument(getFile()); + final Document document = PsiDocumentManager.getInstance(project).getDocument(myDocStringOwner.getContainingFile()); if (document != null) { - if (PyUtil.onSameLine(statements, myFunction) || statements.getStatements().length == 0) { - PyFunction func = elementGenerator.createFromText(LanguageLevel.forElement(myFunction), + if (PyUtil.onSameLine(statements, function) || statements.getStatements().length == 0) { + PyFunction func = elementGenerator.createFromText(LanguageLevel.forElement(function), PyFunction.class, - "def " + myFunction.getName() + myFunction.getParameterList().getText() + ":\n" + + "def " + function.getName() + function.getParameterList().getText() + ":\n" + indentation + replacement + "\n" + indentation + statements.getText()); - myFunction = (PyFunction)myFunction.replace(func); + myDocStringOwner = (PyFunction)function.replace(func); } else { PyExpressionStatement str = elementGenerator.createDocstring(replacement); statements.addBefore(str, statements.getStatements()[0]); } } - - CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myFunction); - myDocStringExpression = myFunction.getDocStringExpression(); } + myDocStringOwner = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myDocStringOwner); } public static class DocstringParam { diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 6b9425c8f80d..64a46c820922 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -23,6 +23,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.python.psi.PyIndentUtil; import com.jetbrains.python.psi.StructuredDocString; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NonNls; @@ -146,7 +147,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen } int lineNum = skipEmptyLines(pair.getSecond()); final List fields = new ArrayList(); - final int sectionIndent = getIndent(getLine(sectionStartLine)); + final int sectionIndent = getLineIndentSize(sectionStartLine); while (!isSectionBreak(lineNum, sectionIndent)) { if (!isEmpty(lineNum)) { final Pair result = parseSectionField(lineNum, title, sectionIndent); @@ -217,7 +218,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen protected boolean isSectionBreak(int lineNum, int curSectionIndent) { return lineNum >= getLineCount() || isSectionStart(lineNum) || - (!isEmpty(lineNum) && getIndent(getLine(lineNum)) <= curSectionIndent); + (!isEmpty(lineNum) && getLineIndentSize(lineNum) <= curSectionIndent); } /** @@ -231,7 +232,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen final List result = new ArrayList(); int lastNonEmpty = lineNum - 1; while (!isSectionBreak(lineNum, sectionIndent)) { - if (getIndent(getLine(lineNum)) > blockIndent) { + if (getLineIndentSize(lineNum) > blockIndent) { // copy all lines after the last non empty including the current one for (int i = lastNonEmpty + 1; i <= lineNum; i++) { result.add(getLine(i)); @@ -302,7 +303,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen if (StringUtil.isEmptyOrSpaces(line)) { continue; } - curMinIndent = Math.min(curMinIndent, getIndent(line)); + curMinIndent = Math.min(curMinIndent, PyIndentUtil.getLineIndentSize(line)); } final int minIndent = curMinIndent; final List dedentedLines = ContainerUtil.map(workList, new Function() { diff --git a/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java b/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java index 873e132a531c..449d65de97da 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/DocStringBuilder.java @@ -49,7 +49,7 @@ public abstract class DocStringBuilder { @NotNull public String buildContent(int indent, boolean indentFirst) { - return buildContent(StringUtil.repeat(" ", indent), indentFirst); + return buildContent(StringUtil.repeatSymbol(' ', indent), indentFirst); } @NotNull diff --git a/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java index fe17e23be352..83868dd53734 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/DocStringUpdater.java @@ -17,9 +17,9 @@ package com.jetbrains.python.documentation.docstrings; import com.intellij.openapi.util.TextRange; import com.jetbrains.python.documentation.DocStringLineParser; +import com.jetbrains.python.psi.PyIndentUtil; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -31,43 +31,35 @@ import java.util.List; public abstract class DocStringUpdater { protected final T myOriginalDocString; private final StringBuilder myBuilder; - private final List myUpdates = new ArrayList(); - protected final List myAddParameterRequests = new ArrayList(); - protected final List myAddExceptionRequest = new ArrayList(); - protected final List myAddReturnTypeRequests = new ArrayList(); + private final List myUpdates = new ArrayList(); + private final String myMinContentIndent; - public DocStringUpdater(@NotNull T docString) { + public DocStringUpdater(@NotNull T docString, @NotNull String minContentIndent) { myBuilder = new StringBuilder(docString.getDocStringContent().getSuperString()); myOriginalDocString = docString; + myMinContentIndent = minContentIndent; } - public final void addParameter(@NotNull String name, @Nullable String type) { - myAddParameterRequests.add(new AddParameter(name, type)); - } - - public final void addReturnType(@Nullable String name, @NotNull String type) { - myAddReturnTypeRequests.add(new AddReturnType(name, type)); - } - - public final void addException(@NotNull String type) { - myAddExceptionRequest.add(new AddException(type)); - } - - protected void insert(int offset, @NotNull String text) { - myUpdates.add(new UpdateOperation(TextRange.from(offset, 0), text)); - } - - protected final void insertAfterLine(int lineNumber, @NotNull String text) { - final Substring line = myOriginalDocString.getLines().get(lineNumber); - insert(line.getEndOffset(), "\n" + text); + protected final void replace(@NotNull TextRange range, @NotNull String text) { + myUpdates.add(new Modification(range, text)); } protected final void replace(int startOffset, int endOffset, @NotNull String text) { replace(new TextRange(startOffset, endOffset), text); } - protected final void replace(@NotNull TextRange range, @NotNull String text) { - myUpdates.add(new UpdateOperation(range, text)); + protected final void insert(int offset, @NotNull String text) { + replace(offset, offset, text); + } + + protected final void insertAfterLine(int lineNumber, @NotNull String text) { + final Substring line = myOriginalDocString.getLines().get(lineNumber); + insert(line.getEndOffset(), '\n' + text); + } + + protected final void insertBeforeLine(int lineNumber, @NotNull String text) { + final Substring line = myOriginalDocString.getLines().get(lineNumber); + insert(line.getStartOffset(), text + '\n'); } protected abstract void scheduleUpdates(); @@ -75,11 +67,15 @@ public abstract class DocStringUpdater { @NotNull public final String getDocStringText() { scheduleUpdates(); - // if several updates insert in one place (e.g. new field), insert them in backward order + // Move closing quotes to the next line, if new lines are going to be inserted + if (myOriginalDocString.getLineCount() == 1 && !myUpdates.isEmpty()) { + insertAfterLine(0, myMinContentIndent); + } + // If several updates insert in one place (e.g. new field), insert them in backward order, + // so the first added is placed above Collections.reverse(myUpdates); - Collections.sort(myUpdates); - for (int i = myUpdates.size() - 1; i >= 0; i--) { - final UpdateOperation update = myUpdates.get(i); + Collections.sort(myUpdates, Collections.reverseOrder()); + for (final Modification update : myUpdates) { final TextRange updateRange = update.range; if (updateRange.getStartOffset() == updateRange.getEndOffset()) { myBuilder.insert(updateRange.getStartOffset(), update.text); @@ -96,45 +92,26 @@ public abstract class DocStringUpdater { return myOriginalDocString; } - protected static class AddParameter { - @NotNull final String name; - @Nullable final String type; - - public AddParameter(@NotNull String name, @Nullable String type) { - this.name = name; - this.type = type; + @NotNull + protected String getLineIndent(int lastNonEmptyLine) { + final String indent = myOriginalDocString.getLineIndent(lastNonEmptyLine); + if (PyIndentUtil.getLineIndentSize(indent) < PyIndentUtil.getLineIndentSize(myMinContentIndent)) { + return myMinContentIndent; } + return indent; } - protected static class AddReturnType { - @Nullable final String name; - @NotNull final String type; - - public AddReturnType(@Nullable String name, @NotNull String type) { - this.name = name; - this.type = type; - } - } - - protected static class AddException { - @NotNull final String type; - - public AddException(@NotNull String type) { - this.type = type; - } - } - - private static class UpdateOperation implements Comparable { + private static class Modification implements Comparable { @NotNull final TextRange range; @NotNull final String text; - public UpdateOperation(@NotNull TextRange range, @NotNull String newText) { + public Modification(@NotNull TextRange range, @NotNull String newText) { this.range = range; this.text = newText; } @Override - public int compareTo(UpdateOperation o) { + public int compareTo(Modification o) { return range.getStartOffset() - o.range.getStartOffset(); } } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/EpydocDocStringProvider.java b/python/src/com/jetbrains/python/documentation/docstrings/EpydocDocStringProvider.java index a58d12b4ea8d..6eb25ef30813 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/EpydocDocStringProvider.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/EpydocDocStringProvider.java @@ -34,8 +34,7 @@ public class EpydocDocStringProvider extends DocStringProvider { @NotNull @Override public DocStringUpdater updateDocString(@NotNull EpydocString docstring) { - return new TagBasedDocStringUpdater(docstring, TAG_PREFIX) { - @Override + return new TagBasedDocStringUpdater(docstring, TAG_PREFIX, "") { public DocStringBuilder createDocStringBuilder() { return createDocString(); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/SphinxDocstringProvider.java b/python/src/com/jetbrains/python/documentation/docstrings/SphinxDocstringProvider.java index e1d238fcca85..2cd2c2a1b7bf 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/SphinxDocstringProvider.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/SphinxDocstringProvider.java @@ -34,8 +34,7 @@ public class SphinxDocstringProvider extends DocStringProvider{ @NotNull @Override public DocStringUpdater updateDocString(@NotNull SphinxDocString docstring) { - return new TagBasedDocStringUpdater(docstring, TAG_PREFIX) { - @Override + return new TagBasedDocStringUpdater(docstring, TAG_PREFIX, "") { public DocStringBuilder createDocStringBuilder() { return createDocString(); } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java b/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java index 7f1e8426c039..273d10929d10 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/TagBasedDocStringUpdater.java @@ -15,84 +15,79 @@ */ package com.jetbrains.python.documentation.docstrings; +import com.intellij.openapi.util.text.StringUtil; import com.jetbrains.python.documentation.TagBasedDocString; import com.jetbrains.python.toolbox.Substring; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author Mikhail Golubev */ -public abstract class TagBasedDocStringUpdater extends DocStringUpdater{ +public class TagBasedDocStringUpdater extends DocStringUpdater{ private final String myTagPrefix; - public TagBasedDocStringUpdater(@NotNull T docString, @NotNull String prefix) { - super(docString); - myTagPrefix = prefix; + public TagBasedDocStringUpdater(@NotNull T docString, @NotNull String tagPrefix, @NotNull String minContentIndent) { + super(docString, minContentIndent); + myTagPrefix = tagPrefix; + } + + @NotNull + private TagBasedDocStringBuilder createBuilder() { + return new TagBasedDocStringBuilder(myTagPrefix); } @Override protected void scheduleUpdates() { - final int anchorLine = firstLineWithTag(); - final int anchorLineIndent = myOriginalDocString.getLineIndent(anchorLine); - for (AddParameter paramReq : myAddParameterRequests) { - if (!myOriginalDocString.getParameters().contains(paramReq.name)) { - insertAfterLine(anchorLine, createParameterNameLine(paramReq.name, anchorLineIndent)); - } - if (paramReq.type != null) { - final Substring typeSub = myOriginalDocString.getParamTypeSubstring(paramReq.name); - if (typeSub != null) { - replace(typeSub.getTextRange(), paramReq.type); - } - else { - insertAfterLine(anchorLine, createParameterTypeLine(paramReq.name, paramReq.type, anchorLineIndent)); - } - } + } + + public final void addParameter(@NotNull String name, @Nullable String type) { + if (type != null) { + insertTagLine(createBuilder().addParameterType(name, type)); } - for (AddReturnType returnReq : myAddReturnTypeRequests) { - final Substring typeSub = myOriginalDocString.getReturnTypeSubstring(); - if (typeSub != null) { - replace(typeSub.getTextRange(), returnReq.type); - } - else { - insertAfterLine(anchorLine, createReturnTypeLine(returnReq.type, anchorLineIndent)); - } - } - for (AddException exception : myAddExceptionRequest) { - //TODO check existing docstring - insertAfterLine(anchorLine, createRaisesLine(exception.type, anchorLineIndent)); + else { + insertTagLine(createBuilder().addParameterDescription(name, "")); } } - @NotNull - private String createRaisesLine(@NotNull String type, int indent) { - return ""; /*createDocStringBuilder().addException(type).buildContent(indent, true);*/ + public final void addReturnValue(@Nullable String type) { + if (type != null) { + insertTagLine(createBuilder().addReturnValueType(type)); + } + else { + insertTagLine(createBuilder().addReturnValueDescription("")); + } } - @NotNull - private String createParameterNameLine(@NotNull String name, int indent) { - return ""; /*createDocStringBuilder().addParameter(name, null).buildContent(indent, true);*/ + private void insertTagLine(@NotNull DocStringBuilder lineBuilder) { + final int firstLineWithTag = findFirstLineWithTag(); + if (firstLineWithTag >= 0) { + final String indent = getLineIndent(firstLineWithTag); + insertBeforeLine(firstLineWithTag, lineBuilder.buildContent(indent, true)); + return; + } + final int lastNonEmptyLine = findLastNonEmptyLine(); + final String indent = getLineIndent(lastNonEmptyLine); + insertAfterLine(lastNonEmptyLine, lineBuilder.buildContent(indent, true)); } - @NotNull - private String createParameterTypeLine(@NotNull String name, @NotNull String type, int indent) { - return ""; /*createDocStringBuilder().addParameterType(name, type).buildContent(indent, true);*/ - } - - @NotNull - private String createReturnTypeLine(@NotNull String type, int indent) { - return ""; /*createDocStringBuilder().addReturnValue(null, type).buildContent(indent, true);*/ - } - - private int firstLineWithTag() { - for (int i = 0; i < myOriginalDocString.getLineCount(); i++) { - final Substring line = myOriginalDocString.getLine(i); - if (line.contains(myTagPrefix)) { + private int findLastNonEmptyLine() { + for (int i = myOriginalDocString.getLineCount() - 1; i >= 0; i--) { + if (StringUtil.isEmptyOrSpaces(myOriginalDocString.getLine(i))) { return i; } } - return myOriginalDocString.getLineCount() - 1; + return 0; } - public abstract DocStringBuilder createDocStringBuilder(); + private int findFirstLineWithTag() { + for (int i = 0; i < myOriginalDocString.getLineCount(); i++) { + final Substring line = myOriginalDocString.getLine(i); + if (line.trimLeft().startsWith(myTagPrefix)) { + return i; + } + } + return -1; + } } diff --git a/python/src/com/jetbrains/python/psi/PyIndentUtil.java b/python/src/com/jetbrains/python/psi/PyIndentUtil.java index 03dc1862225a..796c452c6217 100644 --- a/python/src/com/jetbrains/python/psi/PyIndentUtil.java +++ b/python/src/com/jetbrains/python/psi/PyIndentUtil.java @@ -42,7 +42,7 @@ public class PyIndentUtil { int stop; for (stop = 0; stop < line.length(); stop++) { final char c = line.charAt(stop); - if (c == ' ' || c == '\t') { + if (!(c == ' ' || c == '\t')) { break; } } diff --git a/python/testData/intentions/afterReturnTypeInDocStringDifferentIndentationSize.py b/python/testData/intentions/afterReturnTypeInDocStringDifferentIndentationSize.py index 3e71c84c1972..c1a700fa3dac 100644 --- a/python/testData/intentions/afterReturnTypeInDocStringDifferentIndentationSize.py +++ b/python/testData/intentions/afterReturnTypeInDocStringDifferentIndentationSize.py @@ -1,6 +1,6 @@ def func(x): """ - + :rtype: object """ return 1 \ No newline at end of file