Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create Call Hierarchy View. #111

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion org.scala.tools.eclipse.search/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions org.scala.tools.eclipse.search/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
name="Type Hierarchy"
class="org.scala.tools.eclipse.search.ui.TypeHierarchyView"
icon="icons/type-hierarchy-scala.gif" />
<view
category="org.scala-ide.sdt.core.viewCategory"
class="org.scala.tools.eclipse.search.ui.CallHierarchyView"
id="org.scala.tools.eclipse.search.ui.CallHierarchyView"
name="Call Hierarchy"
restorable="true">
</view>
</extension>

<!-- Search pages extension point -->
Expand Down Expand Up @@ -54,6 +61,12 @@
description="Open Type Hierarchy"
categoryId="org.scala.tools.eclipse.search.commands"
id="org.scala.tools.eclipse.search.commands.OpenTypeHierarchy"/>
<command
categoryId="org.scala.tools.eclipse.search.commands"
description="Open Call Hierarchy"
id="org.scala.tools.eclipse.search.commands.OpenCallHierarchy"
name="Open Call Hierarchy">
</command>
</extension>

<!-- Handlers -->
Expand All @@ -64,6 +77,9 @@
<handler
class="org.scala.tools.eclipse.search.handlers.OpenTypeHierarchy"
commandId="org.scala.tools.eclipse.search.commands.OpenTypeHierarchy" />
<handler
class="org.scala.tools.eclipse.search.handlers.OpenCallHierarchy"
commandId="org.scala.tools.eclipse.search.commands.OpenCallHierarchy" />
</extension>

<!-- Menus -->
Expand All @@ -79,6 +95,11 @@
id="org.scala.tools.eclipse.search.menus.typehiearchy"
label="Open Type Hierarchy"
tooltip="" />
<command
commandId="org.scala.tools.eclipse.search.commands.OpenCallHierarchy"
id="org.scala.tools.eclipse.search.menus.callhiearchy"
label="Open Call Hierarchy">
</command>
</menuContribution>
</extension>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you introduced apply, I would make finder private.

}

class SearchPlugin extends AbstractUIPlugin with HasLogger {
Expand All @@ -28,7 +30,7 @@ class SearchPlugin extends AbstractUIPlugin with HasLogger {

override def start(context: BundleContext) {
super.start(context)

SearchPlugin.plugin = this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should also set plugin to null in the stop method.

val reporter = new DialogErrorReporter
val index = new Index {
override val base = getStateLocation().append(INDEX_DIR_NAME)
Expand Down Expand Up @@ -56,6 +58,7 @@ class SearchPlugin extends AbstractUIPlugin with HasLogger {
indexManager.shutdown()
indexManager = null
SearchPlugin.finder = null
SearchPlugin.plugin = null
}

}
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand All @@ -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)
}

Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand All @@ -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
}
Expand All @@ -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)
}
}
}
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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())
Expand All @@ -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
}
Expand All @@ -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")
Expand All @@ -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
Expand Down
Loading