PY-31442 Restore functioning of Join Lines for f-strings

It's not able to handle more complex cases yet, though. Say, if we want to join plain
and formatted strings literals.
This commit is contained in:
Mikhail Golubev
2018-10-01 12:46:48 +03:00
parent b2193bbcc7
commit 4100824a3e
6 changed files with 52 additions and 37 deletions
@@ -33,8 +33,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.psi.PyUtil.StringNodeInfo;
/**
* Joins lines sanely.
* <ul>
@@ -226,47 +224,49 @@ public class PyJoinLinesHandler implements JoinRawLinesHandlerDelegate {
private static class StringLiteralJoiner implements Joiner {
@Override
public Result join(@NotNull Request req) {
if (req.leftElem != req.rightElem) {
final PsiElement parent = req.rightElem.getParent();
if ((req.leftElem.getParent() == parent && parent instanceof PyStringLiteralExpression) ||
final PyStringElement leftStringElem = PsiTreeUtil.getParentOfType(req.leftElem, PyStringElement.class, false);
final PyStringElement rightStringElem = PsiTreeUtil.getParentOfType(req.rightElem, PyStringElement.class, false);
if (leftStringElem != null && rightStringElem != null && leftStringElem != rightStringElem) {
final PsiElement parent = rightStringElem.getParent();
if ((leftStringElem.getParent() == parent && parent instanceof PyStringLiteralExpression) ||
(req.leftExpr instanceof PyStringLiteralExpression && req.rightExpr instanceof PyStringLiteralExpression)) {
final StringNodeInfo leftNodeInfo = new StringNodeInfo(req.leftElem);
final StringNodeInfo rightNodeInfo = new StringNodeInfo(req.rightElem);
if (leftNodeInfo.isTerminated() && rightNodeInfo.isTerminated()) {
int quotesMaxLength = Math.max(leftNodeInfo.getQuote().length(), rightNodeInfo.getQuote().length());
if (leftStringElem.isTerminated() && rightStringElem.isTerminated() && haveSamePrefixes(leftStringElem, rightStringElem)) {
final String leftElemQuotes = leftStringElem.getQuote();
final String rightElemQuotes = rightStringElem.getQuote();
int quotesMaxLength = Math.max(leftElemQuotes.length(), rightElemQuotes.length());
int stringToJoinMaxLength = getStringToJoinMaxLength(req, quotesMaxLength);
final String replacement = findReplacement(rightNodeInfo.getContent(), stringToJoinMaxLength);
final String replacement = findReplacement(rightStringElem.getContent(), stringToJoinMaxLength);
if (leftNodeInfo.equals(rightNodeInfo)) {
return getResultAndSplitStringIfTooLong(req, leftNodeInfo, rightNodeInfo, replacement, leftNodeInfo.getQuote());
if (leftElemQuotes.equals(rightElemQuotes)) {
return getResultAndSplitStringIfTooLong(req, leftStringElem, rightStringElem, replacement, leftElemQuotes);
}
return processStringsWithDifferentQuotes(req, leftNodeInfo, rightNodeInfo, replacement);
return processStringsWithDifferentQuotes(req, leftStringElem, rightStringElem, replacement);
}
}
}
return null;
}
@Nullable
private static Result processStringsWithDifferentQuotes(@NotNull final Request req,
@NotNull final StringNodeInfo leftNodeInfo,
@NotNull final StringNodeInfo rightNodeInfo,
@NotNull final PyStringElement leftElem,
@NotNull final PyStringElement rightElem,
@NotNull final String replacement) {
if (haveSamePrefixes(leftNodeInfo, rightNodeInfo) && !leftNodeInfo.isTripleQuoted() && !rightNodeInfo.isTripleQuoted()) {
if (!rightNodeInfo.getContent().contains(leftNodeInfo.getQuote())) {
final int quotePos = rightNodeInfo.getAbsoluteContentRange().getEndOffset();
final String quote = leftNodeInfo.getQuote();
if (!leftElem.isTripleQuoted() && !rightElem.isTripleQuoted()) {
if (!rightElem.getContent().contains(leftElem.getQuote())) {
final int quotePos = rightElem.getTextOffset() + rightElem.getContentRange().getEndOffset();
final String quote = leftElem.getQuote();
req.document.replaceString(quotePos, quotePos + 1, quote);
return getResultAndSplitStringIfTooLong(req, leftNodeInfo, rightNodeInfo, replacement, quote);
return getResultAndSplitStringIfTooLong(req, leftElem, rightElem, replacement, quote);
}
else if (!leftNodeInfo.getContent().contains(rightNodeInfo.getQuote())) {
final int quotePos = leftNodeInfo.getAbsoluteContentRange().getStartOffset() - 1;
final String quote = rightNodeInfo.getQuote();
else if (!leftElem.getContent().contains(rightElem.getQuote())) {
final int quotePos = leftElem.getTextOffset() + leftElem.getContentRange().getStartOffset() - 1;
final String quote = rightElem.getQuote();
req.document.replaceString(quotePos, quotePos + 1, quote);
return getResultAndSplitStringIfTooLong(req, leftNodeInfo, rightNodeInfo, replacement, quote);
return getResultAndSplitStringIfTooLong(req, leftElem, rightElem, replacement, quote);
}
}
return null;
@@ -274,28 +274,29 @@ public class PyJoinLinesHandler implements JoinRawLinesHandlerDelegate {
@NotNull
private static Result getResultAndSplitStringIfTooLong(@NotNull final Request req,
@NotNull final StringNodeInfo leftNodeInfo,
@NotNull final StringNodeInfo rightNodeInfo,
@NotNull final PyStringElement leftElem,
@NotNull final PyStringElement rightElem,
@NotNull final String replacement,
@NotNull final String quote) {
int cutIntoRight = rightNodeInfo.getContentRange().getStartOffset();
int cutIntoRight = rightElem.getContentRange().getStartOffset();
String lineEnd = "";
if (!replacement.isEmpty()) {
cutIntoRight = replacement.length() + rightNodeInfo.getQuote().length();
int contentWithQuoteStartColumn = leftNodeInfo.getAbsoluteContentRange().getStartOffset() - getLeftLineStartOffset(req)
- leftNodeInfo.getQuote().length();
int quotePos = rightNodeInfo.getAbsoluteContentRange().getStartOffset() + replacement.length();
req.document.insertString(quotePos, rightNodeInfo.getQuote());
cutIntoRight = replacement.length() + rightElem.getQuote().length();
int contentWithQuoteStartColumn = leftElem.getTextOffset() + leftElem.getPrefixLength() - getLeftLineStartOffset(req);
int quotePos = rightElem.getTextOffset() + rightElem.getContentRange().getStartOffset() + replacement.length();
req.document.insertString(quotePos, rightElem.getQuote());
req.document.insertString(quotePos, StringUtil.repeat(" ", contentWithQuoteStartColumn));
lineEnd = quote + "\\\n";
}
return new Result(replacement + lineEnd, 0, leftNodeInfo.getQuote().length(), cutIntoRight);
return new Result(replacement + lineEnd, 0, leftElem.getQuote().length(), cutIntoRight);
}
private static boolean haveSamePrefixes(@NotNull StringNodeInfo leftNodeInfo, @NotNull StringNodeInfo rightNodeInfo) {
private static boolean haveSamePrefixes(@NotNull PyStringElement leftNodeInfo, @NotNull PyStringElement rightNodeInfo) {
return leftNodeInfo.isUnicode() == rightNodeInfo.isUnicode() &&
leftNodeInfo.isRaw() == rightNodeInfo.isRaw() &&
leftNodeInfo.isBytes() == rightNodeInfo.isBytes();
leftNodeInfo.isBytes() == rightNodeInfo.isBytes() &&
// TODO Merge formatted and plain strings elements (escape curly braces, etc.)
leftNodeInfo.isFormatted() == rightNodeInfo.isFormatted();
}
}
@@ -0,0 +1 @@
(f'{42}' '{}')
@@ -0,0 +1,2 @@
(f'{42}'
'{}')
@@ -0,0 +1 @@
f'{42}foo'
+2
View File
@@ -0,0 +1,2 @@
f'{42}'
f'foo'
@@ -178,4 +178,12 @@ public class PyJoinLinesTest extends PyTestCase {
public void testBackslashBetweenTargetsInFromImport() {
doTest();
}
public void testFStrings() {
doTest();
}
public void testFStringAndPlainLiteral() {
doTest();
}
}