From 3c4ae7d90ebe367aad7372e13d857456ed47db6c Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 10 Sep 2018 23:17:25 +0300 Subject: [PATCH] PY-31442 Support splitting of simple f-strings on enter --- .../python/editor/PythonEnterHandler.java | 86 +++++++++---------- .../com/jetbrains/python/PyEditingTest.java | 13 ++- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java index cbbcf448bceb..c6a9bccce41b 100644 --- a/python/src/com/jetbrains/python/editor/PythonEnterHandler.java +++ b/python/src/com/jetbrains/python/editor/PythonEnterHandler.java @@ -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; } diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java index 6c948ff79f81..afc77de91bcd 100644 --- a/python/testSrc/com/jetbrains/python/PyEditingTest.java +++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java @@ -103,12 +103,23 @@ public class PyEditingTest extends PyTestCase { assertEquals("f'{}'", doTestTyping("f''", 2, '{')); } - public void testEnterInFStringFragment() { + public void testEnterInMultilineFStringFragment() { doTestEnter("f'''{1 + 2}'''", "f'''{1 +\n" + " 2}'''"); } + public void testEnterInSingleLineFStringFragment() { + doTestEnter("f'foo{1 + 2}bar'", + "f'foo{1 +\n" + + "2}bar'"); + } + + public void testEnterInFStringTextPart() { + doTestEnter("f'foobar'", "f'foo' \\\n" + + " f'bar'"); + } + public void testOvertypeFromInside() { assertEquals("''", doTestTyping("''", 1, '\'')); }