diff --git a/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF b/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF
index 789dee9..071b3dd 100644
--- a/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF
+++ b/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF
@@ -27,7 +27,10 @@ Require-Bundle:
org.scala-lang.scala-compiler;bundle-version="[2.11,2.12)",
org.scala-lang.scala-reflect;bundle-version="[2.11,2.12)",
org.scala-ide.sdt.core;bundle-version="[4.0.0,5.0.0)",
- org.eclipse.search
+ org.eclipse.search,
+ org.eclipse.core.databinding.beans,
+ org.eclipse.jface.databinding,
+ org.eclipse.core.databinding.property
Import-Package:
com.ibm.icu.text;apply-aspects:=false;org.eclipse.swt.graphics;apply-aspects:=false,
scala.tools.eclipse.contribution.weaving.jdt.ui.javaeditor.formatter;apply-aspects:=false
diff --git a/org.scala.tools.eclipse.search/plugin.xml b/org.scala.tools.eclipse.search/plugin.xml
index 9540be9..ebd9be9 100644
--- a/org.scala.tools.eclipse.search/plugin.xml
+++ b/org.scala.tools.eclipse.search/plugin.xml
@@ -9,6 +9,13 @@
name="Type Hierarchy"
class="org.scala.tools.eclipse.search.ui.TypeHierarchyView"
icon="icons/type-hierarchy-scala.gif" />
+
+
@@ -54,6 +61,12 @@
description="Open Type Hierarchy"
categoryId="org.scala.tools.eclipse.search.commands"
id="org.scala.tools.eclipse.search.commands.OpenTypeHierarchy"/>
+
+
@@ -64,6 +77,9 @@
+
@@ -79,6 +95,11 @@
id="org.scala.tools.eclipse.search.menus.typehiearchy"
label="Open Type Hierarchy"
tooltip="" />
+
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/SearchPlugin.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/SearchPlugin.scala
index 69850f2..1401576 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/SearchPlugin.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/SearchPlugin.scala
@@ -16,8 +16,10 @@ object SearchPlugin extends HasLogger {
// Only expose the Finder API.
@volatile var finder: Finder = _
+ @volatile private var plugin: SearchPlugin = _
final val PLUGIN_ID = "org.scala.tools.eclipse.search"
+ def apply() = plugin
}
class SearchPlugin extends AbstractUIPlugin with HasLogger {
@@ -28,7 +30,7 @@ class SearchPlugin extends AbstractUIPlugin with HasLogger {
override def start(context: BundleContext) {
super.start(context)
-
+ SearchPlugin.plugin = this
val reporter = new DialogErrorReporter
val index = new Index {
override val base = getStateLocation().append(INDEX_DIR_NAME)
@@ -56,6 +58,7 @@ class SearchPlugin extends AbstractUIPlugin with HasLogger {
indexManager.shutdown()
indexManager = null
SearchPlugin.finder = null
+ SearchPlugin.plugin = null
}
}
\ No newline at end of file
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/handlers/OpenCallHierarchy.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/handlers/OpenCallHierarchy.scala
new file mode 100644
index 0000000..2b72947
--- /dev/null
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/handlers/OpenCallHierarchy.scala
@@ -0,0 +1,90 @@
+package org.scala.tools.eclipse.search
+package handlers
+
+import org.scalaide.logging.HasLogger
+import org.eclipse.core.commands.AbstractHandler
+import org.eclipse.core.commands.ExecutionEvent
+import org.eclipse.ui.PlatformUI
+import org.scala.tools.eclipse.search.ErrorReporter
+import org.scala.tools.eclipse.search.SearchPlugin
+import org.scala.tools.eclipse.search.searching.Finder
+import org.scala.tools.eclipse.search.ui.DialogErrorReporter
+import org.scala.tools.eclipse.search.ui.TypeHierarchyView
+import org.scalaide.util.Utils.WithAsInstanceOfOpt
+import org.eclipse.ui.handlers.HandlerUtil
+import org.scala.tools.eclipse.search.searching.Location
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.core.runtime.IStatus
+import org.eclipse.core.runtime.Status
+import org.eclipse.swt.widgets.Display
+import org.scala.tools.eclipse.search.searching.ProjectFinder
+import org.scalaide.core.IScalaPlugin
+import org.scala.tools.eclipse.search.searching.Scope
+import org.scalaide.ui.editor.InteractiveCompilationUnitEditor
+import org.scala.tools.eclipse.search.ui.CallHierarchyView
+import org.scala.tools.eclipse.search.ui.Node
+import org.eclipse.jface.operation.IRunnableWithProgress
+import org.eclipse.jface.dialogs.ProgressMonitorDialog
+import scala.tools.nsc.interactive.Response
+import scala.reflect.internal.util.OffsetPosition
+import org.scala.tools.eclipse.search.ui.CallerNode
+
+class OpenCallHierarchy extends AbstractHandler with HasLogger {
+ private val finder: Finder = SearchPlugin.finder
+ private val reporter: ErrorReporter = new DialogErrorReporter
+
+ override def execute(event: ExecutionEvent): Object = {
+
+ def scheduleJob(e: Entity, scope: Scope, in: Node) = {
+ val job = new Job("Searching for callers...") {
+ def run(monitor: IProgressMonitor): IStatus = {
+
+ finder.findCallers(e, scope, (offset, ee, label, owner, project) => in.list = CallerNode(offset, ee, scope, label, owner, project) :: in.list, monitor)
+ Status.OK_STATUS
+ }
+ }
+ job.schedule()
+
+ }
+
+ def view = PlatformUI.getWorkbench()
+ .getActiveWorkbenchWindow()
+ .getActivePage()
+ .showView(CallHierarchyView.VIEW_ID)
+
+ for {
+ // For the selection
+ editor <- Option(HandlerUtil.getActiveEditor(event)) onEmpty reporter.reportError("An editor has to be active")
+ scalaEditor <- editor.asInstanceOfOpt[InteractiveCompilationUnitEditor] onEmpty reporter.reportError("Active editor wasn't a Scala editor")
+ selection <- UIUtil.getSelection(scalaEditor) onEmpty reporter.reportError("You need to have a selection")
+ thview <- view.asInstanceOfOpt[CallHierarchyView] onEmpty logger.debug("Wasn't an instance of CallHierarchyView")
+ } {
+ // Get the relevant scope to search for sub-types in.
+ val projects = ProjectFinder.projectClosure(scalaEditor.getInteractiveCompilationUnit.scalaProject.underlying)
+ val scope = Scope(projects.map(IScalaPlugin().asScalaProject(_)).flatten)
+ val loc = Location(scalaEditor.getInteractiveCompilationUnit, selection.getOffset())
+
+ val root = thview.input
+ root.list = List()
+ finder.entityAt(loc) match {
+ case Right(Some(entity: Method)) =>
+ scheduleJob(entity, scope, root)
+ case Right(Some(entity: Val)) =>
+ scheduleJob(entity, scope, root)
+ case Right(Some(entity: Var)) =>
+ scheduleJob(entity, scope, root)
+ case Right(Some(entity)) => reporter.reportError(s"Sorry, can't use selected '${entity.name}' to build a call-hierarchy.")
+ case Right(None) => // No-op
+ case Left(_) =>
+ reporter.reportError("""Couldn't get the symbol of the given entity.
+ |This is very likely a bug, so please submit a bug report that contains
+ |a minimal example to https://www.assembla.com/spaces/scala-ide/tickets
+ |Thank you! - IDE Team""".stripMargin)
+ }
+ }
+
+ null
+ }
+
+}
\ No newline at end of file
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Index.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Index.scala
index 8f2cb81..beab848 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Index.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Index.scala
@@ -6,7 +6,7 @@ import scala.Array.fallbackCanBuildFrom
import scala.collection.JavaConverters.asJavaIterableConverter
import org.scalaide.logging.HasLogger
import scala.util.Try
-import scala.util.control.{Exception => Ex}
+import scala.util.control.{ Exception => Ex }
import scala.util.control.Exception.Catch
import org.apache.lucene.analysis.core.SimpleAnalyzer
import org.apache.lucene.document.Document
@@ -74,7 +74,7 @@ trait Index extends HasLogger {
*/
def isIndexable(file: IFile): Boolean = {
// TODO: https://scala-ide-portfolio.assembla.com/spaces/scala-ide/tickets/1001616
- Option(file).filter(_.exists).map( _.getFileExtension() == "scala").getOrElse(false)
+ Option(file).filter(_.exists).map(_.getFileExtension() == "scala").getOrElse(false)
}
def indexExists(project: IProject): Boolean = {
@@ -86,7 +86,7 @@ trait Index extends HasLogger {
using(FSDirectory.open(loc), handlers = IOToTry[Boolean]) { dir =>
using(new CheckIndex(dir), handlers = IOToTry[Boolean]) { ci =>
Try(ci.checkIndex().clean)
- }
+ }
}.getOrElse(false)
}
@@ -140,17 +140,17 @@ trait Index extends HasLogger {
}
/**
- * Search the projects for all occurrence of the `word` that are found in declarations.
- */
- def findDeclarations(word: String, scope: Scope): (Seq[Occurrence], Seq[SearchFailure]) = {
- val query = new BooleanQuery()
- query.add(new TermQuery(Terms.isDeclaration), BooleanClause.Occur.MUST)
- query.add(new TermQuery(Terms.exactWord(word)), BooleanClause.Occur.MUST)
+ * Search the projects for all occurrence of the `word` that are found in declarations.
+ */
+ def findDeclarations(word: String, scope: Scope): (Seq[Occurrence], Seq[SearchFailure]) = {
+ val query = new BooleanQuery()
+ query.add(new TermQuery(Terms.isDeclaration), BooleanClause.Occur.MUST)
+ query.add(new TermQuery(Terms.exactWord(word)), BooleanClause.Occur.MUST)
- val resuts = queryScope(query, scope)
+ val resuts = queryScope(query, scope)
- groupSearchResults(resuts.seq)
- }
+ groupSearchResults(resuts.seq)
+ }
/**
* Search the relevant project indices for all occurrences of the given words.
@@ -165,8 +165,8 @@ trait Index extends HasLogger {
val innerQuery = new BooleanQuery()
for { w <- words } {
innerQuery.add(
- new TermQuery(Terms.exactWord(w)),
- BooleanClause.Occur.SHOULD)
+ new TermQuery(Terms.exactWord(w)),
+ BooleanClause.Occur.SHOULD)
}
query.add(innerQuery, BooleanClause.Occur.MUST)
@@ -177,9 +177,9 @@ trait Index extends HasLogger {
private def queryScope(query: BooleanQuery, scope: Scope): Set[(IScalaProject, Try[ArraySeq[Occurrence]])] = {
scope.projects.par.map { project =>
- val resultsForProject = withSearcher(project){ searcher =>
+ val resultsForProject = withSearcher(project) { searcher =>
for {
- hit <- searcher.search(query, MAX_POTENTIAL_MATCHES).scoreDocs
+ hit <- searcher.search(query, MAX_POTENTIAL_MATCHES).scoreDocs
occurrence <- fromDocument(searcher.doc(hit.doc)).right.toOption
} yield occurrence
}
@@ -189,11 +189,11 @@ trait Index extends HasLogger {
private def groupSearchResults(results: Set[(IScalaProject, Try[ArraySeq[Occurrence]])]): (Seq[Occurrence], Seq[SearchFailure]) = {
val initial: (Seq[Occurrence], Seq[SearchFailure]) = (Nil, Nil)
- results.foldLeft(initial) { (acc, t: (IScalaProject,Try[Seq[Occurrence]])) =>
+ results.foldLeft(initial) { (acc, t: (IScalaProject, Try[Seq[Occurrence]])) =>
val (occurrences, failures) = acc
t match {
case (_, Success(xs)) => (occurrences ++ xs, failures)
- case (p, Failure(_)) => (occurrences, BrokenIndex(p) +: failures)
+ case (p, Failure(_)) => (occurrences, BrokenIndex(p) +: failures)
}
}
}
@@ -210,7 +210,7 @@ trait Index extends HasLogger {
*/
def addOccurrences(occurrences: Seq[Occurrence], project: IScalaProject): Try[Unit] = {
doWithWriter(project) { writer =>
- val docs = occurrences.map( toDocument(project.underlying, _) )
+ val docs = occurrences.map(toDocument(project.underlying, _))
writer.addDocuments(docs.toIterable.asJava)
}
}
@@ -314,7 +314,7 @@ trait Index extends HasLogger {
def persist(key: String, value: String) =
doc.add(new Field(key, value,
- Field.Store.YES, Field.Index.NOT_ANALYZED))
+ Field.Store.YES, Field.Index.NOT_ANALYZED))
persist(WORD, o.word)
persist(PATH, o.file.workspaceFile.getProjectRelativePath().toPortableString())
@@ -323,6 +323,9 @@ trait Index extends HasLogger {
persist(LINE_CONTENT, o.lineContent)
persist(IS_IN_SUPER_POSITION, o.isInSuperPosition.toString)
persist(PROJECT_NAME, project.getName)
+ o.callerOffset.map { co =>
+ persist(CALLER_OFFSET, co.toString())
+ }
doc
}
@@ -338,7 +341,7 @@ trait Index extends HasLogger {
protected def fromDocument(doc: Document): Either[ConversionError, Occurrence] = {
def convertToBoolean(str: String) = str match {
- case "true" => true
+ case "true" => true
case "false" => false
case x =>
logger.debug(s"Expected true/false when converting document, but got $x")
@@ -347,21 +350,24 @@ trait Index extends HasLogger {
import LuceneFields._
(for {
- word <- Option(doc.get(WORD))
- path <- Option(doc.get(PATH))
- offset <- Option(doc.get(OFFSET))
+ word <- Option(doc.get(WORD))
+ path <- Option(doc.get(PATH))
+ offset <- Option(doc.get(OFFSET))
occurrenceKind <- Option(doc.get(OCCURRENCE_KIND))
- lineContent <- Option(doc.get(LINE_CONTENT))
- projectName <- Option(doc.get(PROJECT_NAME))
- isSuper <- Option(doc.get(IS_IN_SUPER_POSITION)).map(convertToBoolean)
+ lineContent <- Option(doc.get(LINE_CONTENT))
+ projectName <- Option(doc.get(PROJECT_NAME))
+ isSuper <- Option(doc.get(IS_IN_SUPER_POSITION)).map(convertToBoolean)
+
} yield {
val root = ResourcesPlugin.getWorkspace().getRoot()
(for {
- project <- Option(root.getProject(projectName))
- ifile <- Option(project.getFile(Path.fromPortableString(path)))
- file <- Util.scalaSourceFileFromIFile(ifile) if ifile.exists
+ project <- Option(root.getProject(projectName))
+ ifile <- Option(project.getFile(Path.fromPortableString(path)))
+ file <- Util.scalaSourceFileFromIFile(ifile) if ifile.exists
} yield {
- Occurrence(word, file, Integer.parseInt(offset), OccurrenceKind.fromString(occurrenceKind), lineContent, isSuper)
+ val ci: Option[Int] = Option(doc.get(CALLER_OFFSET)).map(Integer.parseInt(_))
+
+ Occurrence(word, file, Integer.parseInt(offset), OccurrenceKind.fromString(occurrenceKind), lineContent, isSuper, ci)
}).fold {
// The file or project apparently no longer exists. This can happen
// if the project/file has been deleted/renamed and a search is
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Occurrence.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Occurrence.scala
index 95ae67e..35c6900 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Occurrence.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/Occurrence.scala
@@ -20,13 +20,14 @@ case object Declaration extends OccurrenceKind
case object Reference extends OccurrenceKind
object LuceneFields {
- val WORD = "word"
- val PATH = "path"
- val OFFSET = "offset"
- val OCCURRENCE_KIND = "occurrenceKind"
- val PROJECT_NAME = "project"
- val LINE_CONTENT = "lineContent"
+ val WORD = "word"
+ val PATH = "path"
+ val OFFSET = "offset"
+ val OCCURRENCE_KIND = "occurrenceKind"
+ val PROJECT_NAME = "project"
+ val LINE_CONTENT = "lineContent"
val IS_IN_SUPER_POSITION = "isInSuperPosition"
+ val CALLER_OFFSET = "callerOffset"
}
/**
@@ -39,25 +40,27 @@ case class Occurrence(
offset: Int, // char offset from beginning of file
occurrenceKind: OccurrenceKind,
lineContent: String = "",
- isInSuperPosition: Boolean = false) {
+ isInSuperPosition: Boolean = false,
+ callerOffset: Option[Int] = None) {
override def equals(other: Any) = other match {
// Don't compare lineCOntent
case o: Occurrence =>
word == o.word &&
- file == o.file &&
- offset == o.offset &&
- occurrenceKind == o.occurrenceKind &&
- isInSuperPosition == o.isInSuperPosition
+ file == o.file &&
+ offset == o.offset &&
+ occurrenceKind == o.occurrenceKind &&
+ isInSuperPosition == o.isInSuperPosition &&
+ callerOffset == o.callerOffset
case _ => false
}
override def toString = "%s in %s at char %s %s".format(
- word,
- Util.getWorkspaceFile(file).map(_.getProjectRelativePath().toString()).getOrElse("File is deleted"),
- offset.toString,
- occurrenceKind.toString)
+ word,
+ Util.getWorkspaceFile(file).map(_.getProjectRelativePath().toString()).getOrElse("deleted file"),
+ offset.toString,
+ occurrenceKind.toString)
- def toHit = Hit(file, word, lineContent, offset)
+ def toHit = Hit(file, word, lineContent, offset, callerOffset)
}
\ No newline at end of file
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/OccurrenceCollector.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/OccurrenceCollector.scala
index 3cf6306..6405784 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/OccurrenceCollector.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/indexing/OccurrenceCollector.scala
@@ -50,10 +50,11 @@ object OccurrenceCollector extends HasLogger {
val traverser = new Traverser {
private var isSuper = false
+ private var ci:List[Int] = List()
override def traverse(t: Tree) {
// Avoid passing the same arguments all over.
- def mkOccurrence = Occurrence(_: String, file, t.pos.point, _: OccurrenceKind, t.pos.lineContent, isSuper)
+ def mkOccurrence = Occurrence(_: String, file, t.pos.point, _: OccurrenceKind, t.pos.lineContent, isSuper, ci.headOption)
t match {
@@ -67,16 +68,18 @@ object OccurrenceCollector extends HasLogger {
// Method definitions
case DefDef(mods, name, _, args, _, body) if !isSynthetic(pc)(t) =>
occurrences += mkOccurrence(name.decodedName.toString, Declaration)
+ ci = t.pos.point :: ci
traverseTrees(mods.annotations)
traverseTreess(args)
traverse(body)
-
+ ci = ci.tail
// Val's and arguments.
case ValDef(_, name, tpt, rhs) =>
occurrences += mkOccurrence(name.decodedName.toString, Declaration)
+ ci = t.pos.point :: ci
traverse(tpt)
traverse(rhs)
-
+ ci = ci.tail
// Class and Trait definitions
case ClassDef(_, name, _, Template(supers, ValDef(_,_,selfType,_), body)) =>
occurrences += mkOccurrence(name.decodedName.toString, Declaration)
@@ -93,7 +96,6 @@ object OccurrenceCollector extends HasLogger {
traverseTrees(supers)
isSuper = false
traverseTrees(body)
-
// Make sure that type arguments aren't listed as being in 'super-type' position
case AppliedTypeTree(tpe, args) =>
traverse(tpe)
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Finder.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Finder.scala
index 1cf2d72..678e772 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Finder.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Finder.scala
@@ -6,7 +6,6 @@ import scala.reflect.internal.util.SourceFile
import org.scalaide.core.IScalaPlugin
import org.scalaide.core.IScalaProject
import org.scalaide.logging.HasLogger
-
import org.eclipse.core.runtime.IProgressMonitor
import org.scala.tools.eclipse.search.Entity
import org.scala.tools.eclipse.search.ErrorHandlingOption
@@ -16,6 +15,9 @@ import org.scala.tools.eclipse.search.indexing.Index
import org.scala.tools.eclipse.search.indexing.Occurrence
import org.scala.tools.eclipse.search.indexing.SearchFailure
import scala.collection.mutable
+import scala.tools.nsc.interactive.Response
+import scala.reflect.internal.util.OffsetPosition
+import org.scalaide.core.compiler.IScalaPresentationCompiler
/**
* Component that provides various methods related to finding Scala entities.
@@ -65,9 +67,8 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger {
*
* Errors are passed to `errorHandler`.
*/
- def findSubtypes(entity: TypeEntity, scope: Scope, monitor: IProgressMonitor)
- (handler: Confidence[TypeEntity] => Unit,
- errorHandler: SearchFailure => Unit = _ => ()): Unit = {
+ def findSubtypes(entity: TypeEntity, scope: Scope, monitor: IProgressMonitor)(handler: Confidence[TypeEntity] => Unit,
+ errorHandler: SearchFailure => Unit = _ => ()): Unit = {
/*
* A short description of how we find the sub-types
@@ -90,7 +91,7 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger {
// Get the declaration that contains the given `hit`.
def getTypeEntity(hit: Hit): Option[TypeEntity] = {
- hit.cu.withSourceFile { (sf,pc) =>
+ hit.cu.withSourceFile { (sf, pc) =>
val spc = new SearchPresentationCompiler(pc)
val maybeEntity = spc.declarationContaining(Location(hit.cu, hit.offset)).right.toOption.flatten
//TODO: Report error
@@ -106,7 +107,7 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger {
// Given a hit where the `entity` is used in a super-type position
// find the declaration that contains it and return it.
def onHit(hit: Confidence[Hit]): Unit = hit match {
- case Certain(hit) => getTypeEntity(hit) map Certain.apply foreach handler
+ case Certain(hit) => getTypeEntity(hit) map Certain.apply foreach handler
case Uncertain(hit) => getTypeEntity(hit) map Uncertain.apply foreach handler
}
@@ -126,9 +127,8 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger {
*
* Errors are passed to `errorHandler`.
*/
- def occurrencesOfEntityAt(entity: Entity, scope: Scope, monitor: IProgressMonitor)
- (handler: Confidence[Hit] => Unit,
- errorHandler: SearchFailure => Unit = _ => ()): Unit = {
+ def occurrencesOfEntityAt(entity: Entity, scope: Scope, monitor: IProgressMonitor)(handler: Confidence[Hit] => Unit,
+ errorHandler: SearchFailure => Unit = _ => ()): Unit = {
val names = entity.alternativeNames
val (occurrences, failures) = index.findOccurrences(names, scope)
@@ -152,12 +152,46 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger {
monitor.subTask(s"Checking ${occurrence.file.file.name}")
val loc = Location(occurrence.file, occurrence.offset)
entity.isReference(loc) match {
- case Same => handler(Certain(occurrence.toHit))
+ case Same => handler(Certain(occurrence.toHit))
case PossiblySame => handler(Uncertain(occurrence.toHit))
- case NotSame => logger.debug(s"$occurrence wasn't the same.")
+ case NotSame => logger.debug(s"$occurrence wasn't the same.")
}
monitor.worked(1)
}
}
+
+ def findCallers(e: Entity, scope: Scope, handler: (Hit, Entity, String, Option[String], IScalaProject) => Unit, monitor: IProgressMonitor): Unit = {
+ def getLabel(pos:OffsetPosition, pc: IScalaPresentationCompiler):Option[(String,Option[String])] ={
+ pc.askTypeAt(pos).get match {
+ case Left(tree) =>
+ pc.asyncExec {
+ val topClass = pc.headerForSymbol(tree.symbol.enclosingTopLevelClass, tree.tpe)
+ (pc.declPrinter.defString(tree.symbol)(), topClass)
+ }.get match {
+ case Left(si) => Some(si)
+ case _ => None
+ }
+ case _ => None
+ }
+ }
+ occurrencesOfEntityAt(e, scope, monitor)(
+
+ c => c match {
+ case c@Certain(Hit(cu, _, _, _, Some(co))) =>
+ val loc = Location(cu, co)
+ entityAt(loc) match {
+ case Right(Some(entity)) =>
+
+ cu.withSourceFile { (sf, pc) =>
+ val pos = new OffsetPosition(sf, loc.offset)
+ getLabel(pos, pc).map { l => handler(c.value, entity, l._1, l._2, cu.scalaProject) }
+ }
+
+ case _ =>
+ }
+ case _ =>
+ },
+ f => {})
+ }
}
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Hit.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Hit.scala
index 1358da4..6d5b139 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Hit.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/searching/Hit.scala
@@ -2,4 +2,4 @@ package org.scala.tools.eclipse.search.searching
import org.scalaide.core.compiler.InteractiveCompilationUnit
-case class Hit(cu: InteractiveCompilationUnit, word: String, lineContent: String, offset: Int)
\ No newline at end of file
+case class Hit(cu: InteractiveCompilationUnit, word: String, lineContent: String, offset: Int, callerOffset:Option[Int])
\ No newline at end of file
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/CallHierarchyView.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/CallHierarchyView.scala
new file mode 100644
index 0000000..40f3ad7
--- /dev/null
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/CallHierarchyView.scala
@@ -0,0 +1,202 @@
+package org.scala.tools.eclipse.search.ui
+
+import org.eclipse.ui.part.ViewPart
+import org.eclipse.swt.widgets.Composite
+import org.eclipse.jface.viewers.TreeViewer
+import org.eclipse.swt.widgets.Tree
+import org.eclipse.swt.SWT
+import org.eclipse.swt.layout.GridData
+import org.eclipse.jface.viewers.ITreeContentProvider
+import org.eclipse.jface.viewers.ILabelProvider
+import org.eclipse.jface.action.Action
+import org.eclipse.jface.action.IAction._
+import CallHierarchyView._
+import org.eclipse.jdt.internal.ui.JavaPluginImages
+import org.eclipse.jface.action.Separator
+import java.beans.PropertyChangeSupport
+import org.eclipse.core.databinding.beans.BeanProperties
+import org.eclipse.jface.databinding.viewers.ViewerSupport
+import java.beans.PropertyChangeListener
+import org.eclipse.swt.events.TreeListener
+import org.eclipse.swt.events.TreeEvent
+import org.eclipse.jface.databinding.viewers.ViewersObservables
+import org.eclipse.jface.internal.databinding.viewers.SelectionChangedListener
+import org.eclipse.jface.viewers.ISelectionChangedListener
+import org.eclipse.jface.viewers.SelectionChangedEvent
+import org.eclipse.jface.viewers.IStructuredSelection
+import org.scala.tools.eclipse.search.searching.Location
+import org.scala.tools.eclipse.search.Util
+import org.eclipse.ui.part.FileEditorInput
+import org.eclipse.ui.ide.IDE
+import org.eclipse.jdt.internal.ui.JavaPlugin
+import org.scalaide.ui.editor.InteractiveCompilationUnitEditor
+import org.scala.tools.eclipse.search.Entity
+import org.scala.tools.eclipse.search.searching.Finder
+import org.scala.tools.eclipse.search.SearchPlugin
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.core.runtime.IProgressMonitor
+import org.scala.tools.eclipse.search.searching.Scope
+import org.eclipse.core.runtime.IStatus
+import org.eclipse.core.runtime.Status
+import org.eclipse.ui.part.PageBook
+import org.eclipse.swt.widgets.TreeColumn
+import org.eclipse.core.databinding.beans.IBeanValueProperty
+import org.eclipse.core.databinding.property.value.IValueProperty
+import org.scalaide.core.IScalaProject
+import org.scala.tools.eclipse.search.searching.Hit
+import org.eclipse.swt.events.ControlListener
+import org.eclipse.swt.events.ControlAdapter
+import org.eclipse.swt.events.ControlEvent
+
+class CallHierarchyView extends ViewPart {
+ import CallHierarchyView._
+ val input = RootNode()
+ private var treeViewer: TreeViewer = _
+ private val finder: Finder = SearchPlugin.finder
+ override def createPartControl(parent: Composite) = {
+ def createViewer(): TreeViewer = {
+
+ class WidthManager(tc:TreeColumn, name:String) extends ControlAdapter{
+ val cname = VIEW_ID+".TreeViewer.Columns.Width"+name
+ SearchPlugin().getPreferenceStore.setDefault(cname, 200)
+ tc.setWidth(SearchPlugin().getPreferenceStore.getInt(cname))
+ tc.addControlListener(this)
+ override def controlResized(e:ControlEvent)={
+ SearchPlugin().getPreferenceStore.setValue(cname, math.max(tc.getWidth, 1))
+ }
+ }
+
+ val tree = new Tree(parent, SWT.MULTI)
+ tree.setHeaderVisible(true)
+ val c1 = new TreeColumn(tree, SWT.LEFT)
+ c1.setText("Caller")
+ new WidthManager(c1,"c1")
+
+ val c2 = new TreeColumn(tree, SWT.LEFT)
+ c2.setText("Class")
+ new WidthManager(c2,"c2")
+
+ val c3 = new TreeColumn(tree, SWT.LEFT)
+ c3.setText("Content")
+ new WidthManager(c3,"c3")
+
+ val c4 = new TreeColumn(tree, SWT.LEFT)
+ c4.setText("Project")
+ new WidthManager(c4,"c4")
+
+ val tv = new TreeViewer(tree)
+ tv.getControl().setLayoutData(new GridData(GridData.FILL_BOTH))
+ tv.setUseHashlookup(true)
+ tv.setAutoExpandLevel(2)
+ tv.getTree.addTreeListener(new TreeListener() {
+ override def treeCollapsed(e: TreeEvent) {}
+
+ override def treeExpanded(e: TreeEvent) {
+ e.item.getData match {
+ case n: CallerNode =>
+ if (!n.list.isEmpty && n.list.head.isInstanceOf[QueryNode]) {
+ n.list = List()
+ val job = new Job("Searching for callers...") {
+ override def run(monitor: IProgressMonitor): IStatus = {
+ finder.findCallers(n.caller, n.scope, (offset, e, label, owner, project) => n.list = CallerNode(offset, e, n.scope, label, owner, project) :: n.list, monitor)
+ Status.OK_STATUS
+ }
+ }
+ job.schedule()
+ }
+ case _ =>
+ }
+ }
+ })
+ tv.addSelectionChangedListener(new ISelectionChangedListener() {
+ import org.scalaide.util.Utils.WithAsInstanceOfOpt
+ override def selectionChanged(event: SelectionChangedEvent) = {
+ val cn = treeViewer.getSelection.asInstanceOf[IStructuredSelection].getFirstElement
+ cn match {
+ case CallerNode(hit, e, scope, label, owner, project) =>
+ for {
+ loc <- e.location
+ file <- Util.getWorkspaceFile(loc.cu)
+ val input = new FileEditorInput(file)
+ desc <- Option(IDE.getEditorDescriptor(file.getName()))
+ page <- Option(JavaPlugin.getActivePage)
+ part <- Option(IDE.openEditor(page, input, desc.getId()))
+ editor <- part.asInstanceOfOpt[InteractiveCompilationUnitEditor]
+ } {
+ editor.selectAndReveal(hit.offset, hit.word.length)
+ setFocus()
+ }
+
+ case _ =>
+ }
+ }
+ })
+ tv
+ }
+
+ treeViewer = createViewer()
+ fillViewMenu()
+ fillActionBars()
+ createDataBindings
+ }
+
+ private def createDataBindings = {
+ val propNames: Array[IValueProperty] = BeanProperties.values(Array("label", "owner","line", "project")).map { x => x.asInstanceOf[IValueProperty] }
+ ViewerSupport.bind(treeViewer, input,
+ BeanProperties.list("list", classOf[Node]),
+ propNames)
+ }
+
+ private def fillActionBars() = {
+ val actionBars = getViewSite().getActionBars()
+ val toolBar = actionBars.getToolBarManager()
+
+ }
+
+ override def setFocus() = {
+ treeViewer.getTree.setFocus()
+ }
+
+ private def fillViewMenu() = {
+ val actionBars = getViewSite().getActionBars()
+ val viewMenu = actionBars.getMenuManager()
+ viewMenu.add(new Separator())
+ }
+
+}
+
+object CallHierarchyView {
+ val VIEW_ID = "org.scala.tools.eclipse.search.ui.CallHierarchyView"
+}
+
+
+abstract class Node(label: String) {
+ import scala.collection.JavaConversions._
+ private val changeSupport = new PropertyChangeSupport(this)
+ protected var list_ : List[Node] = List()
+
+ def getLabel = label
+
+ def getList: java.util.List[Node] = seqAsJavaList(list_)
+
+ def list = list_
+ def list_=(list: List[Node]) = changeSupport.firePropertyChange("list", seqAsJavaList(this.list), seqAsJavaList({ this.list_ = list; list }))
+ def addPropertyChangeListener(listener: PropertyChangeListener) =
+ changeSupport.addPropertyChangeListener(listener)
+
+ def removePropertyChangeListener(listener: PropertyChangeListener) =
+ changeSupport.removePropertyChangeListener(listener)
+
+}
+
+case class RootNode() extends Node("")
+case class QueryNode() extends Node("Searching...") {
+ override def list_=(list: List[Node]) = {}
+}
+
+case class CallerNode(hit:Hit, caller: Entity, scope: Scope, label: String, owner: Option[String], project: IScalaProject) extends Node(label) {
+ def getOwner = owner.map(_.toString).getOrElse("")
+ def getProject = project.underlying.getName
+ def getLine = hit.lineContent.trim
+ list_ = List(QueryNode())
+}
diff --git a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/ResultLabelProvider.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/ResultLabelProvider.scala
index 65ce3bd..b381358 100644
--- a/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/ResultLabelProvider.scala
+++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/ResultLabelProvider.scala
@@ -40,11 +40,11 @@ class ResultLabelProvider extends StyledCellLabelProvider with HasLogger {
text.append(" (%s)".format(count), StyledString.COUNTER_STYLER)
cell.setImage(ScalaImages.SCALA_FILE.createImage())
- case LineNode(Certain(Hit(_,_,line, _))) =>
+ case LineNode(Certain(Hit(_,_,line, _, _))) =>
val styled = new StyledString(line.trim)
text.append(styled)
- case LineNode(Uncertain(Hit(_,_,line,_))) =>
+ case LineNode(Uncertain(Hit(_,_,line,_,_))) =>
val styled = new StyledString(line.trim)
text.append(styled)
text.append(" - Potential match", StyledString.QUALIFIER_STYLER)