From 067ed7600a82e5c6ba2ef7e58298b591a18797d1 Mon Sep 17 00:00:00 2001 From: ailinykh Date: Tue, 15 Dec 2015 22:17:23 -0800 Subject: [PATCH 1/2] create Call Hierarchy View. It is written o the top of Find Occurrences API. One field (caller offset) is added to lucent index. This field is used to build the hierarchy. Fix #1000872 --- .../META-INF/MANIFEST.MF | 5 +- org.scala.tools.eclipse.search/plugin.xml | 21 ++ .../tools/eclipse/search/SearchPlugin.scala | 4 +- .../search/handlers/OpenCallHierarchy.scala | 91 ++++++++ .../tools/eclipse/search/indexing/Index.scala | 68 +++--- .../eclipse/search/indexing/Occurrence.scala | 35 +-- .../search/indexing/OccurrenceCollector.scala | 10 +- .../eclipse/search/searching/Finder.scala | 74 +++++-- .../tools/eclipse/search/searching/Hit.scala | 2 +- .../eclipse/search/ui/CallHierarchyView.scala | 202 ++++++++++++++++++ .../search/ui/ResultLabelProvider.scala | 4 +- 11 files changed, 445 insertions(+), 71 deletions(-) create mode 100644 org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/handlers/OpenCallHierarchy.scala create mode 100644 org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/ui/CallHierarchyView.scala diff --git a/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF b/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF index 789dee9..63ec660 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;bundle-version="1.2.200", + org.eclipse.jface.databinding;bundle-version="1.6.200", + org.eclipse.core.databinding.property;bundle-version="1.4.200" 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..5c5062a 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) 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..992d346 --- /dev/null +++ b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/handlers/OpenCallHierarchy.scala @@ -0,0 +1,91 @@ +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() + + } + + val 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 e = finder.entityAt(loc) + val root = thview.input + root.list = List() + e 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("Sorry, couldn't get the symbol of the given entity.\n\n" + + "This is very likely a bug, so please submit a bug report that contains\n" + + "a minimal example to https://www.assembla.com/spaces/scala-ide/tickets\n\n" + + "Thank you! - IDE Team") + } + } + + 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..ae310ec 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("File is deleted"), + 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..effbc2d 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..7758c49 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) @@ -143,21 +143,65 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger { // The 'monitor' is needed to make it possible to cancel it and // report progress. private def processSame( - entity: Entity, - occurrences: Seq[Occurrence], - monitor: IProgressMonitor, - handler: Confidence[Hit] => Unit): Unit = { + entity: Entity, + occurrences: Seq[Occurrence], + monitor: IProgressMonitor, + handler: Confidence[Hit] => Unit): Unit = { for { occurrence <- occurrences if !monitor.isCanceled } { 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 getSymbolInfo(loc: Location): String = { + loc.cu.withSourceFile { (sf, pc) => + val typed = new Response[pc.Tree] + val pos = new OffsetPosition(sf, loc.offset) + pc.askTypeAt(pos, typed) + "" + + }.getOrElse("") + } + + 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) From d6df8a2b7204aefdb1ff4d407263972f707de2f0 Mon Sep 17 00:00:00 2001 From: ailinykh Date: Wed, 16 Dec 2015 22:39:33 -0800 Subject: [PATCH 2/2] remove unused code, fix formatting --- .../META-INF/MANIFEST.MF | 6 +++--- .../tools/eclipse/search/SearchPlugin.scala | 1 + .../search/handlers/OpenCallHierarchy.scala | 13 ++++++------- .../eclipse/search/indexing/Occurrence.scala | 2 +- .../search/indexing/OccurrenceCollector.scala | 4 ++-- .../eclipse/search/searching/Finder.scala | 18 ++++-------------- 6 files changed, 17 insertions(+), 27 deletions(-) diff --git a/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF b/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF index 63ec660..071b3dd 100644 --- a/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF +++ b/org.scala.tools.eclipse.search/META-INF/MANIFEST.MF @@ -28,9 +28,9 @@ Require-Bundle: 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.core.databinding.beans;bundle-version="1.2.200", - org.eclipse.jface.databinding;bundle-version="1.6.200", - org.eclipse.core.databinding.property;bundle-version="1.4.200" + 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/src/org/scala/tools/eclipse/search/SearchPlugin.scala b/org.scala.tools.eclipse.search/src/org/scala/tools/eclipse/search/SearchPlugin.scala index 5c5062a..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 @@ -58,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 index 992d346..2b72947 100644 --- 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 @@ -48,7 +48,7 @@ class OpenCallHierarchy extends AbstractHandler with HasLogger { } - val view = PlatformUI.getWorkbench() + def view = PlatformUI.getWorkbench() .getActiveWorkbenchWindow() .getActivePage() .showView(CallHierarchyView.VIEW_ID) @@ -65,10 +65,9 @@ class OpenCallHierarchy extends AbstractHandler with HasLogger { val scope = Scope(projects.map(IScalaPlugin().asScalaProject(_)).flatten) val loc = Location(scalaEditor.getInteractiveCompilationUnit, selection.getOffset()) - val e = finder.entityAt(loc) val root = thview.input root.list = List() - e match { + finder.entityAt(loc) match { case Right(Some(entity: Method)) => scheduleJob(entity, scope, root) case Right(Some(entity: Val)) => @@ -78,10 +77,10 @@ class OpenCallHierarchy extends AbstractHandler with HasLogger { 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("Sorry, couldn't get the symbol of the given entity.\n\n" + - "This is very likely a bug, so please submit a bug report that contains\n" + - "a minimal example to https://www.assembla.com/spaces/scala-ide/tickets\n\n" + - "Thank you! - IDE Team") + 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) } } 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 ae310ec..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 @@ -57,7 +57,7 @@ case class Occurrence( override def toString = "%s in %s at char %s %s".format( word, - Util.getWorkspaceFile(file).map(_.getProjectRelativePath().toString()).getOrElse("File is deleted"), + Util.getWorkspaceFile(file).map(_.getProjectRelativePath().toString()).getOrElse("deleted file"), offset.toString, occurrenceKind.toString) 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 effbc2d..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 @@ -72,14 +72,14 @@ object OccurrenceCollector extends HasLogger { traverseTrees(mods.annotations) traverseTreess(args) traverse(body) - ci= ci.tail + 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 + ci = ci.tail // Class and Trait definitions case ClassDef(_, name, _, Template(supers, ValDef(_,_,selfType,_), body)) => occurrences += mkOccurrence(name.decodedName.toString, Declaration) 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 7758c49..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 @@ -143,10 +143,10 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger { // The 'monitor' is needed to make it possible to cancel it and // report progress. private def processSame( - entity: Entity, - occurrences: Seq[Occurrence], - monitor: IProgressMonitor, - handler: Confidence[Hit] => Unit): Unit = { + entity: Entity, + occurrences: Seq[Occurrence], + monitor: IProgressMonitor, + handler: Confidence[Hit] => Unit): Unit = { for { occurrence <- occurrences if !monitor.isCanceled } { monitor.subTask(s"Checking ${occurrence.file.file.name}") @@ -159,16 +159,6 @@ class Finder(index: Index, reporter: ErrorReporter) extends HasLogger { monitor.worked(1) } } - - def getSymbolInfo(loc: Location): String = { - loc.cu.withSourceFile { (sf, pc) => - val typed = new Response[pc.Tree] - val pos = new OffsetPosition(sf, loc.offset) - pc.askTypeAt(pos, typed) - "" - - }.getOrElse("") - } 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])] ={