diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml
index 6c4665d91e20..8a9fe46241eb 100644
--- a/python/src/META-INF/python-core.xml
+++ b/python/src/META-INF/python-core.xml
@@ -18,6 +18,7 @@
+
diff --git a/python/src/com/jetbrains/python/formatter/PyBlock.java b/python/src/com/jetbrains/python/formatter/PyBlock.java
index d160749fbda8..1a867237eab3 100644
--- a/python/src/com/jetbrains/python/formatter/PyBlock.java
+++ b/python/src/com/jetbrains/python/formatter/PyBlock.java
@@ -516,7 +516,7 @@ public class PyBlock implements ASTBlock {
}
}
- if (psi2 instanceof PsiComment && !hasLineBreaksBefore(psi2.getNode(), 1)) {
+ if (psi2 instanceof PsiComment && !hasLineBreaksBefore(psi2.getNode(), 1) && myContext.getPySettings().SPACE_BEFORE_NUMBER_SIGN) {
return Spacing.createSpacing(2, 0, 0, false, 0);
}
}
diff --git a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java
index 961ca725506a..88ddd414152e 100644
--- a/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java
+++ b/python/src/com/jetbrains/python/formatter/PyCodeStyleSettings.java
@@ -38,6 +38,9 @@ public class PyCodeStyleSettings extends CustomCodeStyleSettings {
public boolean NEW_LINE_AFTER_COLON = false;
public boolean NEW_LINE_AFTER_COLON_MULTI_CLAUSE = true;
+ public boolean SPACE_AFTER_NUMBER_SIGN = true;
+ public boolean SPACE_BEFORE_NUMBER_SIGN = true;
+
public PyCodeStyleSettings(CodeStyleSettings container) {
super("Python", container);
}
diff --git a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java
index c26a1a1742bd..9e7f97db5f8e 100644
--- a/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java
+++ b/python/src/com/jetbrains/python/formatter/PyLanguageCodeStyleSettingsProvider.java
@@ -72,9 +72,12 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT", "Around = in keyword argument",
SPACES_AROUND_OPERATORS);
consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_WITHIN_BRACES", "Braces", SPACES_WITHIN);
- consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_BEFORE_PY_COLON", ApplicationBundle.message("checkbox.spaces.before.colon"), SPACES_OTHER);
+ consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_BEFORE_PY_COLON",
+ ApplicationBundle.message("checkbox.spaces.before.colon"), SPACES_OTHER);
consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_AFTER_PY_COLON", ApplicationBundle.message("checkbox.spaces.after.colon"), SPACES_OTHER);
consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_BEFORE_BACKSLASH", "Before '\\'", SPACES_OTHER);
+ consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_BEFORE_NUMBER_SIGN", "Before '#'", SPACES_OTHER);
+ consumer.showCustomOption(PyCodeStyleSettings.class, "SPACE_AFTER_NUMBER_SIGN", "After '#'", SPACES_OTHER);
}
else if (settingsType == SettingsType.BLANK_LINES_SETTINGS) {
consumer.showStandardOptions("BLANK_LINES_AROUND_CLASS",
@@ -112,7 +115,7 @@ public class PyLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettin
CommonCodeStyleSettings.IndentOptions indentOptions = defaultSettings.initIndentOptions();
indentOptions.INDENT_SIZE = 4;
defaultSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = true;
- return defaultSettings;
+ return defaultSettings;
}
@Override
diff --git a/python/src/com/jetbrains/python/formatter/PyLineWrapPositionStrategy.java b/python/src/com/jetbrains/python/formatter/PyLineWrapPositionStrategy.java
index d7fa5f0f5e7b..419d97c9e5d4 100644
--- a/python/src/com/jetbrains/python/formatter/PyLineWrapPositionStrategy.java
+++ b/python/src/com/jetbrains/python/formatter/PyLineWrapPositionStrategy.java
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
+import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.StringLiteralExpression;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -75,11 +76,20 @@ public class PyLineWrapPositionStrategy extends GenericLineWrapPositionStrategy
int maxPreferredOffset,
boolean allowToBeyondMaxPreferredOffset,
boolean virtual) {
+
int wrapPosition =
super.calculateWrapPosition(document, project, startOffset, endOffset, maxPreferredOffset, allowToBeyondMaxPreferredOffset, virtual);
if (wrapPosition < 0) return wrapPosition;
final CharSequence text = document.getCharsSequence();
+ if (wrapPosition > 0) {
+ char charBefore = text.charAt(wrapPosition - 1);
+ if (charBefore == '\'' || charBefore == '"') {
+ //don't wrap the first char of string literal
+ return wrapPosition + 1;
+ }
+ }
+
char c = text.charAt(wrapPosition);
if (!StringUtil.isWhiteSpace(c) || project == null) {
return wrapPosition;
diff --git a/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java
new file mode 100644
index 000000000000..ee56016d1df9
--- /dev/null
+++ b/python/src/com/jetbrains/python/formatter/PyPreFormatProcessor.java
@@ -0,0 +1,98 @@
+/*
+ * 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.formatter;
+
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+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.codeStyle.CodeStyleSettings;
+import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
+import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor;
+import com.jetbrains.python.PythonLanguage;
+import com.jetbrains.python.psi.LanguageLevel;
+import com.jetbrains.python.psi.PyElementGenerator;
+import com.jetbrains.python.psi.PyRecursiveElementVisitor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author traff
+ */
+public class PyPreFormatProcessor implements PreFormatProcessor {
+ @NotNull
+ @Override
+ public TextRange process(@NotNull ASTNode element, @NotNull TextRange range) {
+ PsiElement psiElement = element.getPsi();
+ if (psiElement == null) return range;
+
+ if (!psiElement.getLanguage().is(PythonLanguage.getInstance())) return range;
+
+ PsiFile file = psiElement.isValid() ? psiElement.getContainingFile() : null;
+ if (file == null) return range;
+
+ Project project = psiElement.getProject();
+
+ return new PyCommentFormatter(project).process(psiElement, range);
+ }
+
+ /**
+ * @author traff
+ */
+ public static class PyCommentFormatter extends PyRecursiveElementVisitor {
+ private final Project myProject;
+ private final CodeStyleSettings mySettings;
+ private final PyCodeStyleSettings myPyCodeStyleSettings;
+ private TextRange myRange;
+ private int myDelta = 0;
+
+ public PyCommentFormatter(Project project) {
+ myProject = project;
+ mySettings = CodeStyleSettingsManager.getSettings(project);
+ myPyCodeStyleSettings = mySettings.getCustomSettings(PyCodeStyleSettings.class);
+ }
+
+ public TextRange process(PsiElement element, TextRange range) {
+ if (!myPyCodeStyleSettings.SPACE_AFTER_NUMBER_SIGN) {
+ return range;
+ }
+ myRange = range;
+ element.accept(this);
+ return TextRange.create(range.getStartOffset(), range.getEndOffset() + myDelta);
+ }
+
+ @Override
+ public void visitComment(PsiComment element) {
+ if (!myRange.contains(element.getTextRange())) {
+ return;
+ }
+ String text = element.getText();
+ int commentStart = text.indexOf('#');
+ if (commentStart != -1 && (commentStart + 1) < text.length()) {
+ String commentText = StringUtil.trimLeading(text.substring(commentStart + 1));
+
+ String newText = "# " + commentText;
+ if (!newText.equals(text)) {
+ myDelta += newText.length() - text.length();
+ element.replace(
+ PyElementGenerator.getInstance(myProject).createFromText(LanguageLevel.getDefault(), PsiComment.class, newText));
+ }
+ }
+ }
+ }
+}
diff --git a/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment.py b/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment.py
index a0f52904114e..c9082539ab46 100644
--- a/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment.py
+++ b/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment.py
@@ -1,7 +1,7 @@
class A(object):
pass
-#comment
+# comment
def one():
pass
@@ -9,10 +9,10 @@ def one():
def two():
pass
-#comment
+# comment
class B(object):
pass
-#comment
+# comment
class C(object):
pass
\ No newline at end of file
diff --git a/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment_after.py b/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment_after.py
index d582036d8654..ba0e69717738 100644
--- a/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment_after.py
+++ b/python/testData/formatter/twoLinesBetweenTopLevelDeclarationsWithComment_after.py
@@ -2,7 +2,7 @@ class A(object):
pass
-#comment
+# comment
def one():
pass
@@ -12,11 +12,11 @@ def two():
pass
-#comment
+# comment
class B(object):
pass
-#comment
+# comment
class C(object):
pass
\ No newline at end of file
diff --git a/python/testData/refactoring/extractmethod/Comment2.after.py b/python/testData/refactoring/extractmethod/Comment2.after.py
index 5e5fdc3c6a8c..c5cc3a7ff224 100644
--- a/python/testData/refactoring/extractmethod/Comment2.after.py
+++ b/python/testData/refactoring/extractmethod/Comment2.after.py
@@ -1,6 +1,6 @@
class Foo():
def baz():
- tmp = "!" #try to extract this assignmet, either with or without this comment
+ tmp = "!" # try to extract this assignment, either with or without this comment
baz()
diff --git a/python/testData/refactoring/extractmethod/Comment2.before.py b/python/testData/refactoring/extractmethod/Comment2.before.py
index d43386aff93c..23ba13de61f7 100644
--- a/python/testData/refactoring/extractmethod/Comment2.before.py
+++ b/python/testData/refactoring/extractmethod/Comment2.before.py
@@ -1,5 +1,5 @@
class Foo():
- tmp = "!" #try to extract this assignmet, either with or without this comment
+ tmp = "!" #try to extract this assignment, either with or without this comment
def bar(self):
pass
\ No newline at end of file
diff --git a/python/testData/refactoring/extractmethod/ElseBody.after.py b/python/testData/refactoring/extractmethod/ElseBody.after.py
index 57b5fa011715..24e387dd3633 100644
--- a/python/testData/refactoring/extractmethod/ElseBody.after.py
+++ b/python/testData/refactoring/extractmethod/ElseBody.after.py
@@ -1,5 +1,5 @@
def baz(f_new):
- length = len(f_new.readlines()) #<---extract something from here
+ length = len(f_new.readlines()) # <---extract something from here
print("hi from else")
diff --git a/python/testData/wrap/DontWrapStartOfString.after.py b/python/testData/wrap/DontWrapStartOfString.after.py
new file mode 100644
index 000000000000..5fb7919f6566
--- /dev/null
+++ b/python/testData/wrap/DontWrapStartOfString.after.py
@@ -0,0 +1,2 @@
+call_command('.'
+ 'regressiontests_some_long_text_here_to_test_right_margin_some_long_text_here_to_test_right_margin_some_long_text_here_to_test_right_margin')
\ No newline at end of file
diff --git a/python/testData/wrap/DontWrapStartOfString.py b/python/testData/wrap/DontWrapStartOfString.py
new file mode 100644
index 000000000000..06487362ca6a
--- /dev/null
+++ b/python/testData/wrap/DontWrapStartOfString.py
@@ -0,0 +1 @@
+call_command('.regressiontests')
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyFormatterTest.java b/python/testSrc/com/jetbrains/python/PyFormatterTest.java
index 815f61bd311f..f59fd49b2d03 100644
--- a/python/testSrc/com/jetbrains/python/PyFormatterTest.java
+++ b/python/testSrc/com/jetbrains/python/PyFormatterTest.java
@@ -251,7 +251,7 @@ public class PyFormatterTest extends PyTestCase {
" desired_response_parameters,\n" +
" inverse_filter_length, \n" +
" observed_impulse_response):\n" +
- " # Extract from here to ...\n" +
+ " # Extract from here to ...\n" +
" desired_impulse_response = {'dirac, 'gaussian', logistic_derivative'}\n" +
"return desired, o";
@@ -264,7 +264,7 @@ public class PyFormatterTest extends PyTestCase {
" desired_response_parameters,\n" +
" inverse_filter_length,\n" +
" observed_impulse_response):\n" +
- " # Extract from here to ...\n" +
+ " # Extract from here to ...\n" +
" desired_impulse_response = {'dirac, '\n" +
" gaussian\n" +
" ', logistic_derivative'}\n" +
diff --git a/python/testSrc/com/jetbrains/python/PyWrapTest.java b/python/testSrc/com/jetbrains/python/PyWrapTest.java
index 39606c3b5ca2..05c21b8af21a 100644
--- a/python/testSrc/com/jetbrains/python/PyWrapTest.java
+++ b/python/testSrc/com/jetbrains/python/PyWrapTest.java
@@ -64,6 +64,10 @@ public class PyWrapTest extends PyTestCase {
doTest(" AND field");
}
+ public void testDontWrapStartOfString() { // PY-9436
+ doTest("_some_long_text_here_to_test_right_margin_some_long_text_here_to_test_right_margin_some_long_text_here_to_test_right_margin");
+ }
+
public void testWrapRightMargin() {
final CodeStyleSettings settings = CodeStyleSettingsManager.getInstance(myFixture.getProject()).getCurrentSettings();
@@ -83,7 +87,6 @@ public class PyWrapTest extends PyTestCase {
settings.RIGHT_MARGIN = oldValue;
settings.WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN = oldMarginValue;
}
-
}
private void doTest(final String textToType) {