mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
class name completion adds class name to existing import statement if possible (PY-1003)
This commit is contained in:
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user