KT-805 Auto import on code completion insert (Initial version)

This commit is contained in:
Nikolay Krasko
2011-12-15 17:19:00 +04:00
parent d604591c5c
commit 26ec797444
15 changed files with 565 additions and 90 deletions
@@ -7,8 +7,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.resolve.calls.inference.SolutionStatus;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lexer.JetKeywordToken;
import org.jetbrains.jet.resolve.DescriptorRenderer;
@@ -43,6 +43,7 @@ public interface Errors {
return e.getClass().getSimpleName() + ": " + e.getMessage();
}
};
UnresolvedReferenceDiagnosticFactory UNRESOLVED_REFERENCE = new UnresolvedReferenceDiagnosticFactory("Unresolved reference");
RedeclarationDiagnosticFactory REDECLARATION = RedeclarationDiagnosticFactory.REDECLARATION;
@@ -2,15 +2,16 @@ package org.jetbrains.jet.lang.diagnostics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.JetReferenceExpression;
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
/**
* @author abreslav
*/
public class UnresolvedReferenceDiagnosticFactory extends AbstractDiagnosticFactory {
// public static final UnresolvedReferenceDiagnosticFactory INSTANCE = new UnresolvedReferenceDiagnosticFactory();
public class UnresolvedReferenceDiagnosticFactory extends AbstractDiagnosticFactory
implements PsiElementOnlyDiagnosticFactory<JetSimpleNameExpression> {
private final String message;
public UnresolvedReferenceDiagnosticFactory(String message) {
this.message = message;
}
@@ -138,7 +138,7 @@ public interface BindingContext {
WritableSlice<JetReferenceExpression, PsiElement> LABEL_TARGET = Slices.<JetReferenceExpression, PsiElement>sliceBuilder().build();
WritableSlice<JetParameter, PropertyDescriptor> VALUE_PARAMETER_AS_PROPERTY = Slices.<JetParameter, PropertyDescriptor>sliceBuilder().build();
WritableSlice<String, ClassDescriptor> FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice<String, ClassDescriptor>(DO_NOTHING);
WritableSlice<String, ClassDescriptor> FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice<String, ClassDescriptor>(DO_NOTHING, true);
WritableSlice<String, NamespaceDescriptor> FQNAME_TO_NAMESPACE_DESCRIPTOR = new BasicWritableSlice<String, NamespaceDescriptor>(DO_NOTHING);
@SuppressWarnings("UnusedDeclaration")
@@ -6,7 +6,7 @@ import java.lang.reflect.Modifier;
/**
* @author abreslav
*/
public class BasicWritableSlice<K, V> implements WritableSlice<K,V> {
public class BasicWritableSlice<K, V> implements WritableSlice<K, V> {
public static Void initSliceDebugNames(Class<?> declarationOwner) {
for (Field field : declarationOwner.getFields()) {
@@ -26,9 +26,15 @@ public class BasicWritableSlice<K, V> implements WritableSlice<K,V> {
private String debugName;
private final RewritePolicy rewritePolicy;
private final boolean isCollective;
public BasicWritableSlice(RewritePolicy rewritePolicy) {
this(rewritePolicy, false);
}
public BasicWritableSlice(RewritePolicy rewritePolicy, boolean isCollective) {
this.rewritePolicy = rewritePolicy;
this.isCollective = isCollective;
}
@Override
@@ -62,7 +68,7 @@ public class BasicWritableSlice<K, V> implements WritableSlice<K,V> {
@Override
public boolean isCollective() {
return false;
return isCollective;
}
@Override
@@ -3,6 +3,8 @@ package org.jetbrains.jet.util.slicedmap;
import org.jetbrains.annotations.NotNull;
/**
* Do nothing but dispatching all invokes to internal writable slice.
*
* @author abreslav
*/
public class DelegatingSlice<K, V> implements WritableSlice<K, V> {
@@ -17,10 +19,12 @@ public class DelegatingSlice<K, V> implements WritableSlice<K, V> {
return delegate.isCollective();
}
@Override
public boolean check(K key, V value) {
return delegate.check(key, value);
}
@Override
public void afterPut(MutableSlicedMap map, K key, V value) {
delegate.afterPut(map, key, value);
}
@@ -30,10 +34,12 @@ public class DelegatingSlice<K, V> implements WritableSlice<K, V> {
return delegate.getRewritePolicy();
}
@Override
public SlicedMapKey<K, V> makeKey(K key) {
return delegate.makeKey(key);
}
@Override
public V computeValue(SlicedMap map, K key, V value, boolean valueNotFound) {
return delegate.computeValue(map, key, value, valueNotFound);
}
@@ -0,0 +1,154 @@
package org.jetbrains.jet.plugin.actions;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.actions.AddImportAction;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.plugin.quickfix.ImportClassHelper;
import javax.swing.*;
import java.util.List;
/**
* Automatically adds import directive to the file for resolving reference.
* Based on {@link AddImportAction}
*
* @author Nikolay Krasko
*/
public class JetAddImportAction implements QuestionAction {
private final Project myProject;
private final Editor myEditor;
private final PsiElement myElement;
private final List<String> possibleImports;
/**
* @param project Project where action takes place.
* @param editor Editor where modification should be done.
* @param element Element with unresolved reference.
* @param imports Variants for resolution.
*/
public JetAddImportAction(
@NotNull Project project,
@NotNull Editor editor,
@NotNull PsiElement element,
@NotNull Iterable<String> imports
) {
myProject = project;
myEditor = editor;
myElement = element;
possibleImports = Lists.newArrayList(imports);
}
@Override
public boolean execute() {
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
if (!myElement.isValid()){
return false;
}
// TODO: Validate resolution variants. See AddImportAction.execute()
if (possibleImports.size() == 1){
addImport(myElement, myProject, possibleImports.get(0));
}
else{
chooseClassAndImport();
}
return true;
}
protected BaseListPopupStep getImportSelectionPopup() {
return new BaseListPopupStep<String>(QuickFixBundle.message("class.to.import.chooser.title"), possibleImports) {
@Override
public boolean isAutoSelectionEnabled() {
return false;
}
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
if (selectedValue == null) {
return FINAL_CHOICE;
}
if (finalChoice) {
addImport(myElement, myProject, selectedValue);
return FINAL_CHOICE;
}
List<String> toExclude = AddImportAction.getAllExcludableStrings(selectedValue);
return new BaseListPopupStep<String>(null, toExclude) {
@NotNull
@Override
public String getTextFor(String value) {
return "Exclude '" + value + "' from auto-import";
}
@Override
public PopupStep onChosen(String selectedValue, boolean finalChoice) {
if (finalChoice) {
AddImportAction.excludeFromImport(myProject, selectedValue);
}
return super.onChosen(selectedValue, finalChoice);
}
};
}
@Override
public boolean hasSubstep(String selectedValue) {
return true;
}
@NotNull
@Override
public String getTextFor(String value) {
return value;
}
@Override
public Icon getIconFor(String aValue) {
return PlatformIcons.CLASS_ICON;
}
};
}
protected void addImport(final PsiElement element, final Project project, final String selectedImport) {
PsiDocumentManager.getInstance(project).commitAllDocuments();
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
// TODO: See {@link com.intellij.codeInsight.daemon.impl.actions.AddImportAction#_addImport} for more ideas.
// TODO: Optimize imports
ImportClassHelper.addImportDirective(
selectedImport,
ImportClassHelper.findOuterNamespace(element)
);
}
});
}
}, QuickFixBundle.message("add.import"), null);
}
private void chooseClassAndImport() {
JBPopupFactory.getInstance().createListPopup(getImportSelectionPopup()).showInBestPositionFor(myEditor);
}
}
@@ -9,12 +9,12 @@ import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.Annotator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.MultiRangeReference;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
@@ -22,7 +22,6 @@ import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
import org.jetbrains.jet.lang.diagnostics.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.plugin.JetHighlighter;
import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade;
@@ -53,7 +52,7 @@ public class JetPsiChecker implements Annotator {
public void annotate(@NotNull PsiElement element, @NotNull final AnnotationHolder holder) {
if (element instanceof JetFile) {
JetFile file = (JetFile) element;
Project project = element.getProject();
try {
final BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file);
@@ -61,56 +60,11 @@ public class JetPsiChecker implements Annotator {
Collection<Diagnostic> diagnostics = Sets.newLinkedHashSet(bindingContext.getDiagnostics());
Set<PsiElement> redeclarations = Sets.newHashSet();
for (Diagnostic diagnostic : diagnostics) {
if (diagnostic.getFactory().getPsiFile(diagnostic) != file) continue; // This is needed because we have the same context for all files
Annotation annotation = null;
if (diagnostic.getSeverity() == Severity.ERROR) {
if (diagnostic instanceof UnresolvedReferenceDiagnostic) {
UnresolvedReferenceDiagnostic unresolvedReferenceDiagnostic = (UnresolvedReferenceDiagnostic) diagnostic;
JetReferenceExpression referenceExpression = unresolvedReferenceDiagnostic.getPsiElement();
PsiReference reference = referenceExpression.getReference();
if (reference instanceof MultiRangeReference) {
MultiRangeReference mrr = (MultiRangeReference) reference;
for (TextRange range : mrr.getRanges()) {
holder.createErrorAnnotation(range.shiftRight(referenceExpression.getTextOffset()), diagnostic.getMessage()).setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
else {
holder.createErrorAnnotation(referenceExpression, diagnostic.getMessage()).setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
else if (diagnostic instanceof RedeclarationDiagnostic) {
RedeclarationDiagnostic redeclarationDiagnostic = (RedeclarationDiagnostic) diagnostic;
annotation = markRedeclaration(redeclarations, redeclarationDiagnostic, holder);
}
else {
annotation = holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic));
}
}
else if (diagnostic.getSeverity() == Severity.WARNING) {
annotation = holder.createWarningAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic));
if (diagnostic.getFactory() instanceof UnusedElementDiagnosticFactory) {
annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
if (annotation != null) {
if (diagnostic instanceof DiagnosticWithPsiElementImpl && diagnostic.getFactory() instanceof PsiElementOnlyDiagnosticFactory) {
PsiElementOnlyDiagnosticFactory factory = (PsiElementOnlyDiagnosticFactory) diagnostic.getFactory();
Collection<JetIntentionActionFactory> intentionActionFactories = QuickFixes.get(factory);
for (JetIntentionActionFactory intentionActionFactory : intentionActionFactories) {
IntentionAction action = null;
if (intentionActionFactory != null) {
action = intentionActionFactory.createAction((DiagnosticWithPsiElement) diagnostic);
}
if (action != null) {
annotation.registerFix(action);
}
}
}
Collection<IntentionAction> actions = QuickFixes.get(diagnostic.getFactory());
for (IntentionAction action : actions) {
annotation.registerFix(action);
}
}
// This is needed because we have the same context for all files
if (diagnostic.getFactory().getPsiFile(diagnostic) != file) continue;
registerDiagnosticAnnotations(diagnostic, redeclarations, holder);
}
}
@@ -118,7 +72,7 @@ public class JetPsiChecker implements Annotator {
file.acceptChildren(new JetVisitorVoid() {
@Override
public void visitSimpleNameExpression(JetSimpleNameExpression expression) {
public void visitSimpleNameExpression(@NotNull JetSimpleNameExpression expression) {
DeclarationDescriptor target = bindingContext.get(REFERENCE_TARGET, expression);
if (target instanceof ValueParameterDescriptor) {
ValueParameterDescriptor parameterDescriptor = (ValueParameterDescriptor) target;
@@ -131,7 +85,7 @@ public class JetPsiChecker implements Annotator {
super.visitSimpleNameExpression(expression);
}
private void markVariableAsWrappedIfNeeded(ASTNode node, DeclarationDescriptor target) {
private void markVariableAsWrappedIfNeeded(@NotNull ASTNode node, DeclarationDescriptor target) {
if (target instanceof VariableDescriptor) {
VariableDescriptor variableDescriptor = (VariableDescriptor) target;
if (bindingContext.get(MUST_BE_WRAPPED_IN_A_REF, variableDescriptor)) {
@@ -142,7 +96,7 @@ public class JetPsiChecker implements Annotator {
}
@Override
public void visitProperty(JetProperty property) {
public void visitProperty(@NotNull JetProperty property) {
DeclarationDescriptor declarationDescriptor = bindingContext.get(DECLARATION_TO_DESCRIPTOR, property);
PsiElement nameIdentifier = property.getNameIdentifier();
if (nameIdentifier != null) {
@@ -152,7 +106,7 @@ public class JetPsiChecker implements Annotator {
}
@Override
public void visitExpression(JetExpression expression) {
public void visitExpression(@NotNull JetExpression expression) {
JetType autoCast = bindingContext.get(AUTOCAST, expression);
if (autoCast != null) {
holder.createInfoAnnotation(expression, "Automatically cast to " + autoCast).setTextAttributes(JetHighlighter.JET_AUTO_CAST_EXPRESSION);
@@ -161,7 +115,7 @@ public class JetPsiChecker implements Annotator {
}
@Override
public void visitJetElement(JetElement element) {
public void visitJetElement(@NotNull JetElement element) {
element.acceptChildren(this);
}
});
@@ -177,23 +131,111 @@ public class JetPsiChecker implements Annotator {
}
}
private String getMessage(Diagnostic diagnostic) {
private void registerDiagnosticAnnotations(
@NotNull Diagnostic diagnostic,
@NotNull Set<PsiElement> redeclarations,
@NotNull final AnnotationHolder holder
) {
if (diagnostic.getSeverity() == Severity.ERROR) {
if (diagnostic instanceof UnresolvedReferenceDiagnostic) {
UnresolvedReferenceDiagnostic unresolvedReferenceDiagnostic = (UnresolvedReferenceDiagnostic) diagnostic;
JetReferenceExpression referenceExpression = unresolvedReferenceDiagnostic.getPsiElement();
PsiReference reference = referenceExpression.getReference();
if (reference instanceof MultiRangeReference) {
MultiRangeReference mrr = (MultiRangeReference) reference;
for (TextRange range : mrr.getRanges()) {
Annotation annotation = holder.createErrorAnnotation(
range.shiftRight(referenceExpression.getTextOffset()),
diagnostic.getMessage());
registerQuickFix(annotation, diagnostic);
annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
}
else {
Annotation annotation = holder.createErrorAnnotation(referenceExpression, diagnostic.getMessage());
registerQuickFix(annotation, diagnostic);
annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL);
}
return;
}
if (diagnostic instanceof RedeclarationDiagnostic) {
RedeclarationDiagnostic redeclarationDiagnostic = (RedeclarationDiagnostic) diagnostic;
registerQuickFix(markRedeclaration(redeclarations, redeclarationDiagnostic, holder), diagnostic);
return;
}
// General annotation
registerQuickFix(
holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic)),
diagnostic);
}
else if (diagnostic.getSeverity() == Severity.WARNING) {
Annotation annotation = holder.createWarningAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic));
registerQuickFix(annotation, diagnostic);
if (diagnostic.getFactory() instanceof UnusedElementDiagnosticFactory) {
annotation.setHighlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
}
/*
* Add a quick fix if and return modified annotation.
*/
@Nullable
private Annotation registerQuickFix(
@Nullable Annotation annotation,
@NotNull Diagnostic diagnostic) {
if (annotation == null) {
return null;
}
if (diagnostic instanceof DiagnosticWithPsiElementImpl && diagnostic.getFactory() instanceof PsiElementOnlyDiagnosticFactory) {
PsiElementOnlyDiagnosticFactory factory = (PsiElementOnlyDiagnosticFactory) diagnostic.getFactory();
Collection<JetIntentionActionFactory> intentionActionFactories = QuickFixes.get(factory);
for (JetIntentionActionFactory intentionActionFactory : intentionActionFactories) {
IntentionAction action = null;
if (intentionActionFactory != null) {
action = intentionActionFactory.createAction((DiagnosticWithPsiElement) diagnostic);
}
if (action != null) {
annotation.registerFix(action);
}
}
}
Collection<IntentionAction> actions = QuickFixes.get(diagnostic.getFactory());
for (IntentionAction action : actions) {
annotation.registerFix(action);
}
return annotation;
}
@NotNull
private String getMessage(@NotNull Diagnostic diagnostic) {
if (ApplicationManager.getApplication().isInternal() || ApplicationManager.getApplication().isUnitTestMode()) {
return "[" + diagnostic.getFactory().getName() + "] " + diagnostic.getMessage();
}
return diagnostic.getMessage();
}
private Annotation markRedeclaration(Set<PsiElement> redeclarations, RedeclarationDiagnostic diagnostic, AnnotationHolder holder) {
@Nullable
private Annotation markRedeclaration(@NotNull Set<PsiElement> redeclarations, @NotNull RedeclarationDiagnostic diagnostic, @NotNull AnnotationHolder holder) {
if (!redeclarations.add(diagnostic.getPsiElement())) return null;
return holder.createErrorAnnotation(diagnostic.getFactory().getTextRange(diagnostic), getMessage(diagnostic));
}
private void highlightBackingFields(final AnnotationHolder holder, JetFile file, final BindingContext bindingContext) {
private void highlightBackingFields(@NotNull final AnnotationHolder holder, @NotNull JetFile file, @NotNull final BindingContext bindingContext) {
file.acceptChildren(new JetVisitorVoid() {
@Override
public void visitProperty(JetProperty property) {
public void visitProperty(@NotNull JetProperty property) {
VariableDescriptor propertyDescriptor = bindingContext.get(BindingContext.VARIABLE, property);
if (propertyDescriptor instanceof PropertyDescriptor) {
if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) propertyDescriptor)) {
@@ -203,7 +245,7 @@ public class JetPsiChecker implements Annotator {
}
@Override
public void visitParameter(JetParameter parameter) {
public void visitParameter(@NotNull JetParameter parameter) {
PropertyDescriptor propertyDescriptor = bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter);
if (propertyDescriptor != null && bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, propertyDescriptor)) {
putBackingfieldAnnotation(holder, parameter);
@@ -211,13 +253,13 @@ public class JetPsiChecker implements Annotator {
}
@Override
public void visitJetElement(JetElement element) {
public void visitJetElement(@NotNull JetElement element) {
element.acceptChildren(this);
}
});
}
private void putBackingfieldAnnotation(AnnotationHolder holder, JetNamedDeclaration element) {
private void putBackingfieldAnnotation(@NotNull AnnotationHolder holder, @NotNull JetNamedDeclaration element) {
PsiElement nameIdentifier = element.getNameIdentifier();
if (nameIdentifier != null) {
holder.createInfoAnnotation(
@@ -0,0 +1,190 @@
package org.jetbrains.jet.plugin.quickfix;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.DelegatingGlobalSearchScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.diagnostics.DiagnosticWithPsiElement;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.plugin.JetFileType;
import org.jetbrains.jet.plugin.actions.JetAddImportAction;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Check possibility and perform fix for unresolved references.
*
* @author Nikolay Krasko
*/
public class ImportClassFix extends JetHintAction<JetSimpleNameExpression> implements HighPriorityAction {
@NotNull
private final List<String> suggestions;
public ImportClassFix(@NotNull JetSimpleNameExpression element) {
super(element);
suggestions = computeSuggestions(element);
}
private static List<String> computeSuggestions(@NotNull JetSimpleNameExpression element) {
final PsiFile file = element.getContainingFile();
if (!(file instanceof JetFile)) {
return Collections.emptyList();
}
final BindingContext context = AnalyzerFacade.analyzeFileWithCache(
(JetFile) file, AnalyzerFacade.SINGLE_DECLARATION_PROVIDER);
return getClassNames(element, context, file.getProject());
}
/*
* Searches for possible class names in kotlin context and java facade.
*/
public static List<String> getClassNames(@NotNull JetSimpleNameExpression expression, @NotNull BindingContext context, @NotNull Project project) {
final String referenceName = expression.getReferencedName();
if (referenceName == null) {
return Collections.emptyList();
}
Set<String> possibleResolveNames = Sets.newHashSet();
possibleResolveNames.addAll(filterPossibleTypes(referenceName, context.getKeys(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR)));
possibleResolveNames.addAll(getJavaClasses(referenceName, project));
// TODO: Do appropriate sorting
return Lists.newArrayList(possibleResolveNames);
}
private static Collection<String> getJavaClasses(@NotNull final String typeName, @NotNull Project project) {
PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project);
PsiClass[] classes = cache.getClassesByName(typeName, new DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) {
@Override
public boolean contains(@NotNull VirtualFile file) {
return myBaseScope.contains(file) && file.getFileType() != JetFileType.INSTANCE;
}
});
return Collections2.transform(Lists.newArrayList(classes), new Function<PsiClass, String>() {
@Nullable
@Override
public String apply(@Nullable PsiClass javaClass) {
assert javaClass != null;
return javaClass.getQualifiedName();
}
});
}
@NotNull
private static Collection<String> filterPossibleTypes(@NotNull final String typeName, @NotNull Collection<String> variants) {
return Collections2.filter(variants, new Predicate<String>() {
@Override
public boolean apply(@Nullable String fqName) {
return fqName != null && (fqName.endsWith('.' + typeName) || fqName.equals(typeName));
}
});
}
@Override
public boolean showHint(@NotNull Editor editor) {
if (suggestions.isEmpty()) {
return false;
}
final Project project = editor.getProject();
if (project == null) {
return false;
}
String hintText = ShowAutoImportPass.getMessage(suggestions.size() > 1, suggestions.get(0));
if (!ApplicationManager.getApplication().isUnitTestMode() &&
!HintManager.getInstance().hasShownHintsThatWillHideByOtherHint()) {
HintManager.getInstance().showQuestionHint(
editor, hintText,
element.getTextOffset(), element.getTextRange().getEndOffset(),
createAction(project, editor));
}
return true;
}
@Override
@NotNull
public String getText() {
return QuickFixBundle.message("import.class.fix");
}
@Override
@NotNull
public String getFamilyName() {
return QuickFixBundle.message("import.class.fix");
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return super.isAvailable(project, editor, file) && !suggestions.isEmpty();
}
@Override
public void invoke(@NotNull final Project project, @NotNull final Editor editor, final PsiFile file) throws IncorrectOperationException {
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
@Override
public void run() {
createAction(project, editor).execute();
}
});
}
@Override
public boolean startInWriteAction() {
return true;
}
@NotNull
private JetAddImportAction createAction(@NotNull Project project, @NotNull Editor editor) {
return new JetAddImportAction(project, editor, element, suggestions);
}
@Nullable
public static JetIntentionActionFactory<JetSimpleNameExpression> createFactory() {
return new JetIntentionActionFactory<JetSimpleNameExpression>() {
@Nullable
@Override
public JetIntentionAction<JetSimpleNameExpression> createAction(@NotNull DiagnosticWithPsiElement diagnostic) {
assert diagnostic.getPsiElement() instanceof JetSimpleNameExpression;
if (diagnostic.getPsiElement() instanceof JetSimpleNameExpression) {
JetSimpleNameExpression psiElement = (JetSimpleNameExpression) diagnostic.getPsiElement();
return new ImportClassFix(psiElement);
}
return null;
}
};
}}
@@ -3,7 +3,10 @@ package org.jetbrains.jet.plugin.quickfix;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetImportDirective;
import org.jetbrains.jet.lang.psi.JetNamespace;
import org.jetbrains.jet.lang.psi.JetPsiFactory;
import org.jetbrains.jet.lang.types.ErrorUtils;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.plugin.JetPluginUtil;
@@ -26,34 +29,50 @@ public class ImportClassHelper {
perform(typeFullName, namespace, null, null);
}
public static void perform(@NotNull String typeFullName, @NotNull PsiElement elementToReplace, @NotNull PsiElement newElement) {
PsiElement parent = elementToReplace;
/**
* Get the outer namespace PSI element for given element in the tree.
*
* @param element Some element in the tree.
* @return A namespace element in the tree.
*/
public static JetNamespace findOuterNamespace(@NotNull PsiElement element) {
PsiElement parent = element;
while (!(parent instanceof JetNamespace)) {
parent = parent.getParent();
assert parent != null;
}
perform(typeFullName, (JetNamespace) parent, elementToReplace, newElement);
return (JetNamespace) parent;
}
public static void perform(@NotNull String typeFullName, @NotNull JetNamespace namespace, @Nullable PsiElement elementToReplace, @Nullable PsiElement newElement) {
public static void perform(@NotNull String typeFullName, @NotNull PsiElement elementToReplace, @NotNull PsiElement newElement) {
perform(typeFullName, findOuterNamespace(elementToReplace), elementToReplace, newElement);
}
/**
* Add import directive into the PSI tree for the given namespace.
*
* @param importString full name of the import. Can contain .* if necessary.
* @param namespace Namespace where directive should be added.
*/
public static void addImportDirective(@NotNull String importString, @NotNull JetNamespace namespace) {
List<JetImportDirective> importDirectives = namespace.getImportDirectives();
JetImportDirective newDirective = JetPsiFactory.createImportDirective(namespace.getProject(), typeFullName);
JetImportDirective newDirective = JetPsiFactory.createImportDirective(namespace.getProject(), importString);
String lineSeparator = System.getProperty("line.separator");
if (!importDirectives.isEmpty()) {
boolean isPresent = false;
// Check if import is already present
for (JetImportDirective directive : importDirectives) {
if (directive.getText().endsWith(typeFullName) ||
directive.getText().endsWith(typeFullName + ";")) {
isPresent = true;
if (directive.getText().endsWith(importString) || directive.getText().endsWith(importString + ";")) {
return;
}
}
if (!isPresent) {
JetImportDirective lastDirective = importDirectives.get(importDirectives.size() - 1);
lastDirective.getParent().addAfter(newDirective, lastDirective);
lastDirective.getParent().addAfter(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator), lastDirective);
}
JetImportDirective lastDirective = importDirectives.get(importDirectives.size() - 1);
lastDirective.getParent().addAfter(newDirective, lastDirective);
lastDirective.getParent().addAfter(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator), lastDirective);
}
else {
List<JetDeclaration> declarations = namespace.getDeclarations();
@@ -62,6 +81,11 @@ public class ImportClassHelper {
firstDeclaration.getParent().addBefore(newDirective, firstDeclaration);
firstDeclaration.getParent().addBefore(JetPsiFactory.createWhiteSpace(namespace.getProject(), lineSeparator + lineSeparator), firstDeclaration);
}
}
public static void perform(@NotNull String typeFullName, @NotNull JetNamespace namespace, @Nullable PsiElement elementToReplace, @Nullable PsiElement newElement) {
addImportDirective(typeFullName, namespace);
if (elementToReplace != null && newElement != null && elementToReplace != newElement) {
elementToReplace.replace(newElement);
}
@@ -0,0 +1,26 @@
package org.jetbrains.jet.plugin.quickfix;
import com.intellij.codeInspection.HintAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
/**
* A fix with the user information hint.
*
* @author Nikolay Krasko
*/
public abstract class JetHintAction<T extends PsiElement> extends JetIntentionAction<T> implements HintAction {
public JetHintAction(@NotNull T element) {
super(element);
}
protected boolean isCaretNearRef(Editor editor, T ref) {
TextRange range = element.getTextRange();
int offset = editor.getCaretModel().getOffset();
return offset == range.getEndOffset();
}
}
@@ -24,7 +24,7 @@ public abstract class JetIntentionAction<T extends PsiElement> implements Intent
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return element.isValid();
return element.isValid() && file.getManager().isInProject(file);
}
@Override
@@ -42,7 +42,7 @@ public class QuickFixes {
add(Errors.ABSTRACT_PROPERTY_IN_PRIMARY_CONSTRUCTOR_PARAMETERS, removeAbstractModifierFactory);
add(Errors.ABSTRACT_PROPERTY_NOT_IN_CLASS, removeAbstractModifierFactory);
JetIntentionActionFactory<JetProperty> removePartsFromPropertyFactory = RemovePartsFromPropertyFix.createFactory();
add(Errors.ABSTRACT_PROPERTY_WITH_INITIALIZER, removeAbstractModifierFactory);
add(Errors.ABSTRACT_PROPERTY_WITH_INITIALIZER, removePartsFromPropertyFactory);
@@ -116,6 +116,9 @@ public class QuickFixes {
add(Errors.INITIALIZATION_USING_BACKING_FIELD_CUSTOM_SETTER, changeToBackingFieldFactory);
add(Errors.INITIALIZATION_USING_BACKING_FIELD_OPEN_SETTER, changeToBackingFieldFactory);
JetIntentionActionFactory<JetSimpleNameExpression> unresolvedReferenceFactory = ImportClassFix.createFactory();
add(Errors.UNRESOLVED_REFERENCE, unresolvedReferenceFactory);
ImplementMethodsHandler implementMethodsHandler = new ImplementMethodsHandler();
actionMap.put(Errors.ABSTRACT_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler);
actionMap.put(Errors.MANY_IMPL_MEMBER_NOT_IMPLEMENTED, implementMethodsHandler);
@@ -0,0 +1,12 @@
// "Import Class" "true"
namespace a
import a.b.M
fun test() {
val v = M
}
namespace b {
class M() { }
}
@@ -0,0 +1,10 @@
// "Import Class" "true"
namespace a
fun test() {
val v = <caret>M
}
namespace b {
class M() { }
}
@@ -19,7 +19,7 @@ import java.util.List;
/**
* @author svtk
*/
public class JetQuickFixTest extends LightQuickFixTestCase {
public class JetQuickFixTest extends LightQuickFixTestCase {
private final String dataPath;
private final String name;
private static FilenameFilter quickFixTestsFilter;