PY-31442 Support splitting of simple f-strings on enter

This commit is contained in:
Mikhail Golubev
2018-10-01 12:46:48 +03:00
parent ed90be5647
commit 3c4ae7d90e
2 changed files with 53 additions and 46 deletions
@@ -99,11 +99,12 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
final Document doc = editor.getDocument();
PsiDocumentManager.getInstance(file.getProject()).commitDocument(doc);
final PsiElement element = file.findElementAt(offset);
final PsiElement prevElement = offset > 0 ? file.findElementAt(offset - 1) : null;
CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
if (codeInsightSettings.JAVADOC_STUB_ON_ENTER) {
PsiElement comment = element;
if (comment == null && offset != 0) {
comment = file.findElementAt(offset - 1);
comment = prevElement;
}
int expectedStringStart = editor.getCaretModel().getOffset() - 3; // """ or '''
if (comment != null) {
@@ -119,65 +120,60 @@ public class PythonEnterHandler extends EnterHandlerDelegateAdapter {
return Result.Continue;
}
final ASTNode node = element.getNode();
PsiElement elementParent = element.getParent();
if (element.getNode().getElementType() == PyTokenTypes.LPAR) elementParent = elementParent.getParent();
if (node.getElementType() == PyTokenTypes.LPAR) elementParent = elementParent.getParent();
if (elementParent instanceof PyParenthesizedExpression || elementParent instanceof PyGeneratorExpression) return Result.Continue;
if (offset > 0 && !(PyTokenTypes.STRING_NODES.contains(element.getNode().getElementType()))) {
final PsiElement prevElement = file.findElementAt(offset - 1);
if (prevElement == element) return Result.Continue;
}
if (PyTokenTypes.TRIPLE_NODES.contains(element.getNode().getElementType()) ||
element.getNode().getElementType() == PyTokenTypes.DOCSTRING) {
final PyStringElement stringElement = PsiTreeUtil.getParentOfType(element, PyStringElement.class, false);
if (stringElement == null && prevElement == element) {
return Result.Continue;
}
final PsiElement prevElement = file.findElementAt(offset - 1);
PyStringLiteralExpression string = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PyStringLiteralExpression.class, false);
if (string != null && prevElement != null && PyTokenTypes.STRING_NODES.contains(prevElement.getNode().getElementType())
&& string.getTextOffset() < offset && !(element.getNode() instanceof PsiWhiteSpace)) {
final String stringText = element.getText();
final int prefixLength = PyStringLiteralUtil.getPrefixLength(stringText);
if (string.getTextOffset() + prefixLength >= offset) {
if (stringElement != null && (!stringElement.isFormatted() || node.getElementType() == PyTokenTypes.FSTRING_TEXT)) {
if (stringElement.isTripleQuoted() || node.getElementType() == PyTokenTypes.DOCSTRING) {
return Result.Continue;
}
final String pref = element.getText().substring(0, prefixLength);
final String quote = element.getText().substring(prefixLength, prefixLength + 1);
// Don't split in the middle of an escape sequence
final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.from(offset - 1, 1)));
final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.from(offset, 1))) && nextIsBackslash;
final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.from(offset - 2, 1))) && nextIsBackslash;
if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) return Result.Continue;
if (prevElement != null && PsiTreeUtil.isAncestor(stringElement, prevElement, false)) {
if (stringElement.getTextOffset() + stringElement.getPrefixLength() >= offset) {
return Result.Continue;
}
final String pref = stringElement.getPrefix();
final String quote = stringElement.getQuote();
myPostprocessShift = prefixLength + quote.length();
// Don't split in the middle of an escape sequence
final boolean nextIsBackslash = "\\".equals(doc.getText(TextRange.from(offset - 1, 1)));
final boolean isEscapedQuote = quote.equals(doc.getText(TextRange.from(offset, 1))) && nextIsBackslash;
final boolean isEscapedBackslash = "\\".equals(doc.getText(TextRange.from(offset - 2, 1))) && nextIsBackslash;
if (nextIsBackslash && !isEscapedQuote && !isEscapedBackslash) return Result.Continue;
if (PsiTreeUtil.getParentOfType(string, IMPLICIT_WRAP_CLASSES) != null) {
final StringBuilder replacementString = new StringBuilder();
replacementString.append(quote).append(pref).append(quote);
doc.insertString(offset, replacementString);
caretOffset.set(caretOffset.get() + 1);
return Result.Continue;
}
else {
final StringBuilder replacementString = new StringBuilder();
if (isEscapedQuote) {
replacementString.append(quote);
myPostprocessShift = pref.length() + quote.length();
if (PsiTreeUtil.getParentOfType(stringElement, IMPLICIT_WRAP_CLASSES) != null) {
final StringBuilder replacementString = new StringBuilder();
replacementString.append(quote).append(pref).append(quote);
doc.insertString(offset, replacementString);
caretOffset.set(caretOffset.get() + 1);
return Result.Continue;
}
replacementString.append(quote).append(" \\").append(pref);
if (!isEscapedQuote) {
replacementString.append(quote);
else {
final StringBuilder replacementString = new StringBuilder();
if (isEscapedQuote) {
replacementString.append(quote);
caretOffset.set(caretOffset.get() + 1);
}
replacementString.append(quote).append(" \\").append(pref);
if (!isEscapedQuote) {
replacementString.append(quote);
}
doc.insertString(offset, replacementString);
caretOffset.set(caretOffset.get() + 3);
return Result.Continue;
}
doc.insertString(offset, replacementString);
caretOffset.set(caretOffset.get() + 3);
return Result.Continue;
}
}
if (!PyCodeInsightSettings.getInstance().INSERT_BACKSLASH_ON_WRAP) {
return Result.Continue;
}
@@ -103,12 +103,23 @@ public class PyEditingTest extends PyTestCase {
assertEquals("f'{}'", doTestTyping("f''", 2, '{'));
}
public void testEnterInFStringFragment() {
public void testEnterInMultilineFStringFragment() {
doTestEnter("f'''{1 +<caret> 2}'''",
"f'''{1 +\n" +
" 2}'''");
}
public void testEnterInSingleLineFStringFragment() {
doTestEnter("f'foo{1 +<caret> 2}bar'",
"f'foo{1 +\n" +
"2}bar'");
}
public void testEnterInFStringTextPart() {
doTestEnter("f'foo<caret>bar'", "f'foo' \\\n" +
" f'bar'");
}
public void testOvertypeFromInside() {
assertEquals("''", doTestTyping("''", 1, '\''));
}