diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 93da871b7a5c..23b204a71d59 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.intellij.openapi.util.Condition; 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; @@ -259,33 +258,6 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen return line.split(":", 1); } - // like Python's textwrap.dedent() - @NotNull - protected static String stripCommonIndent(@NotNull Substring text, boolean ignoreFirstStringIfNonEmpty) { - final List lines = text.splitLines(); - if (lines.isEmpty()) { - return ""; - } - final String firstLine = lines.get(0).toString(); - final boolean skipFirstLine = ignoreFirstStringIfNonEmpty && !StringUtil.isEmptyOrSpaces(firstLine); - final Iterable workList = lines.subList(skipFirstLine ? 1 : 0, lines.size()); - int curMinIndent = Integer.MAX_VALUE; - for (Substring line : workList) { - if (StringUtil.isEmptyOrSpaces(line)) { - continue; - } - curMinIndent = Math.min(curMinIndent, PyIndentUtil.getLineIndentSize(line)); - } - final int minIndent = curMinIndent; - final List dedentedLines = ContainerUtil.map(workList, new Function() { - @Override - public String fun(Substring line) { - return line.substring(Math.min(line.length(), minIndent)).toString(); - } - }); - return StringUtil.join(skipFirstLine ? ContainerUtil.prepend(dedentedLines, firstLine) : dedentedLines, "\n"); - } - @NotNull public List
getSections() { return Collections.unmodifiableList(mySections); @@ -624,7 +596,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen @NotNull public String getDescription() { - return myDescription == null ? "" : stripCommonIndent(myDescription, true); + return myDescription == null ? "" : PyIndentUtil.removeCommonIndent(myDescription.getValue(), true); } @Nullable diff --git a/python/src/com/jetbrains/python/psi/PyIndentUtil.java b/python/src/com/jetbrains/python/psi/PyIndentUtil.java index d476339519e3..8f5d337530d5 100644 --- a/python/src/com/jetbrains/python/psi/PyIndentUtil.java +++ b/python/src/com/jetbrains/python/psi/PyIndentUtil.java @@ -15,7 +15,9 @@ */ package com.jetbrains.python.psi; +import com.google.common.collect.Iterables; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; @@ -23,9 +25,14 @@ import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PythonFileType; import org.jetbrains.annotations.NotNull; +import java.util.Collections; +import java.util.List; + /** * @author Mikhail Golubev */ @@ -99,4 +106,89 @@ public class PyIndentUtil { public static String getIndentFromSettings(@NotNull Project project) { return StringUtil.repeatSymbol(' ', getIndentSizeFromSettings(project)); } + + @NotNull + public static List removeCommonIndent(@NotNull Iterable lines, boolean ignoreFirstLine) { + return changeIndent(lines, ignoreFirstLine, ""); + } + + @NotNull + public static String removeCommonIndent(@NotNull String s, boolean ignoreFirstLine) { + final List trimmed = removeCommonIndent(LineTokenizer.tokenizeIntoList(s, false), ignoreFirstLine); + return StringUtil.join(trimmed, "\n"); + } + + @NotNull + public static String changeIndent(@NotNull String s, boolean ignoreFirstLine, String newIndent) { + final List trimmed = changeIndent(LineTokenizer.tokenizeIntoList(s, false), ignoreFirstLine, newIndent); + return StringUtil.join(trimmed, "\n"); + } + + + + /** + * Not that all empty lines will be trimmed. + */ + @NotNull + public static List changeIndent(@NotNull Iterable lines, boolean ignoreFirstLine, final String newIndent) { + final String oldIndent = findCommonIndent(lines, ignoreFirstLine); + if (Iterables.isEmpty(lines)) { + return Collections.emptyList(); + } + + final List result = ContainerUtil.map(Iterables.skip(lines, ignoreFirstLine ? 1 : 0), new Function() { + @Override + public String fun(String line) { + if (StringUtil.isEmptyOrSpaces(line)) { + return ""; + } + else { + return newIndent + line.substring(oldIndent.length()); + } + } + }); + if (ignoreFirstLine) { + return ContainerUtil.prepend(result, Iterables.get(lines, 0)); + } + return result; + } + + /** + * If lines include non-empty lines, all empty lines or lines that contain only spaces are ignored. + * Otherwise (all line are empty) their common indent are returned as expected. If any two lines + * have different indentation (e.g. one contains tab character and another doesn't), empty prefix + * is returned. + */ + @NotNull + public static String findCommonIndent(@NotNull Iterable lines, boolean ignoreFirstLine) { + String minIndent = null; + boolean allLinesEmpty = true; + if (Iterables.isEmpty(lines)) { + return ""; + } + boolean hasBadEmptyLineIndent = false; + for (String line : Iterables.skip(lines, ignoreFirstLine ? 1 : 0)) { + final boolean lineEmpty = StringUtil.isEmptyOrSpaces(line); + if (lineEmpty && !allLinesEmpty) { + continue; + } + final String indent = (String)getLineIndent(line); + if (minIndent == null || (!lineEmpty && allLinesEmpty) || minIndent.startsWith(indent)) { + minIndent = indent; + } + else if (!indent.startsWith(minIndent)) { + if (lineEmpty) { + hasBadEmptyLineIndent = true; + } + else { + return ""; + } + } + allLinesEmpty &= lineEmpty; + } + if (allLinesEmpty && hasBadEmptyLineIndent) { + return ""; + } + return StringUtil.notNullize(minIndent); + } }