package com.redhat.ceylon.eclipse.code.correct; import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.appendPositionalArgs; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getAssignableLiterals; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getCurrentSpecifierRegion; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getProposedName; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getSortedProposedValues; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleClass; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleMethod; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleValue; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isInBounds; import static com.redhat.ceylon.eclipse.code.editor.Navigation.gotoFile; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getDecoratedImage; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getImageForDeclaration; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.getCompletionFont; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CEYLON_LITERAL; import static org.eclipse.jface.text.link.LinkedPositionGroup.NO_STOP; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedNamesAssistProposal.DeleteBlockingExitPolicy; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.ProposalPosition; import org.eclipse.jface.viewers.StyledString; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.eclipse.util.LinkedMode; import com.redhat.ceylon.ide.common.model.ModifiableSourceFile; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.Constructor; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.DeclarationWithProximity; import com.redhat.ceylon.model.typechecker.model.Function; import com.redhat.ceylon.model.typechecker.model.Functional; import com.redhat.ceylon.model.typechecker.model.ModelUtil; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.NothingType; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.Value; class InitializerProposal extends CorrectionProposal { final class NestedCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private Unit getUnit() { return unit; } private final Declaration dec; private final int offset; NestedCompletionProposal(Declaration dec, int offset) { this.offset = offset; this.dec = dec; } @Override public Point getSelection(IDocument document) { return null; } public void apply(IDocument document) { try { IRegion region = getCurrentSpecifierRegion(document, offset); document.replace(region.getOffset(), region.getLength(), getText(false)); } catch (BadLocationException e) { e.printStackTrace(); } } public String getDisplayString() { return getText(true); } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, getCompletionFont()); return result; } @Override public Image getImage() { return getImageForDeclaration(dec); } public String getAdditionalProposalInfo() { return null; } @Override public IContextInformation getContextInformation() { return null; } private String getText(boolean description) { StringBuilder sb = new StringBuilder(); Unit unit = getUnit(); sb.append(getProposedName(null, dec, unit)); if (dec instanceof Functional) { appendPositionalArgs(dec, unit, sb, false, description); } return sb.toString(); } @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } @Override public void selected(ITextViewer viewer, boolean smartToggle) {} @Override public void unselected(ITextViewer viewer) {} @Override public boolean validate(IDocument document, int currentOffset, DocumentEvent event) { if (event==null) { return true; } else { try { IRegion region = getCurrentSpecifierRegion(document, offset); String content = document.get(region.getOffset(), currentOffset-region.getOffset()); return isContentValid(content); } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } private boolean isContentValid(String content) { String filter = content.trim().toLowerCase(); return ModelUtil.isNameMatching(content, dec) || getProposedName(null, dec, getUnit()) .toLowerCase() .startsWith(filter); } } final class NestedLiteralCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final String value; private final int offset; NestedLiteralCompletionProposal(String value, int offset) { this.offset = offset; this.value = value; } @Override public Point getSelection(IDocument document) { return null; } public void apply(IDocument document) { try { IRegion region = getCurrentSpecifierRegion(document, offset); document.replace(region.getOffset(), region.getLength(), value); } catch (BadLocationException e) { e.printStackTrace(); } } public String getDisplayString() { return value; } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, getCompletionFont()); return result; } @Override public Image getImage() { return getDecoratedImage(CEYLON_LITERAL, 0, false); } public String getAdditionalProposalInfo() { return null; } @Override public IContextInformation getContextInformation() { return null; } @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } @Override public void selected(ITextViewer viewer, boolean smartToggle) {} @Override public void unselected(ITextViewer viewer) {} @Override public boolean validate(IDocument document, int currentOffset, DocumentEvent event) { if (event==null) { return true; } else { try { IRegion region = getCurrentSpecifierRegion(document, offset); String content = document.get(region.getOffset(), currentOffset-region.getOffset()); String filter = content.trim().toLowerCase(); if (value.toLowerCase().startsWith(filter)) { return true; } } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } } private final Type type; private final Scope scope; private final Unit unit; private int exitPos; InitializerProposal(String name, Change change, Declaration declaration, Type type, Region selection, Image image, int exitPos) { this(name, change, declaration.getScope(), declaration.getUnit(), type, selection, image, exitPos); } InitializerProposal(String name, Change change, Scope scope, Unit unit, Type type, Region selection, Image image, int exitPos) { super(name, change, selection, image); this.exitPos = exitPos; this.scope = scope; this.unit = unit; this.type = type; } @Override public Point getSelection(IDocument document) { //we don't apply a selection because: //1. we're using linked mode anyway, and //2. the change might have been applied to // a different editor to the one from // which the quick fix was invoked. return null; } @Override public void apply(IDocument document) { CeylonEditor editor = null; if (unit instanceof ModifiableSourceFile) { ModifiableSourceFile<IProject,IResource,IFolder,IFile> msf = (ModifiableSourceFile<IProject,IResource,IFolder,IFile>) unit; IFile file = msf.getResourceFile(); if (file!=null) { editor = (CeylonEditor) gotoFile(file, 0, 0); //NOTE: the document we're given is the one //for the editor from which the quick fix was //invoked, not the one to which the fix applies IDocument ed = editor.getParseController() .getDocument(); if (ed!=document) { document = ed; exitPos = -1; } } } int lenBefore = document.getLength(); super.apply(document); int lenAfter = document.getLength(); Point point = super.getSelection(document); if (point==null) return; editor.selectAndReveal(point.x, point.y); //TODO: preference to disable linked mode? if (lenAfter>lenBefore && editor!=null) { if (point.y>0) { LinkedModeModel linkedModeModel = new LinkedModeModel(); ICompletionProposal[] proposals = getProposals(document, point); if (proposals.length>1) { ProposalPosition linkedPosition = new ProposalPosition(document, point.x, point.y, 0, proposals); try { LinkedMode.addLinkedPosition( linkedModeModel, linkedPosition); int adjustedExitPos = exitPos; if (exitPos>=0 && exitPos>point.x) { adjustedExitPos += lenAfter-lenBefore; } int exitSeq = exitPos>=0 ? 1 : NO_STOP; LinkedMode.installLinkedMode(editor, document, linkedModeModel, this, new DeleteBlockingExitPolicy(document), exitSeq, adjustedExitPos); } catch (BadLocationException e) { e.printStackTrace(); } } } } } private ICompletionProposal[] getProposals( IDocument document, Point point) { List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); // try { // //this is totally lame // //TODO: see InvocationCompletionProcessor // proposals.add(new NestedLiteralCompletionProposal( // document.get(point.x, point.y), point.x)); // } // catch (BadLocationException e1) { // e1.printStackTrace(); // } addValueArgumentProposals(point.x, proposals); return proposals.toArray(new ICompletionProposal[0]); } private void addValueArgumentProposals(int loc, List<ICompletionProposal> props) { if (type==null) return; for (String value: getAssignableLiterals(type, unit)) { props.add(new NestedLiteralCompletionProposal( value, loc)); } TypeDeclaration td = type.getDeclaration(); for (DeclarationWithProximity dwp: getSortedProposedValues(scope, unit)) { if (dwp.isUnimported()) { //don't propose unimported stuff b/c adding //imports drops us out of linked mode and //because it results in a pause continue; } Declaration d = dwp.getDeclaration(); if (d instanceof NothingType) { return; } String pname = d.getUnit() .getPackage() .getNameAsString(); boolean inLangModule = pname.equals(Module.LANGUAGE_MODULE_NAME); if (d instanceof Value) { Value value = (Value) d; if (inLangModule) { if (isIgnoredLanguageModuleValue(value)) { continue; } } Type vt = value.getType(); if (vt!=null && !vt.isNothing() && (isTypeParamInBounds(td, vt) || vt.isSubtypeOf(type))) { props.add(new NestedCompletionProposal(d, loc)); } } if (d instanceof Function) { if (!d.isAnnotation()) { Function method = (Function) d; if (inLangModule) { if (isIgnoredLanguageModuleMethod(method)) { continue; } } Type mt = method.getType(); if (mt!=null && !mt.isNothing() && (isTypeParamInBounds(td, mt) || mt.isSubtypeOf(type))) { props.add(new NestedCompletionProposal(d, loc)); } } } if (d instanceof Class) { Class clazz = (Class) d; if (!clazz.isAbstract() && !d.isAnnotation()) { if (inLangModule) { if (isIgnoredLanguageModuleClass(clazz)) { continue; } } Type ct = clazz.getType(); if (ct!=null && !ct.isNothing() && (isTypeParamInBounds(td, ct) || ct.getDeclaration() .equals(type.getDeclaration()) || ct.isSubtypeOf(type))) { if (clazz.getParameterList()!=null) { props.add(new NestedCompletionProposal(d, loc)); } for (Declaration m: clazz.getMembers()) { if (m instanceof Constructor && m.isShared() && m.getName()!=null) { props.add(new NestedCompletionProposal(m, loc)); } } } } } } } private boolean isTypeParamInBounds(TypeDeclaration td, Type t) { return (td instanceof TypeParameter) && isInBounds(((TypeParameter)td).getSatisfiedTypes(), t); } }