package com.redhat.ceylon.eclipse.code.complete; import static com.redhat.ceylon.eclipse.code.complete.EclipseCompletionProcessor.NO_COMPLETIONS; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.fullPath; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isModuleDescriptor; import static com.redhat.ceylon.eclipse.code.hover.DocumentationHover.getDocumentationFor; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getImageForDeclaration; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.LINKED_MODE_ARGUMENTS; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getPackageName; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.importsJ2C; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.getCompletionFont; import static com.redhat.ceylon.eclipse.ui.CeylonResources.MODULE; import static com.redhat.ceylon.eclipse.ui.CeylonResources.PACKAGE; import static com.redhat.ceylon.eclipse.util.Highlights.MEMBER_STYLER; import static com.redhat.ceylon.eclipse.util.Highlights.TYPE_STYLER; import static com.redhat.ceylon.eclipse.util.ModuleQueries.getModuleQuery; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isNameMatching; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isOverloadedVersion; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; 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.ILinkedModeListener; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.ProposalPosition; import org.eclipse.jface.viewers.StyledString; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import com.redhat.ceylon.cmr.api.ModuleQuery; import com.redhat.ceylon.cmr.api.ModuleSearchResult; import com.redhat.ceylon.cmr.api.ModuleSearchResult.ModuleDetails; import com.redhat.ceylon.cmr.api.ModuleVersionDetails; import com.redhat.ceylon.common.Versions; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.eclipse.code.complete.CompletionProposal.FixedImageRetriever; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.util.EditorUtil; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.eclipse.util.LinkedMode; import com.redhat.ceylon.ide.common.typechecker.LocalAnalysisResult; import com.redhat.ceylon.ide.common.util.escaping_; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.ImportList; import com.redhat.ceylon.model.typechecker.model.ModelUtil; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; public class PackageCompletions { public static final class QueriedModulePackageProposal extends PackageProposal { private final ModuleVersionDetails version; private final Unit unit; private final ModuleDetails md; private String fullPackageName; public QueriedModulePackageProposal(int offset, String prefix, String memberPackageSubname, boolean withBody, String fullPackageName, LocalAnalysisResult controller, ModuleVersionDetails version, Unit unit, ModuleDetails md) { super(offset, prefix, memberPackageSubname, withBody, fullPackageName, (CeylonParseController) controller); this.fullPackageName = fullPackageName; this.version = version; this.unit = unit; this.md = md; } @Override public void apply(IDocument document) { super.apply(document); importsJ2C().importUtil().addModuleImport( controller.getLastPhasedUnit().getPackage().getModule(), version.getModule(), version.getVersion()); } @Override public String getAdditionalProposalInfo() { return getAdditionalProposalInfo(null); } @Override public String getAdditionalProposalInfo(IProgressMonitor monitor) { return getDocumentationFor(md, version.getVersion(), fullPackageName, controller.getLastCompilationUnit().getScope(), unit); } } public static final class ImportedModulePackageProposal extends PackageProposal { private final class PackageMemberCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final Point selection; private final LinkedModeModel linkedModeModel; private final Declaration d; private PackageMemberCompletionProposal( Point selection, LinkedModeModel linkedModeModel, Declaration d) { this.selection = selection; this.linkedModeModel = linkedModeModel; this.d = d; } @Override public Point getSelection(IDocument document) { return null; } @Override public Image getImage() { return getImageForDeclaration(d); } @Override public String getDisplayString() { return d.getName(); } @Override public IContextInformation getContextInformation() { return null; } @Override public String getAdditionalProposalInfo() { return null; } int length(IDocument document) { int length = 0; try { for (int i=selection.x; i<document.getLength() && (Character.isJavaIdentifierPart(document.getChar(i)) || document.getChar(i)=='.'); i++) { length++; } } catch (BadLocationException e) { e.printStackTrace(); } return length; } @Override public void apply(IDocument document) { try { document.replace(selection.x, length(document), d.getName()); } catch (BadLocationException e) { e.printStackTrace(); } linkedModeModel.exit(ILinkedModeListener.UPDATE_CARET); } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleIdentifier(result, prefix, getDisplayString(), d instanceof TypeDeclaration ? TYPE_STYLER : MEMBER_STYLER, getCompletionFont()); return result; } @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 offset, DocumentEvent event) { int start = selection.x; if (offset<start) { return false; } String prefix; try { prefix = document.get(start, offset-start); } catch (BadLocationException e) { return false; } return isNameMatching(prefix, d); } } private final Package candidate; public ImportedModulePackageProposal(int offset, String prefix, String memberPackageSubname, boolean withBody, String fullPackageName, CeylonParseController controller, Package candidate) { super(offset, prefix, memberPackageSubname, withBody, fullPackageName, controller); this.candidate = candidate; } @Override public void apply(IDocument document) { super.apply(document); if (withBody && CeylonPlugin.getPreferences().getBoolean(LINKED_MODE_ARGUMENTS)) { final LinkedModeModel linkedModeModel = new LinkedModeModel(); final Point selection = getSelection(document); List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); for (final Declaration d: candidate.getMembers()) { if (ModelUtil.isResolvable(d) && d.isShared() && !isOverloadedVersion(d)) { proposals.add( new PackageMemberCompletionProposal( selection, linkedModeModel, d)); } } if (!proposals.isEmpty()) { ProposalPosition linkedPosition = new ProposalPosition(document, selection.x, selection.y, 0, proposals.toArray(NO_COMPLETIONS)); try { LinkedMode.addLinkedPosition(linkedModeModel, linkedPosition); CeylonEditor editor = (CeylonEditor) EditorUtil.getCurrentEditor(); LinkedMode.installLinkedMode(editor, document, linkedModeModel, this, new LinkedMode.NullExitPolicy(), -1, 0); } catch (BadLocationException ble) { ble.printStackTrace(); } } } } @Override public String getAdditionalProposalInfo() { return getAdditionalProposalInfo(null); } @Override public String getAdditionalProposalInfo(IProgressMonitor monitor) { return getDocumentationFor(controller, candidate); } } public static final class PackageDescriptorProposal extends CompletionProposal { public PackageDescriptorProposal(int offset, String prefix, String desc, String text) { super(offset, prefix, new FixedImageRetriever(PACKAGE), desc, text); } @Deprecated PackageDescriptorProposal(int offset, String prefix, String packageName) { super(offset, prefix, new FixedImageRetriever(PACKAGE), "package " + packageName, "package " + packageName + ";"); } @Override protected boolean qualifiedNameIsPath() { return true; } } public static class PackageProposal extends CompletionProposal { protected final boolean withBody; protected final CeylonParseController controller; PackageProposal(int offset, String prefix, String memberPackageSubname, boolean withBody, String fullPackageName, CeylonParseController controller) { super(offset, prefix, new FixedImageRetriever(PACKAGE), fullPackageName + (withBody ? " { ... }" : ""), memberPackageSubname + (withBody ? " { ... }" : "")); this.withBody = withBody; this.controller = controller; } @Override public Point getSelection(IDocument document) { if (withBody) { return new Point(offset+text.indexOf("...")-prefix.length(), 3); } else { return super.getSelection(document); } } @Override protected boolean qualifiedNameIsPath() { return true; } @Override public StyledString getStyledDisplayString() { String text = getDisplayString(); if (withBody) { int loc = text.indexOf(" {"); return new StyledString( text.substring(0, loc), Highlights.PACKAGE_STYLER) .append(text.substring(loc)); } else { return new StyledString(text, Highlights.PACKAGE_STYLER); } } } @Deprecated static void addPackageCompletions(CeylonParseController cpc, int offset, String prefix, Tree.ImportPath path, Node node, List<ICompletionProposal> result, boolean withBody, IProgressMonitor monitor) { String fullPath = fullPath(offset, prefix, path); addPackageCompletions(offset, prefix, fullPath, withBody, node.getUnit(), cpc, result, monitor); } @Deprecated private static void addPackageCompletions( int offset, String prefix, String fullPath, boolean withBody, Unit unit, CeylonParseController controller, List<ICompletionProposal> result, IProgressMonitor monitor) { if (unit!=null) { //a null unit can occur if we have not finished parsing the file boolean found = false; Module module = unit.getPackage().getModule(); final String fullPrefix = fullPath + prefix; for (final Package candidate: module.getAllVisiblePackages()) { //if (!packages.contains(p)) { //packages.add(p); //if ( p.getModule().equals(module) || p.isShared() ) { String packageName = escaping_.get_().escapePackageName(candidate); if (!packageName.isEmpty() && packageName.startsWith(fullPrefix)) { boolean already = false; if (!fullPrefix.equals(packageName)) { //don't add already imported packages, unless //it is an exact match to the typed path for (ImportList il: unit.getImportLists()) { if (il.getImportedScope()==candidate) { already = true; break; } } } //TODO: completion filtering if (!already) { result.add(new ImportedModulePackageProposal( offset, prefix, packageName.substring(fullPath.length()), withBody, packageName, controller, candidate)); found = true; } } //} } if (!found && !unit.getPackage().getNameAsString().isEmpty()) { IProject project = controller.getProject(); monitor.subTask("querying module repositories..."); ModuleQuery query = getModuleQuery("", module, project); query.setMemberName(fullPrefix); query.setMemberSearchPackageOnly(true); query.setMemberSearchExact(false); query.setJvmBinaryMajor(Versions.JVM_BINARY_MAJOR_VERSION); query.setJvmBinaryMinor(Versions.JVM_BINARY_MINOR_VERSION); query.setJsBinaryMajor(Versions.JS_BINARY_MAJOR_VERSION); query.setJsBinaryMinor(Versions.JS_BINARY_MINOR_VERSION); ModuleSearchResult msr = controller.getTypeChecker() .getContext() .getRepositoryManager() .searchModules(query); for (final ModuleDetails md: msr.getResults()) { final ModuleVersionDetails version = md.getLastVersion(); for (String packageName: version.getMembers()) { //TODO: completion filtering if (packageName.startsWith(fullPrefix)) { result.add(new QueriedModulePackageProposal(offset, prefix, packageName.substring(fullPath.length()), withBody, packageName, controller, version, unit, md)); } } } } } } @Deprecated static void addPackageDescriptorCompletion(CeylonParseController cpc, int offset, String prefix, List<ICompletionProposal> result) { if (!"package".startsWith(prefix)) return; IFile file = cpc.getProject().getFile(cpc.getPath()); String packageName = getPackageName(file); if (packageName!=null) { result.add(new PackageDescriptorProposal(offset, prefix, packageName)); } } @Deprecated static void addCurrentPackageNameCompletion(CeylonParseController cpc, int offset, String prefix, List<ICompletionProposal> result) { IFile file = cpc.getProject().getFile(cpc.getPath()); String moduleName = getPackageName(file); if (moduleName!=null) { result.add(new CompletionProposal(offset, prefix, new FixedImageRetriever(isModuleDescriptor(cpc) ? MODULE : PACKAGE), moduleName, moduleName)); } } }