mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-14 18:05:27 +07:00
PY-15608 Handle converting quotes on "glued" string literal with varying quotes
Namely, if such composed literal has nodes with different quotes, convert only the one directly under the caret. This way we both preserve the old behavior, when quotes of all individual strings of the literal were converted, and allow to convert quotes of at least one element if that is not possible. I've also migrated the intention to newer PyStringElement API that appeared with the new f-string support. GitOrigin-RevId: a8592a46e2991b172a205f041cbc3b668e242e8a
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f001a2eb53
commit
7affe68244
@@ -3,18 +3,26 @@ package com.jetbrains.python.codeInsight.intentions;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* User: catherine
|
||||
* Intention to convert between single-quoted and double-quoted strings
|
||||
*/
|
||||
public class PyQuotedStringIntention extends PyBaseIntentionAction {
|
||||
private SmartPsiElementPointer<PsiElement> myConversionTarget;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@@ -28,54 +36,58 @@ public class PyQuotedStringIntention extends PyBaseIntentionAction {
|
||||
return false;
|
||||
}
|
||||
|
||||
PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class);
|
||||
if (string != null) {
|
||||
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(string, PyDocStringOwner.class);
|
||||
if (docStringOwner != null) {
|
||||
if (docStringOwner.getDocStringExpression() == string) return false;
|
||||
}
|
||||
String stringText = string.getText();
|
||||
int prefixLength = PyStringLiteralUtil.getPrefixLength(stringText);
|
||||
stringText = stringText.substring(prefixLength);
|
||||
PyStringElement stringElement = findConvertibleStringElementUnderCaret(editor, file);
|
||||
if (stringElement == null) return false;
|
||||
PyStringLiteralExpression stringLiteral = as(stringElement.getParent(), PyStringLiteralExpression.class);
|
||||
if (stringLiteral == null) return false;
|
||||
|
||||
if (stringText.length() >= 6) {
|
||||
if (stringText.startsWith("'''") && stringText.endsWith("'''") ||
|
||||
stringText.startsWith("\"\"\"") && stringText.endsWith("\"\"\"")) return false;
|
||||
}
|
||||
if (stringText.length() > 2) {
|
||||
if (stringText.startsWith("'") && stringText.endsWith("'")) {
|
||||
setText(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
|
||||
return true;
|
||||
}
|
||||
if (stringText.startsWith("\"") && stringText.endsWith("\"")) {
|
||||
setText(PyPsiBundle.message("INTN.quoted.string.double.to.single"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(stringLiteral, PyDocStringOwner.class);
|
||||
if (docStringOwner != null && docStringOwner.getDocStringExpression() == stringLiteral) return false;
|
||||
|
||||
String currentQuote = stringElement.getQuote();
|
||||
boolean allComponentsCanBeConverted = ContainerUtil.all(stringLiteral.getStringElements(),
|
||||
s -> s.getQuote().equals(currentQuote) && canBeConverted(s));
|
||||
|
||||
myConversionTarget = SmartPointerManager.createPointer(allComponentsCanBeConverted ? stringLiteral : stringElement);
|
||||
|
||||
if (currentQuote.equals("'")) {
|
||||
setText(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
|
||||
}
|
||||
return false;
|
||||
else {
|
||||
setText(PyPsiBundle.message("INTN.quoted.string.double.to.single"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public @Nullable PyStringElement findConvertibleStringElementUnderCaret(@NotNull Editor editor, @NotNull PsiFile file) {
|
||||
PsiElement elemUnderCaret = file.findElementAt(editor.getCaretModel().getOffset());
|
||||
PyStringElement stringElement = PsiTreeUtil.getParentOfType(elemUnderCaret, PyStringElement.class, false);
|
||||
return stringElement != null && canBeConverted(stringElement) ? stringElement : null;
|
||||
}
|
||||
|
||||
public boolean canBeConverted(@NotNull PyStringElement stringElement) {
|
||||
return !stringElement.isTripleQuoted() && stringElement.isTerminated() && !stringElement.getContentRange().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doInvoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
PyStringLiteralExpression string = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyStringLiteralExpression.class);
|
||||
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
|
||||
if (string != null) {
|
||||
final String stringText = string.getText();
|
||||
int prefixLength = PyStringLiteralUtil.getPrefixLength(stringText);
|
||||
final String text = stringText.substring(prefixLength);
|
||||
|
||||
if (text.startsWith("'") && text.endsWith("'")) {
|
||||
String result = convertSingleToDoubleQuoted(stringText);
|
||||
PyStringLiteralExpression st = elementGenerator.createStringLiteralAlreadyEscaped(result);
|
||||
string.replace(st);
|
||||
}
|
||||
if (text.startsWith("\"") && text.endsWith("\"")) {
|
||||
String result = convertDoubleToSingleQuoted(stringText);
|
||||
PyStringLiteralExpression st = elementGenerator.createStringLiteralAlreadyEscaped(result);
|
||||
string.replace(st);
|
||||
}
|
||||
@Nullable PsiElement target = myConversionTarget.getElement();
|
||||
if (target instanceof PyStringLiteralExpression) {
|
||||
((PyStringLiteralExpression)target).getStringElements().forEach(this::convertStringElement);
|
||||
}
|
||||
else if (target instanceof PyStringElement) {
|
||||
convertStringElement((PyStringElement)target);
|
||||
}
|
||||
}
|
||||
|
||||
public void convertStringElement(@NotNull PyStringElement stringElement) {
|
||||
Project project = stringElement.getProject();
|
||||
String result = stringElement.getQuote().equals("'")
|
||||
? convertSingleToDoubleQuoted(stringElement.getText())
|
||||
: convertDoubleToSingleQuoted(stringElement.getText());
|
||||
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
|
||||
PyStringLiteralExpression st = elementGenerator.createStringLiteralAlreadyEscaped(result);
|
||||
stringElement.replace(st.getFirstChild());
|
||||
}
|
||||
|
||||
private static String convertDoubleToSingleQuoted(String stringText) {
|
||||
@@ -95,9 +107,9 @@ public class PyQuotedStringIntention extends PyBaseIntentionAction {
|
||||
else if (ch == '\'') {
|
||||
stringBuilder.append("\\'");
|
||||
}
|
||||
else if (ch == '\\' && charArr[i+1] == '\"' && !(i+2 == charArr.length)) {
|
||||
else if (ch == '\\' && charArr[i + 1] == '\"' && !(i + 2 == charArr.length)) {
|
||||
skipNext = true;
|
||||
stringBuilder.append(charArr[i+1]);
|
||||
stringBuilder.append(charArr[i + 1]);
|
||||
}
|
||||
else {
|
||||
stringBuilder.append(ch);
|
||||
@@ -123,9 +135,9 @@ public class PyQuotedStringIntention extends PyBaseIntentionAction {
|
||||
else if (ch == '"') {
|
||||
stringBuilder.append("\\\"");
|
||||
}
|
||||
else if (ch == '\\' && charArr[i+1] == '\'' && !(i+2 == charArr.length)) {
|
||||
else if (ch == '\\' && charArr[i + 1] == '\'' && !(i + 2 == charArr.length)) {
|
||||
skipNext = true;
|
||||
stringBuilder.append(charArr[i+1]);
|
||||
stringBuilder.append(charArr[i + 1]);
|
||||
}
|
||||
else {
|
||||
stringBuilder.append(ch);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
s = ('f<caret>oo'
|
||||
"bar"
|
||||
'baz')
|
||||
@@ -0,0 +1,3 @@
|
||||
s = ("foo"
|
||||
"bar"
|
||||
'baz')
|
||||
@@ -206,6 +206,11 @@ public class PyIntentionTest extends PyTestCase {
|
||||
doTest(PyPsiBundle.message("INTN.quoted.string.double.to.single"));
|
||||
}
|
||||
|
||||
// PY-15608
|
||||
public void testConvertingQuotesOfGluedStringWithDifferentElementQuotes() {
|
||||
doTest(PyPsiBundle.message("INTN.quoted.string.single.to.double"));
|
||||
}
|
||||
|
||||
public void testConvertLambdaToFunction() {
|
||||
doTest(PyPsiBundle.message("INTN.convert.lambda.to.function"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user