class name completion adds class name to existing import statement if possible (PY-1003)

This commit is contained in:
Dmitry Jemerov
2010-06-24 13:30:41 +04:00
parent 6791a424cd
commit bf982a42e0
16 changed files with 150 additions and 21 deletions
@@ -10,17 +10,14 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Collections;
import java.util.List;
/**
@@ -51,12 +48,6 @@ public class ImportFromExistingAction implements QuestionAction {
myUseQualifiedImport = useQualified;
}
public ImportFromExistingAction(@NotNull PyElement target, PsiNamedElement source, Editor editor, boolean useQualified) {
this(target, Collections.singletonList(new ImportCandidateHolder(source, source.getContainingFile(), null,
ResolveImportUtil.findShortestImportableName(target, source.getContainingFile().getVirtualFile()), null)),
source.getName(), editor, useQualified);
}
public void onDone(Runnable callback) {
assert myOnDoneCallback == null;
myOnDoneCallback = callback;
@@ -120,7 +111,7 @@ public class ImportFromExistingAction implements QuestionAction {
PsiElement parent = src.getParent();
if (parent instanceof PyFromImportStatement) {
// add another import element right after the one we got
PsiElement new_elt = gen.createFromText(PyImportElement.class, "from foo import " + myName, new int[]{0, 6});
PsiElement new_elt = gen.createImportElement(myName);
PyUtil.addListNode(parent, new_elt, null, false, true);
}
else { // just 'import'
@@ -4,22 +4,27 @@ import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Iconable;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.ProjectScope;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.actions.ImportFromExistingAction;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.actions.AddImportHelper;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyQualifiedName;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
import static com.intellij.patterns.PlatformPatterns.psiElement;
@@ -60,15 +65,40 @@ public class PyClassNameCompletionContributor extends CompletionContributor {
}
private static class PyClassNameInsertHandler implements InsertHandler<LookupElement> {
public void handleInsert(InsertionContext context, LookupElement item) {
public void handleInsert(final InsertionContext context, final LookupElement item) {
final PsiReference ref = context.getFile().findReferenceAt(context.getTailOffset() - 1);
if (ref == null || ref.resolve() == item.getObject()) {
// no import statement needed
return;
}
PyElement element = (PyElement) ref.getElement();
boolean useQualified = !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT;
new ImportFromExistingAction(element, (PsiNamedElement) item.getObject(), context.getEditor(), useQualified).execute();
new WriteCommandAction(context.getProject(), context.getFile()) {
@Override
protected void run(Result result) throws Throwable {
addImport((PsiNamedElement) item.getObject(), context.getFile(), (PyElement) ref.getElement());
}
}.execute();
}
}
private static void addImport(final PsiNamedElement target, final PsiFile file, final PyElement element) {
final boolean useQualified = !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT;
final String path = ResolveImportUtil.findShortestImportableName(element, target.getContainingFile().getVirtualFile());
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(file.getProject());
if (useQualified) {
AddImportHelper.addImportStatement(file, path, null);
element.replace(elementGenerator.createExpressionFromText(path + "." + target.getName()));
}
else {
final List<PyFromImportStatement> existingImports = ((PyFile)file).getFromImports();
for (PyFromImportStatement existingImport : existingImports) {
final PyQualifiedName qName = existingImport.getImportSourceQName();
if (qName != null && qName.toString().equals(path)) {
PyImportElement importElement = elementGenerator.createImportElement(target.getName());
existingImport.add(importElement);
return;
}
}
AddImportHelper.addImportFromStatement(file, path, target.getName(), null);
}
}
@@ -5,7 +5,6 @@ import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -47,6 +46,8 @@ public abstract class PyElementGenerator {
public abstract PyImportStatement createImportStatementFromText(String text);
public abstract PyImportElement createImportElement(String name);
public abstract <T> T createFromText(Class<T> aClass, final String text);
/**
@@ -2,12 +2,10 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyTokenTypes;
@@ -194,6 +192,11 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
return (PyImportStatement)dummyFile.getFirstChild();
}
@Override
public PyImportElement createImportElement(String name) {
return createFromText(PyImportElement.class, "from foo import " + name, new int[]{0, 6});
}
static final int[] FROM_ROOT = new int[]{0};
public <T> T createFromText(Class<T> aClass, final String text) {
@@ -3,6 +3,7 @@ package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.TokenType;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.tree.TokenSet;
import com.intellij.util.ArrayFactory;
@@ -140,6 +141,23 @@ public class PyFromImportStatementImpl extends PyBaseElementImpl<PyFromImportSta
return true;
}
@Override
public ASTNode addInternal(ASTNode first, ASTNode last, ASTNode anchor, Boolean before) {
final ASTNode result = super.addInternal(first, last, anchor, before);
ASTNode prevNode = result;
do {
prevNode = prevNode.getTreePrev();
} while(prevNode != null && prevNode.getElementType() == TokenType.WHITE_SPACE);
if (prevNode != null && prevNode.getElementType() == PyElementTypes.IMPORT_ELEMENT &&
result.getElementType() == PyElementTypes.IMPORT_ELEMENT) {
ASTNode comma = PyElementGenerator.getInstance(getProject()).createComma();
super.addInternal(comma, comma, prevNode, false);
}
return result;
}
@Override
public void deleteChildInternal(@NotNull ASTNode child) {
PyPsiUtils.deleteAdjacentComma(this, child, getImportElements());
@@ -0,0 +1,3 @@
class Shazam:
pass
@@ -0,0 +1,3 @@
import mypackage
mypackage.Shazam
@@ -0,0 +1 @@
Shaz<caret>
@@ -0,0 +1,6 @@
class Xyzzy:
pass
class Shazam:
pass
@@ -0,0 +1,3 @@
from mypackage import Xyzzy, Shazam
Shazam
@@ -0,0 +1,3 @@
from mypackage import Xyzzy
Shaz<caret>
@@ -0,0 +1,3 @@
class Shazam:
pass
@@ -0,0 +1,3 @@
from mypackage import Shazam
Shazam
@@ -0,0 +1 @@
Shaz<caret>
@@ -0,0 +1,59 @@
package com.jetbrains.python;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.completion.CompletionType;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
/**
* @author yole
*/
public class PyClassNameCompletionTest extends PyLightFixtureTestCase {
private boolean myOldAutocompleteValue;
@Override
protected String getTestDataPath() {
return PythonTestUtil.getTestDataPath() + "/completion/className/";
}
@Override
protected void setUp() throws Exception {
super.setUp();
final CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
myOldAutocompleteValue = codeInsightSettings.AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION;
codeInsightSettings.AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION = true;
}
@Override
protected void tearDown() throws Exception {
CodeInsightSettings.getInstance().AUTOCOMPLETE_ON_CLASS_NAME_COMPLETION = myOldAutocompleteValue;
super.tearDown();
}
public void testSimple() throws Exception {
doTest();
}
public void testReuseExisting() throws Exception {
doTest();
}
public void testQualified() throws Exception {
final PyCodeInsightSettings settings = PyCodeInsightSettings.getInstance();
boolean oldValue = settings.PREFER_FROM_IMPORT;
settings.PREFER_FROM_IMPORT = false;
try {
doTest();
}
finally {
settings.PREFER_FROM_IMPORT = oldValue;
}
}
private void doTest() throws Exception {
myFixture.copyDirectoryToProject(getTestName(true), "");
myFixture.configureFromTempProjectFile(getTestName(true) + ".py");
myFixture.complete(CompletionType.CLASS_NAME);
myFixture.checkResultByFile(getTestName(true) + "/" + getTestName(true) + ".after.py");
}
}
@@ -54,7 +54,8 @@ public class PythonAllTestsSuite {
PyOptimizeImportsTest.class,
PySmartEnterTest.class,
PyStatementMoverTest.class,
PyIntroduceVariableTest.class
PyIntroduceVariableTest.class,
PyClassNameCompletionTest.class
};
public static TestSuite suite() {