diff --git a/python/src/com/jetbrains/python/console/PyConsoleIndentUtil.java b/python/src/com/jetbrains/python/console/PyConsoleIndentUtil.java new file mode 100644 index 000000000000..7728d30fc331 --- /dev/null +++ b/python/src/com/jetbrains/python/console/PyConsoleIndentUtil.java @@ -0,0 +1,129 @@ +package com.jetbrains.python.console; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Scanner; + +/** + * @author traff + */ +public class PyConsoleIndentUtil { + private static final int TAB_INDENT = 4; + + private PyConsoleIndentUtil() { + } + + public static String normalize(@NotNull String codeFragment) { + Scanner s = new Scanner(codeFragment); + + List lines = Lists.newArrayList(); + List indents = Lists.newArrayList(); + int minIndent = Integer.MAX_VALUE; + while (s.hasNextLine()) { + String line = s.nextLine(); + int indent = 0; + boolean flag = false; + for (char c : line.toCharArray()) { + if (c == ' ') { + indent++; + } + else if (c == '\t') { + indent += TAB_INDENT; + } + else { + flag = true; + break; + } + } + if (flag && indent < minIndent) { + minIndent = indent; + } + lines.add(line.trim()); + indents.add(indent); + } + + int[] indentArray = ArrayUtil.toIntArray(indents); + + shiftLeftAll(indentArray, lines); + + shiftToParentOnUnindent(indentArray); + + return padOutput(lines, indentArray); + } + + private static void shiftToParentOnUnindent(int[] indentArray) { + int[] stack = new int[indentArray.length]; + int count = 0; + int replace = -1; + int replaceBy = -1; + for (int i = 0; i < indentArray.length; i++) { + if (indentArray[i] == replace) { + indentArray[i] = replaceBy; + } + else { + replace = -1; + if (count == 0 || indentArray[i] > stack[count - 1]) { + stack[count++] = indentArray[i]; + } + if (count > 0 && indentArray[i] < stack[count - 1]) { + do { + count--; + } + while (count > 0 && stack[count - 1] > indentArray[i]); + if (count > 0) { + replace = indentArray[i]; + replaceBy = stack[count - 1]; + indentArray[i] = replaceBy; + } + } + } + } + } + + private static void shiftLeftAll(int[] indentArray, List lines) { + if (indentArray.length == 0) { + return; + } + int indent = indentArray[0]; + int lastIndent = Integer.MAX_VALUE; + boolean lastIndented = false; + for (int i = 0; i < indentArray.length; i++) { + if (!StringUtil.isEmpty(lines.get(i))) { + if (indentArray[i] < indent || indentArray[i] > lastIndent && !lastIndented) { + indent = indentArray[i]; + } + lastIndent = indentArray[i]; + indentArray[i] -= indent; + if (lines.get(i).trim().endsWith(":")) { + lastIndented = true; + } + else { + lastIndented = false; + } + + } + } + } + + private static String padOutput(List lines, int[] indentArray) { + int i = 0; + StringBuilder result = new StringBuilder(); + for (String line : lines) { + if (!StringUtil.isEmpty(line)) { + line = Strings.padStart(line, indentArray[i] + line.length(), ' '); + } + i++; + result.append(line); + if (i < lines.size()) { + result.append("\n"); + } + } + + return result.toString(); + } +} diff --git a/python/src/com/jetbrains/python/console/PydevLanguageConsoleView.java b/python/src/com/jetbrains/python/console/PydevLanguageConsoleView.java index cc86ca3ebd50..9739ac8cf541 100644 --- a/python/src/com/jetbrains/python/console/PydevLanguageConsoleView.java +++ b/python/src/com/jetbrains/python/console/PydevLanguageConsoleView.java @@ -38,7 +38,7 @@ public class PydevLanguageConsoleView extends LanguageConsoleViewImpl implements @Override public void executeCode(@NotNull String code) { - getPydevLanguageConsole().addTextToCurrentEditor(code); + getPydevLanguageConsole().addTextToCurrentEditor(PyConsoleIndentUtil.normalize(code)); myExecuteActionHandler.runExecuteAction(getPydevLanguageConsole()); } diff --git a/python/src/com/jetbrains/python/console/PythonDebugConsoleView.java b/python/src/com/jetbrains/python/console/PythonDebugConsoleView.java index 532030d36611..30c6a25b0654 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonDebugConsoleView.java @@ -42,7 +42,7 @@ public class PythonDebugConsoleView extends LanguageConsoleViewImpl implements P @Override public void executeCode(@NotNull String code) { - getPythonLanguageConsole().addTextToCurrentEditor(code); + getPythonLanguageConsole().addTextToCurrentEditor(PyConsoleIndentUtil.normalize(code)); myExecuteActionHandler.runExecuteAction(getPythonLanguageConsole()); } diff --git a/python/testData/console/indent1.after.py b/python/testData/console/indent1.after.py new file mode 100644 index 000000000000..a549208a7133 --- /dev/null +++ b/python/testData/console/indent1.after.py @@ -0,0 +1,2 @@ +for x in range(1, 10): + print x \ No newline at end of file diff --git a/python/testData/console/indent1.py b/python/testData/console/indent1.py new file mode 100644 index 000000000000..68418371a780 --- /dev/null +++ b/python/testData/console/indent1.py @@ -0,0 +1,2 @@ + for x in range(1, 10): + print x \ No newline at end of file diff --git a/python/testData/console/indent2.after.py b/python/testData/console/indent2.after.py new file mode 100644 index 000000000000..6b3a39b5e89a --- /dev/null +++ b/python/testData/console/indent2.after.py @@ -0,0 +1,4 @@ +for x in range(1, 10): + print x +print 1 +print 2 \ No newline at end of file diff --git a/python/testData/console/indent2.py b/python/testData/console/indent2.py new file mode 100644 index 000000000000..c8a30844fb9c --- /dev/null +++ b/python/testData/console/indent2.py @@ -0,0 +1,4 @@ +for x in range(1, 10): + print x + print 1 + print 2 \ No newline at end of file diff --git a/python/testData/console/indent3.after.py b/python/testData/console/indent3.after.py new file mode 100644 index 000000000000..865fe7c8ada8 --- /dev/null +++ b/python/testData/console/indent3.after.py @@ -0,0 +1,5 @@ +for i in range(1, 10): + print i + +for i in range(1, 10): + print i \ No newline at end of file diff --git a/python/testData/console/indent3.py b/python/testData/console/indent3.py new file mode 100644 index 000000000000..ea63a54dd6a1 --- /dev/null +++ b/python/testData/console/indent3.py @@ -0,0 +1,5 @@ + for i in range(1, 10): + print i + + for i in range(1, 10): + print i \ No newline at end of file diff --git a/python/testData/console/indent4.after.py b/python/testData/console/indent4.after.py new file mode 100644 index 000000000000..f2f6e2deb8b3 --- /dev/null +++ b/python/testData/console/indent4.after.py @@ -0,0 +1,6 @@ +print 1 + +for j in range(0, 2): + for i in range(1, 10): + print i + print '!' \ No newline at end of file diff --git a/python/testData/console/indent4.py b/python/testData/console/indent4.py new file mode 100644 index 000000000000..84cfb09b60d2 --- /dev/null +++ b/python/testData/console/indent4.py @@ -0,0 +1,6 @@ +print 1 + + for j in range(0, 2): + for i in range(1, 10): + print i + print '!' \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyConsoleIndentTest.java b/python/testSrc/com/jetbrains/python/PyConsoleIndentTest.java new file mode 100644 index 000000000000..9e34ed280c5e --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyConsoleIndentTest.java @@ -0,0 +1,45 @@ +package com.jetbrains.python; + +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.testFramework.UsefulTestCase; +import com.jetbrains.python.console.PyConsoleIndentUtil; + +import java.io.File; +import java.io.IOException; + +/** + * @author traff + */ +public class PyConsoleIndentTest extends UsefulTestCase{ + public void testIndent1() { + doTest(); + } + + public void testIndent2() { + doTest(); + } + + public void testIndent3() { + doTest(); + } + + public void testIndent4() { + doTest(); + } + + private void doTest() { + String name = getTestName(true); + try { + String fileText = new String(FileUtil.loadFileText(new File(getTestDataPath() + name + ".py"))); + String expected = new String(FileUtil.loadFileText(new File(getTestDataPath() + name + ".after.py"))); + assertEquals(expected, PyConsoleIndentUtil.normalize(fileText)); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/console/"; + } +}