Fix problem with adding completion from star argument, make element pattern more specific, merge completion provider classes

This commit is contained in:
Valentina Kiryushkina
2015-12-09 20:22:30 +03:00
parent 1b082c5584
commit 2dfb48d82a
4 changed files with 109 additions and 87 deletions
@@ -21,6 +21,7 @@ import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.inspections.PyStringFormatParser;
@@ -33,36 +34,121 @@ import java.util.List;
import java.util.Map;
import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.or;
public class PyStringFormatCompletionContributor extends CompletionContributor {
public PyStringFormatCompletionContributor() {
extend(
CompletionType.BASIC,
psiElement().inside(PyReferenceExpression.class),
new FormatArgumentsCompletionProvider()
);
extend(
CompletionType.BASIC,
psiElement().inside(PyStringLiteralExpression.class),
or(psiElement().inside(PyArgumentList
.class), psiElement().inside(PyStringLiteralExpression.class)),
new FormattedStringCompletionProvider()
);
}
private static class FormatArgumentsCompletionProvider extends CompletionProvider <CompletionParameters> {
private static class FormattedStringCompletionProvider extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
PsiElement original = parameters.getOriginalPosition();
PyReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(original, PyReferenceExpression.class);
tryToAddElementsFromFormattedString(result, referenceExpression);
if (original != null) {
PsiElement parent = original.getParent();
if (parent instanceof PyStringLiteralExpression) {
int stringOffset = parameters.getOffset() - parameters.getPosition().getTextRange().getStartOffset();
if (isInsideSubstitutionChunk((PyStringLiteralExpression)parent,
stringOffset)) {
PyExpression[] arguments = getFormatFunctionKeyWordArguments(original);
for (PyExpression argument : arguments) {
result = result.withPrefixMatcher(getPrefix(parameters.getOffset(), argument.getContainingFile()));
tryToAddKeysFromStarArgument(result, argument);
tryToAddKeyWordArgument(result, argument);
}
}
}
else if (PyUtil.instanceOf(parent, PyKeywordArgument.class, PyReferenceExpression.class)) {
PyArgumentList argumentList = PsiTreeUtil.getParentOfType(original, PyArgumentList.class);
result = result.withPrefixMatcher(getPrefix(parameters.getOffset(), parent.getContainingFile()));
tryToAddElementsFromFormattedString(result, argumentList);
}
}
}
private static boolean isInsideSubstitutionChunk(@NotNull final PyStringLiteralExpression expression, final int offset) {
List<PyStringFormatParser.SubstitutionChunk> substitutions = PyStringFormatParser.filterSubstitutions(
PyStringFormatParser.parseNewStyleFormat(expression.getStringValue()));
for (PyStringFormatParser.SubstitutionChunk substitution: substitutions) {
if (offset >= substitution.getStartIndex() && offset <= substitution.getEndIndex()) {
return true;
}
}
return false;
}
@NotNull
private static PyExpression[] getFormatFunctionKeyWordArguments(PsiElement original) {
PsiElement pyReferenceExpression = PsiTreeUtil.getParentOfType(original, PyReferenceExpression.class);
PyArgumentList argumentList = PsiTreeUtil.getNextSiblingOfType(pyReferenceExpression, PyArgumentList.class);
if (argumentList != null) {
return argumentList.getArguments();
}
return PyExpression.EMPTY_ARRAY;
}
private static void tryToAddKeysFromStarArgument(@NotNull CompletionResultSet result, @NotNull final PyExpression arg) {
if (arg instanceof PyStarArgument) {
PyDictLiteralExpression dict = ObjectUtils.chooseNotNull(PsiTreeUtil.getChildOfType(arg, PyDictLiteralExpression.class),
getDictFromReference(arg));
if (dict != null) {
for (PyKeyValueExpression keyValue: dict.getElements()) {
if (keyValue.getKey() instanceof PyStringLiteralExpression) {
String key = ((PyStringLiteralExpression) keyValue.getKey()).getStringValue();
addElementToResult(result, key);
}
}
}
else {
getDictFromReference(arg);
}
}
}
private static PyDictLiteralExpression getDictFromReference(@NotNull PyExpression arg) {
PyReferenceExpression referenceExpression = PsiTreeUtil.getChildOfType(arg, PyReferenceExpression.class);
if (referenceExpression != null) {
PsiElement resolveResult = referenceExpression.getReference().resolve();
if (resolveResult instanceof PyTargetExpression) {
PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(resolveResult, PyAssignmentStatement.class);
return PsiTreeUtil.getChildOfType(assignmentStatement, PyDictLiteralExpression.class);
}
}
return null;
}
private static void tryToAddKeyWordArgument(@NotNull final CompletionResultSet result, @NotNull final PyExpression arg) {
if (arg instanceof PyKeywordArgument) {
final String keyword = ((PyKeywordArgument)arg).getKeyword();
addElementToResult(result, keyword);
result.stopHere();
}
}
@NotNull
private static CompletionResultSet addElementToResult(@NotNull CompletionResultSet result,String element) {
result.addElement(LookupElementBuilder
.create(element)
.withTypeText("arg")
.withIcon(PlatformIcons.VARIABLE_ICON));
return result;
}
private static void tryToAddElementsFromFormattedString(@NotNull final CompletionResultSet result,
@Nullable final PyReferenceExpression referenceExpression) {
PyArgumentList argumentList = PsiTreeUtil.getParentOfType(referenceExpression, PyArgumentList.class);
@Nullable final PyArgumentList argumentList) {
if (argumentList != null) {
PyReferenceExpression pyReferenceExpression = PsiTreeUtil.getPrevSiblingOfType(argumentList, PyReferenceExpression.class);
PyStringLiteralExpression formattedString = PsiTreeUtil.getChildOfType(pyReferenceExpression, PyStringLiteralExpression.class);
@@ -79,84 +165,10 @@ public class PyStringFormatCompletionContributor extends CompletionContributor {
PyStringFormatParser.filterSubstitutions(PyStringFormatParser.parseNewStyleFormat(literalExpression.getStringValue())));
List<LookupElementBuilder> keys = new ArrayList<LookupElementBuilder>();
for (String chunk: chunks.keySet()) {
keys.add(LookupElementBuilder.create(chunk).withTypeText("field name").withIcon(PlatformIcons.VARIABLE_ICON));
keys.add(LookupElementBuilder.create(chunk).withTypeText("arg").withIcon(PlatformIcons.VARIABLE_ICON));
}
return keys;
}
}
private static class FormattedStringCompletionProvider extends CompletionProvider<CompletionParameters> {
@Override
protected void addCompletions(@NotNull CompletionParameters parameters,
ProcessingContext context,
@NotNull CompletionResultSet result) {
PsiElement original = parameters.getOriginalPosition();
if (isInsideSubstitutionChunk(original, parameters.getOffset())) {
PyExpression[] arguments = getFormatFunctionKeyWordArguments(original);
for (PyExpression argument : arguments) {
result = result.withPrefixMatcher(getPrefix(parameters.getOffset(), argument.getContainingFile()));
tryToAddKeysFromStarArgument(result, argument);
tryToAddKeyWordArgument(result, argument);
}
}
}
private static boolean isInsideSubstitutionChunk(final PsiElement original, final int offset) {
final PsiElement parent = original.getParent();
if (parent instanceof PyStringLiteralExpression) {
Map<String, PyStringFormatParser.SubstitutionChunk> substitutions =
PyStringFormatParser.getKeywordSubstitutions(PyStringFormatParser.filterSubstitutions(
PyStringFormatParser.parseNewStyleFormat(((PyStringLiteralExpression)
(parent)).getStringValue())));
for (PyStringFormatParser.SubstitutionChunk substitution: substitutions.values()) {
if (offset >= substitution.getStartIndex() && offset <= substitution.getEndIndex()) {
return true;
}
}
}
return false;
}
@NotNull
private static PyExpression[] getFormatFunctionKeyWordArguments(PsiElement original) {
PsiElement pyReferenceExpression = PsiTreeUtil.getParentOfType(original, PyReferenceExpression.class);
PyArgumentList argumentList = PsiTreeUtil.getNextSiblingOfType(pyReferenceExpression, PyArgumentList.class);
if (argumentList != null) {
return argumentList.getArguments();
}
return PyExpression.EMPTY_ARRAY;
}
private static void tryToAddKeysFromStarArgument(@NotNull CompletionResultSet result, @NotNull final PyExpression arg) {
if (arg instanceof PyStarArgument) {
PyDictLiteralExpression dict = PsiTreeUtil.getChildOfType(arg, PyDictLiteralExpression.class);
if (dict != null) {
for (PyKeyValueExpression keyValue: dict.getElements()) {
if (keyValue.getKey() instanceof PyStringLiteralExpression) {
String key = ((PyStringLiteralExpression) keyValue.getKey()).getStringValue();
addElementToResult(result, key);
}
}
}
}
}
private static void tryToAddKeyWordArgument(@NotNull final CompletionResultSet result, @NotNull final PyExpression arg) {
if (arg instanceof PyKeywordArgument) {
final String keyword = ((PyKeywordArgument)arg).getKeyword();
addElementToResult(result, keyword);
result.stopHere();
}
}
@NotNull
private static CompletionResultSet addElementToResult(@NotNull CompletionResultSet result,String element) {
result.addElement(LookupElementBuilder
.create(element)
.withTypeText("dict keys")
.withIcon(PlatformIcons.VARIABLE_ICON));
return result;
}
}
@@ -0,0 +1,3 @@
my_dict = {"fst": 1, "snd": 2}
"formatted string {fst}{snd}".format(**my_dict)
@@ -0,0 +1,3 @@
my_dict = {"fst": 1, "snd": 2}
"formatted string {f<caret>}{snd}".format(**my_dict)
@@ -918,6 +918,10 @@ public class PythonCompletionTest extends PyTestCase {
doTest();
}
public void testFormatStringFromRef() {
doTest();
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/completion";