KT-805 Auto import on code completion insert (Initial version)
This commit is contained in:
@@ -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;
|
||||
|
||||
+4
-3
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user