/******************************************************************************* * Copyright (c) 2009-2011 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI * * Arnold Lankamp - Arnold.Lankamp@cwi.nl *******************************************************************************/ package org.rascalmpl.eclipse.editor; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.ui.texteditor.ITextEditor; import org.rascalmpl.eclipse.Activator; import org.rascalmpl.eclipse.preferences.RascalPreferences; import org.rascalmpl.eclipse.terms.TermParseController; import org.rascalmpl.eclipse.util.ProjectConfig; import org.rascalmpl.library.util.PathConfig; import io.usethesource.vallang.IMap; import io.usethesource.vallang.ISet; import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IString; import io.usethesource.vallang.ITuple; import io.usethesource.vallang.IValue; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.values.uptr.ITree; import org.rascalmpl.values.uptr.TreeAdapter; import io.usethesource.impulse.model.ISourceProject; import io.usethesource.impulse.parser.IParseController; import io.usethesource.impulse.services.ISourceHyperlinkDetector; public class HyperlinkDetector implements ISourceHyperlinkDetector { private static final TypeFactory tf = TypeFactory.getInstance(); private static final Type linksRelType1 = tf.relType(tf.sourceLocationType(), tf.sourceLocationType()); private static final Type linksRelType2 = tf.relType(tf.sourceLocationType(), tf.sourceLocationType(), tf.stringType()); private static final IDEServicesModelProvider imp = IDEServicesModelProvider.getInstance(); public IHyperlink[] detectHyperlinks(IRegion region, ITextEditor editor, ITextViewer textViewer, IParseController parseController) { if (parseController == null) { return null; } ITree tree = (ITree) parseController.getCurrentAst(); if (tree != null && parseController instanceof TermParseController) { // DSL case return getTreeLinks(tree, region); } if (tree != null && parseController instanceof ParseController && RascalPreferences.isRascalCompilerEnabled()) { // Rascal case // TODO: integrate with DSL case ParseController rascalPc = (ParseController) parseController; ISourceProject rprj = rascalPc.getProject(); IProject prj = rprj != null ? rprj.getRawProject() : null; PathConfig pcfg = prj != null ? new ProjectConfig(ValueFactoryFactory.getValueFactory()).getPathConfig(prj) : new PathConfig(); ISet useDef = imp.getUseDef(rascalPc.getSourceLocation(), pcfg, rascalPc.getModuleName()); if (!checkUseDefType(useDef)) { Activator.log(useDef.getType() + " is not a rel[loc,loc] or rel[loc,loc,str]? " + useDef, null); return new IHyperlink[0]; } return getLinksForRegionFromUseDefRelation(region, useDef); } return null; } private boolean checkUseDefType(ISet useDef) { return useDef.getType().isSubtypeOf(linksRelType1) || useDef.getType().isSubtypeOf(linksRelType2); } private IHyperlink[] getTreeLinks(ITree tree, IRegion region) { IValue xref = tree.asAnnotatable().getAnnotation("hyperlinks"); if (xref != null && (xref.getType().isSubtypeOf(linksRelType1) || xref.getType().isSubtypeOf(linksRelType2))) { return getLinksForRegionFromUseDefRelation(region, (ISet) xref); } ITree ref = TreeAdapter.locateAnnotatedTree(tree, "link", region.getOffset()); if (ref != null) { IValue link = ref.asAnnotatable().getAnnotation("link"); if (link != null && link.getType().isSourceLocation()) { return new IHyperlink[] { new SourceLocationHyperlink(TreeAdapter.getLocation(ref), (ISourceLocation) link) }; } } ref = TreeAdapter.locateAnnotatedTree(tree, "links", region.getOffset()); if (ref != null) { IValue links = ref.asAnnotatable().getAnnotation("links"); if (links != null && links.getType().isSet() && links.getType().getElementType().isSourceLocation()) { IHyperlink[] a = new IHyperlink[((ISet) links).size()]; int i = 0; for (IValue l : ((ISet) links)) { a[i++] = new SourceLocationHyperlink(TreeAdapter.getLocation(ref), (ISourceLocation) l); } return a; } } IValue docLinksMapValue = tree.asAnnotatable().getAnnotation("docLinks"); ITree subtree = TreeAdapter.locateAnnotatedTree(tree, "loc", region.getOffset()); if (docLinksMapValue != null && docLinksMapValue.getType().isMap() && subtree != null) { ISourceLocation loc = TreeAdapter.getLocation(subtree); if (loc != null) { IMap docLinksMap = (IMap)docLinksMapValue; if (docLinksMap.containsKey(loc)) { IValue links = docLinksMap.get(loc); if (links != null && links.getType().isSet() && links.getType().getElementType().isSourceLocation()) { IHyperlink[] a = new IHyperlink[((ISet) links).size()]; int i = 0; for (IValue l : ((ISet) links)) { a[i++] = new SourceLocationHyperlink(loc, (ISourceLocation) l); } return a; } } } } return null; } private IHyperlink[] getLinksForRegionFromUseDefRelation(IRegion region, ISet rel) { List<IHyperlink> links = new ArrayList<>(); for (IValue v: rel) { ITuple t = ((ITuple)v); ISourceLocation loc = (ISourceLocation)t.get(0); if (region.getOffset() >= loc.getOffset() && region.getOffset() < loc.getOffset() + loc.getLength()) { ISourceLocation to = (ISourceLocation)t.get(1); if (rel.getType().getElementType().getArity() == 3 && rel.getType().getElementType().getFieldType(2).isString()) { links.add(new SourceLocationHyperlink(loc, to, ((IString)t.get(2)).getValue())); } else { links.add(new SourceLocationHyperlink(loc, to, to.toString())); } } } if (links.isEmpty()) { return null; } return sortAndFilterHyperlinks(links); //.toArray(new IHyperlink[] {}); } private IHyperlink[] sortAndFilterHyperlinks(List<IHyperlink> hyperlinks) { Collections.sort(hyperlinks, new Comparator<IHyperlink>() { @Override public int compare(IHyperlink o1, IHyperlink o2) { // Always show the smallest offset link first, this is the link under the mouse cursor return o2.getHyperlinkRegion().getOffset() - o1.getHyperlinkRegion().getOffset(); } }); List<IHyperlink> filteredLinks = new ArrayList<>(); for (IHyperlink link : hyperlinks) { if (filteredLinks.isEmpty() || filteredLinks.get(0).getHyperlinkRegion().equals(link.getHyperlinkRegion())) { filteredLinks.add(link); } } return filteredLinks.toArray(new IHyperlink[] {}); } }