Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactored ScalaHyperlinksDetector

Splitted ScalaHyperlinkDetector into two classes:

* HyperlinksResolver: Finds the symbol associated to a text selection in a scala
                      source. The symbol is then used to create the hyperlinks.
* HyperlinksDetector: Creates all possible hyperlinks for a given region. For
                      instance, if hyperlinks to implicits are resolved in this
                      class because the editor's annotation model is needed.
  • Loading branch information...
commit d8ebcde90a6bb62be3011f2caa0d493b72e41073 1 parent 589b9b4
Mirco Dotta dotta authored
6 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/hyperlinks/HyperlinkDetectorTests.scala
View
@@ -2,7 +2,7 @@ package scala.tools.eclipse.hyperlinks
import scala.tools.eclipse.testsetup.SDTTestUtils
import scala.tools.eclipse.testsetup.TestProjectSetup
-import scala.tools.eclipse.ScalaHyperlinkDetector
+import scala.tools.eclipse.hyperlink.HyperlinksResolver
import scala.tools.eclipse.ScalaWordFinder
import org.eclipse.core.resources.IMarker
@@ -28,11 +28,11 @@ class HyperlinkDetectorTests {
val positions = SDTTestUtils.positionsOf(contents, "/*^*/")
println("checking %d positions".format(positions.size))
- val detector = new ScalaHyperlinkDetector
+ val resolver = new HyperlinksResolver
for (pos <- positions) {
val wordRegion = ScalaWordFinder.findWord(unit.getContents, pos - 1)
val word = new String(unit.getContents.slice(wordRegion.getOffset, wordRegion.getOffset + wordRegion.getLength))
- val links = detector.scalaHyperlinks(unit, wordRegion)
+ val links = resolver.findHyperlinks(unit, wordRegion)
println("Found links: " + links)
assertTrue(links.isDefined)
assertEquals("Failed hyperlinking at position %d (%s)".format(pos, word), 1, links.get.size)
6 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/hyperlinks/HyperlinkTester.scala
View
@@ -1,7 +1,7 @@
package scala.tools.eclipse.hyperlinks
import scala.tools.eclipse.testsetup.TestProjectSetup
-import scala.tools.eclipse.ScalaHyperlinkDetector
+import scala.tools.eclipse.hyperlink.HyperlinksResolver
import scala.tools.eclipse.ScalaWordFinder
import org.junit.Assert.assertEquals
@@ -41,8 +41,8 @@ trait HyperlinkTester extends TestProjectSetup {
println("hyperlinking at position %d (%s)".format(pos, word))
// Execute SUT
- val detector = new ScalaHyperlinkDetector
- val maybeLinks = detector.scalaHyperlinks(unit, wordRegion)
+ val resolver = new HyperlinksResolver
+ val maybeLinks = resolver.findHyperlinks(unit, wordRegion)
// Verify Expectations
assertTrue("no links found for `%s`".format(word), maybeLinks.isDefined)
147 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaHyperlinkDetector.scala
View
@@ -1,147 +0,0 @@
-/*
- * Copyright 2005-2010 LAMP/EPFL
- */
-// $Id$
-package scala.tools.eclipse
-
-import org.eclipse.jdt.core.{ ICodeAssist, IJavaElement }
-import org.eclipse.jface.text.{ IRegion, ITextViewer }
-import org.eclipse.jface.text.hyperlink.{ AbstractHyperlinkDetector, IHyperlink }
-import org.eclipse.jdt.internal.core.Openable
-import org.eclipse.ui.texteditor.ITextEditor
-import org.eclipse.jdt.internal.ui.javaeditor.{ EditorUtility, JavaElementHyperlink }
-import org.eclipse.jdt.ui.actions.OpenAction
-import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor
-import javaelements.{ ScalaCompilationUnit, ScalaSelectionEngine, ScalaSelectionRequestor }
-import scala.tools.eclipse.logging.HasLogger
-
-class ScalaHyperlinkDetector extends AbstractHyperlinkDetector with HasLogger {
- def detectHyperlinks(viewer: ITextViewer, region: IRegion, canShowMultipleHyperlinks: Boolean): Array[IHyperlink] = {
- val textEditor = getAdapter(classOf[ITextEditor]).asInstanceOf[ITextEditor]
- detectHyperlinks(textEditor, region, canShowMultipleHyperlinks)
- }
-
- def scalaHyperlinks(scu: ScalaCompilationUnit, wordRegion: IRegion): Option[List[IHyperlink]] = {
- scu.withSourceFile({ (sourceFile, compiler) =>
- if (wordRegion == null || wordRegion.getLength == 0)
- None
- else {
- val start = wordRegion.getOffset
- val regionEnd = wordRegion.getOffset + wordRegion.getLength
- // removing 1 handles correctly hyperlinking requests @ EOF
- val end = if(sourceFile.length == regionEnd) regionEnd - 1 else regionEnd
-
- val pos = compiler.rangePos(sourceFile, start, start, end)
-
- import compiler.{ log => _, _ }
- val response = new Response[compiler.Tree]
- askTypeAt(pos, response)
- val typed = response.get
-
- logger.info("detectHyperlinks: wordRegion = " + wordRegion)
- compiler.askOption { () =>
- typed.left.toOption map {
- case Import(expr, sels) =>
- if(expr.pos.includes(pos)) {
- @annotation.tailrec
- def locate(p: Position, inExpr: Tree): Symbol = inExpr match {
- case Select(qualifier, name) =>
- if(qualifier.pos.includes(p)) locate(p, qualifier)
- else inExpr.symbol
- case tree => tree.symbol
- }
-
- List(locate(pos, expr))
- }
- else {
- sels find (selPos => selPos.namePos >= pos.start && selPos.namePos <= pos.end) map { sel =>
- val tpe = stabilizedType(expr)
- List(tpe.member(sel.name), tpe.member(sel.name.toTypeName))
- } getOrElse Nil
- }
- case Annotated(atp, _) => List(atp.symbol)
- case st: SymTree => List(st.symbol)
- case t => logger.info("unhandled tree " + t.getClass); List()
- } flatMap { list =>
- val filteredSyms = list filterNot { sym => sym.isPackage || sym == NoSymbol }
- if (filteredSyms.isEmpty) None else Some(
- filteredSyms.foldLeft(List[IHyperlink]()) { (links, sym) =>
- if (sym.isJavaDefined) links
- else {
- object DeclarationHyperlinkFactory extends scala.tools.eclipse.hyperlink.DeclarationHyperlinkFactory {
- protected val global: compiler.type = compiler
- }
- DeclarationHyperlinkFactory.create(scu, sym, wordRegion) match {
- case None => links
- case Some(l) => l :: links
- }
- }
- })
- }
- }.flatten.headOption match {
- case links @ Some(List()) =>
- logger.info("Falling back to selection engine for %s!".format(typed.left))
- links
- case links =>
- links
- }
- }
- })(None)
- }
-
- def detectHyperlinks(textEditor: ITextEditor, currentSelection: IRegion, canShowMultipleHyperlinks: Boolean): Array[IHyperlink] = {
- if (textEditor == null) // can be null if generated through ScalaPreviewerFactory
- null
- else
- EditorUtility.getEditorInputJavaElement(textEditor, false) match {
- case scu: ScalaCompilationUnit =>
- import scala.tools.eclipse.semantichighlighting.implicits.ImplicitConversionAnnotation
- import scala.tools.eclipse.ui.EditorUtils.{withEditor, getAnnotationsAtOffset}
- import scala.collection.mutable.ListBuffer
-
- var links = ListBuffer[IHyperlink]()
-
- withEditor(scu) { editor =>
- for ((ann, pos) <- getAnnotationsAtOffset(editor, currentSelection.getOffset)) ann match {
- case a: ImplicitConversionAnnotation if a.sourceLink.isDefined =>
- a.sourceLink.get +: links
- case _ => ()
- }
- }
-
- val wordRegion = ScalaWordFinder.findWord(scu.getContents, currentSelection.getOffset)
-
- scalaHyperlinks(scu, wordRegion) match {
- case None => null // do not try to use codeSelect.
- case Some(List()) =>
- codeSelect(textEditor, wordRegion, scu)
- case Some(hyperlinks) =>
- (hyperlinks ::: links.toList).toArray
- }
-
- case _ => null
- }
- }
-
- //Default path used for selecting.
- def codeSelect(textEditor: ITextEditor, wordRegion: IRegion, scu: ScalaCompilationUnit): Array[IHyperlink] = {
- try {
- val environment = scu.newSearchableEnvironment()
- val requestor = new ScalaSelectionRequestor(environment.nameLookup, scu)
- val engine = new ScalaSelectionEngine(environment, requestor, scu.getJavaProject.getOptions(true))
- val offset = wordRegion.getOffset
- engine.select(scu, offset, offset + wordRegion.getLength - 1)
- val elements = requestor.getElements
-
- if (elements.length == 0)
- null
- else {
- val qualify = elements.length > 1
- val openAction = new OpenAction(textEditor.asInstanceOf[JavaEditor])
- elements.map(new JavaElementHyperlink(wordRegion, openAction, _, qualify))
- }
- } catch {
- case _ => null
- }
- }
-}
10 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaSourceViewerConfiguration.scala
View
@@ -35,6 +35,7 @@ import scala.tools.eclipse.lexical._
import scala.tools.eclipse.formatter.ScalaFormattingStrategy
import scala.tools.eclipse.ui.AutoCloseBracketStrategy
import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
+import scala.tools.eclipse.hyperlink.text.HyperlinksDetector
class ScalaSourceViewerConfiguration(store: IPreferenceStore, scalaPreferenceStore: IPreferenceStore, editor: ITextEditor)
extends JavaSourceViewerConfiguration(JavaPlugin.getDefault.getJavaTextTools.getColorManager, store, editor, IJavaPartitions.JAVA_PARTITIONING) {
@@ -85,11 +86,10 @@ class ScalaSourceViewerConfiguration(store: IPreferenceStore, scalaPreferenceSto
new ScalaHover(getCodeAssist _)
}
- override def getHyperlinkDetectors(sv: ISourceViewer) = {
- val shd = new ScalaHyperlinkDetector
- if (editor != null)
- shd.setContext(editor)
- Array(shd)
+ override def getHyperlinkDetectors(sv: ISourceViewer): Array[IHyperlinkDetector] = {
+ val detector = new HyperlinksDetector
+ if (editor != null) detector.setContext(editor)
+ Array(detector)
}
def getCodeAssist: Option[ICodeAssist] = Option(editor) map { editor =>
81 org.scala-ide.sdt.core/src/scala/tools/eclipse/hyperlink/HyperlinksResolver.scala
View
@@ -0,0 +1,81 @@
+package scala.tools.eclipse.hyperlink
+
+import scala.Option.option2Iterable
+import scala.annotation.tailrec
+import scala.tools.eclipse.javaelements.ScalaCompilationUnit
+import scala.tools.eclipse.logging.HasLogger
+import scala.tools.eclipse.{ScalaPresentationCompiler => compiler}
+
+import org.eclipse.jface.text.hyperlink.IHyperlink
+import org.eclipse.jface.text.IRegion
+
+class HyperlinksResolver extends HasLogger {
+ def findHyperlinks(scu: ScalaCompilationUnit, wordRegion: IRegion): Option[List[IHyperlink]] = {
+ scu.withSourceFile({ (sourceFile, compiler) =>
+ if (wordRegion == null || wordRegion.getLength == 0)
+ None
+ else {
+ val start = wordRegion.getOffset
+ val regionEnd = wordRegion.getOffset + wordRegion.getLength
+ // removing 1 handles correctly hyperlinking requests @ EOF
+ val end = if (sourceFile.length == regionEnd) regionEnd - 1 else regionEnd
+
+ val pos = compiler.rangePos(sourceFile, start, start, end)
+
+ import compiler.{ log => _, _ }
+
+ val response = new Response[compiler.Tree]
+ askTypeAt(pos, response)
+ val typed = response.get
+
+ logger.info("detectHyperlinks: wordRegion = " + wordRegion)
+ compiler.askOption { () =>
+ typed.left.toOption map {
+ case Import(expr, sels) =>
+ if (expr.pos.includes(pos)) {
+ @annotation.tailrec
+ def locate(p: Position, inExpr: Tree): Symbol = inExpr match {
+ case Select(qualifier, name) =>
+ if (qualifier.pos.includes(p)) locate(p, qualifier)
+ else inExpr.symbol
+ case tree => tree.symbol
+ }
+
+ List(locate(pos, expr))
+ } else {
+ sels find (selPos => selPos.namePos >= pos.start && selPos.namePos <= pos.end) map { sel =>
+ val tpe = stabilizedType(expr)
+ List(tpe.member(sel.name), tpe.member(sel.name.toTypeName))
+ } getOrElse Nil
+ }
+ case Annotated(atp, _) => List(atp.symbol)
+ case st: SymTree => List(st.symbol)
+ case t => logger.info("unhandled tree " + t.getClass); List()
+ } flatMap { list =>
+ val filteredSyms = list filterNot { sym => sym.isPackage || sym == NoSymbol }
+ if (filteredSyms.isEmpty) None else Some(
+ filteredSyms.foldLeft(List[IHyperlink]()) { (links, sym) =>
+ if (sym.isJavaDefined) links
+ else {
+ object DeclarationHyperlinkFactory extends scala.tools.eclipse.hyperlink.DeclarationHyperlinkFactory {
+ protected val global: compiler.type = compiler
+ }
+ DeclarationHyperlinkFactory.create(scu, sym, wordRegion) match {
+ case None => links
+ case Some(l) => l :: links
+ }
+ }
+ })
+ }
+ }.flatten.headOption match {
+ case links @ Some(List()) =>
+ logger.info("Falling back to selection engine for %s!".format(typed.left))
+ links
+ case links =>
+ links
+ }
+ }
+ })(None)
+ }
+
+}
101 org.scala-ide.sdt.core/src/scala/tools/eclipse/hyperlink/text/HyperlinksDetector.scala
View
@@ -0,0 +1,101 @@
+package scala.tools.eclipse.hyperlink
+package text
+
+import scala.Array.canBuildFrom
+import scala.collection.mutable.ListBuffer
+import scala.tools.eclipse.hyperlink.HyperlinksResolver
+import scala.tools.eclipse.javaelements.ScalaCompilationUnit
+import scala.tools.eclipse.javaelements.ScalaSelectionEngine
+import scala.tools.eclipse.javaelements.ScalaSelectionRequestor
+import scala.tools.eclipse.semantichighlighting.implicits.ImplicitConversionAnnotation
+import scala.tools.eclipse.ui.EditorUtils.getAnnotationsAtOffset
+import scala.tools.eclipse.ui.EditorUtils.withEditor
+import scala.tools.eclipse.ScalaWordFinder
+
+import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility
+import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor
+import org.eclipse.jdt.internal.ui.javaeditor.JavaElementHyperlink
+import org.eclipse.jdt.ui.actions.OpenAction
+import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector
+import org.eclipse.jface.text.hyperlink.IHyperlink
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.ITextViewer
+import org.eclipse.ui.texteditor.ITextEditor
+
+class HyperlinksDetector extends AbstractHyperlinkDetector {
+
+ private val resolver: HyperlinksResolver = new HyperlinksResolver
+
+ def detectHyperlinks(viewer: ITextViewer, region: IRegion, canShowMultipleHyperlinks: Boolean): Array[IHyperlink] = {
+ val textEditor = getAdapter(classOf[ITextEditor]).asInstanceOf[ITextEditor]
+ detectHyperlinks(textEditor, region, canShowMultipleHyperlinks)
+ }
+
+ def detectHyperlinks(textEditor: ITextEditor, currentSelection: IRegion, canShowMultipleHyperlinks: Boolean): Array[IHyperlink] = {
+ if (textEditor == null) // can be null if generated through ScalaPreviewerFactory
+ null
+ else
+ EditorUtility.getEditorInputJavaElement(textEditor, false) match {
+ case scu: ScalaCompilationUnit =>
+ val wordRegion = ScalaWordFinder.findWord(scu.getContents, currentSelection.getOffset)
+
+ resolver.findHyperlinks(scu, wordRegion) match {
+ case None => null // do not try to use codeSelect.
+ case Some(hyperlinks) =>
+ val implicitHyperlinks = findHyperlinkToImplicit(scu, currentSelection.getOffset)
+ if (hyperlinks.isEmpty) codeSelect(textEditor, wordRegion, scu) ++: implicitHyperlinks.toArray
+ else (hyperlinks ::: implicitHyperlinks).toArray
+ }
+
+ case _ => null
+ }
+ }
+
+ /**
+ * Check if an {{{ImplicitConversionAnnotation}}} at the given {{{offset}}} exists in the
+ * editor's annotation model.
+ *
+ * @return the {{{IHyperlink}}} to the implicit declaration, if one exists.
+ */
+ // FIXME: I quite dislike the current implementation, for the following reasons:
+ // 1) We go through all the editor's annotations to find if an implicit conversion is applied at the given {{{offset}}}.
+ // 2) Because we use the editor's annotation model, this functionality cannot be tested in a UI-less environment.
+ private def findHyperlinkToImplicit(scu: ScalaCompilationUnit, offset: Int): List[IHyperlink] = {
+ import scala.tools.eclipse.semantichighlighting.implicits.ImplicitConversionAnnotation
+ import scala.tools.eclipse.ui.EditorUtils.{ withEditor, getAnnotationsAtOffset }
+
+ var hyperlinks = List[IHyperlink]()
+
+ withEditor(scu) { editor =>
+ for ((ann, pos) <- getAnnotationsAtOffset(editor, offset)) ann match {
+ case a: ImplicitConversionAnnotation if a.sourceLink.isDefined =>
+ hyperlinks = a.sourceLink.get :: hyperlinks
+ case _ => ()
+ }
+ }
+
+ hyperlinks
+ }
+
+ //Default path used for selecting.
+ private def codeSelect(textEditor: ITextEditor, wordRegion: IRegion, scu: ScalaCompilationUnit): Array[IHyperlink] = {
+ try {
+ val environment = scu.newSearchableEnvironment()
+ val requestor = new ScalaSelectionRequestor(environment.nameLookup, scu)
+ val engine = new ScalaSelectionEngine(environment, requestor, scu.getJavaProject.getOptions(true))
+ val offset = wordRegion.getOffset
+ engine.select(scu, offset, offset + wordRegion.getLength - 1)
+ val elements = requestor.getElements
+
+ if (elements.length == 0)
+ null
+ else {
+ val qualify = elements.length > 1
+ val openAction = new OpenAction(textEditor.asInstanceOf[JavaEditor])
+ elements.map(new JavaElementHyperlink(wordRegion, openAction, _, qualify))
+ }
+ } catch {
+ case _ => null
+ }
+ }
+}
4 org.scala-ide.sdt.core/src/scala/tools/eclipse/javaelements/ScalaCompilationUnit.scala
View
@@ -32,6 +32,7 @@ import org.eclipse.jdt.internal.core.JavaElement
import org.eclipse.jdt.internal.core.SourceRefElement
import scala.tools.eclipse.logging.HasLogger
import scala.tools.nsc.interactive.Response
+import scala.tools.eclipse.hyperlink.text.HyperlinksDetector
trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with ScalaElement with IScalaCompilationUnit with IBufferChangedListener with HasLogger {
val project = ScalaPlugin.plugin.getScalaProject(getJavaProject.getProject)
@@ -261,7 +262,8 @@ trait ScalaCompilationUnit extends Openable with env.ICompilationUnit with Scala
def getOffset = selection.getOffset
def getLength = selection.getLength
}
- new ScalaHyperlinkDetector().detectHyperlinks(editor, region, false) match {
+
+ new HyperlinksDetector().detectHyperlinks(editor, region, canShowMultipleHyperlinks = false) match {
case Array(hyp) => hyp.open
case _ =>
}
Please sign in to comment.
Something went wrong with that request. Please try again.