Improve navigation by Hoogle.

This commit is contained in:
Rik van der Kleij 2019-08-04 18:16:54 +02:00
parent 28364e601d
commit fe02b403f7
5 changed files with 50 additions and 81 deletions

View File

@ -171,4 +171,5 @@ private[component] object BrowseModuleComponent {
}
}
// value of name is without (operator) parens
case class ModuleIdentifier(name: String, moduleName: String, declaration: String, isOperator: Boolean)

View File

@ -47,7 +47,7 @@ object DeclarationLineUtil {
None
}
}
name.map(n => NameAndShortDeclaration(n, declaration))
name.map(n => NameAndShortDeclaration(StringUtil.removeOuterParens(n), declaration))
}
}

View File

@ -142,7 +142,7 @@ private[component] object DefinitionLocationComponent {
HaskellReference.findIdentifiersByNameInfo(info, key.qualifiedNameElement.getIdentifierElement, project) match {
case Right((mn, ne, pn)) => Right(PackageModuleLocation(findModuleName(ne), ne, name, pn))
case Left(noInfo) =>
HaskellReference.findIdentifierInFileByName(psiFile, name).
HaskellReference.findIdentifierInFileByName(psiFile, name, prioIdInExpression = true).
map(ne => Right(PackageModuleLocation(findModuleName(ne), ne, name, None))).getOrElse(Left(noInfo))
}
case None => Left(NoInfoAvailable(name, psiFile.getName))
@ -234,7 +234,7 @@ private[component] object DefinitionLocationComponent {
case Some(r) => r
case None => Left(NoInfoAvailable(name, key.psiFile.getName))
}
case Left(NoMatchingExport) => HaskellReference.findIdentifierInFileByName(psiFile, name) match {
case Left(NoMatchingExport) => HaskellReference.findIdentifierInFileByName(psiFile, name, prioIdInExpression = true) match {
case Some(ne) => Right(LocalModuleLocation(psiFile, ne, name))
case None => Left(NoInfoAvailable(name, psiFile.getName))
}
@ -263,7 +263,7 @@ private[component] object DefinitionLocationComponent {
case Right(files) =>
ProgressManager.checkCanceled()
files.headOption.flatMap(HaskellReference.findIdentifierInFileByName(_, name)) match {
files.headOption.flatMap(HaskellReference.findIdentifierInFileByName(_, name, prioIdInExpression = true)) match {
case Some(e) => Right(PackageModuleLocation(mn, e, name, Some(pn)))
case None => Left(NoInfoAvailable(name, key.psiFile.getName))
}

View File

@ -205,24 +205,18 @@ object HaskellReference {
}
def findIdentifiersByLibraryNameInfo(project: Project, libraryNameInfo: LibraryNameInfo, name: String): Either[NoInfo, (String, HaskellNamedElement)] = {
findIdentifiersByModuleAndName(project, libraryNameInfo.moduleName, name)
findIdentifiersByModulesAndName(project, Seq(libraryNameInfo.moduleName), name)
}
def findIdentifiersByModuleAndName(project: Project, moduleName: String, name: String): Either[NoInfo, (String, HaskellNamedElement)] = {
def findIdentifiersByModulesAndName(project: Project, moduleNames: Seq[String], name: String, prioIdInExpression: Boolean = true): Either[NoInfo, (String, HaskellNamedElement)] = {
ProgressManager.checkCanceled()
findIdentifiersByModulesAndName(project, Seq(moduleName), name)
}
def findIdentifiersByModulesAndName(project: Project, moduleNames: Seq[String], name: String): Either[NoInfo, (String, HaskellNamedElement)] = {
ProgressManager.checkCanceled()
findIdentifiersByModuleAndName2(project, moduleNames, name).headOption.map {
findIdentifiersByModuleAndName2(project, moduleNames, name, prioIdInExpression).headOption.map {
case (mn, nes) => nes.headOption.map(ne => Right((mn, ne))).getOrElse(Left(NoInfoAvailable(name, moduleNames.mkString(" | "))))
}.getOrElse(Left(ModuleNotAvailable(moduleNames.mkString(" | "))))
}
private def findIdentifiersByModuleAndName2(project: Project, moduleNames: Seq[String], name: String): Seq[(String, Seq[HaskellNamedElement])] = {
private def findIdentifiersByModuleAndName2(project: Project, moduleNames: Seq[String], name: String, prioIdInExpression: Boolean): Seq[(String, Seq[HaskellNamedElement])] = {
ProgressManager.checkCanceled()
moduleNames.distinct.flatMap(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn) match {
@ -230,13 +224,13 @@ object HaskellReference {
case Right(files) =>
ProgressManager.checkCanceled()
val identifiers = files.flatMap(f => findIdentifierInFileByName(f, name))
val identifiers = files.flatMap(f => findIdentifierInFileByName(f, name, prioIdInExpression))
if (identifiers.isEmpty) {
val importedModuleNames = files.flatMap(f => FileModuleIdentifiers.findAvailableModuleIdentifiers(f).filter(_.name == name).map(_.moduleName))
val importedModuleNames = files.flatMap(f => FileModuleIdentifiers.findAvailableModuleIdentifiers(f).filter(mid => mid.name == name || mid.name == "_" + name).map(_.moduleName))
if (importedModuleNames.isEmpty) {
Seq()
} else {
findIdentifiersByModuleAndName2(project, importedModuleNames, name)
findIdentifiersByModuleAndName2(project, importedModuleNames, name, prioIdInExpression)
}
} else {
Seq((mn, identifiers))
@ -244,20 +238,22 @@ object HaskellReference {
})
}
def findIdentifierInFileByName(psifile: PsiFile, name: String): Option[HaskellNamedElement] = {
def findIdentifierInFileByName(psiFile: PsiFile, name: String, prioIdInExpression: Boolean): Option[HaskellNamedElement] = {
ProgressManager.checkCanceled()
def findIdInExpressions = {
ProgressManager.checkCanceled()
val topLevelExpressions = HaskellPsiUtil.findTopLevelExpressions(psifile)
val topLevelExpressions = HaskellPsiUtil.findTopLevelExpressions(psiFile)
ProgressManager.checkCanceled()
ProgressManager.checkCanceled()
val expressionIdentifiers = topLevelExpressions.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement)).find(_.getName == name)
topLevelExpressions.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement)).find(_.getName == name)
}
ProgressManager.checkCanceled()
def findIdInDeclarations = {
ProgressManager.checkCanceled()
if (expressionIdentifiers.isEmpty) {
val declarationElements = HaskellPsiUtil.findHaskellDeclarationElements(psifile)
val declarationElements = HaskellPsiUtil.findHaskellDeclarationElements(psiFile)
ProgressManager.checkCanceled()
@ -266,8 +262,22 @@ object HaskellReference {
ProgressManager.checkCanceled()
declarationIdentifiers.toSeq.sortWith(sortByClassDeclarationFirst).headOption
}
if (prioIdInExpression) {
val expressionIdentifiers = findIdInExpressions
if (expressionIdentifiers.isEmpty) {
findIdInDeclarations
} else {
expressionIdentifiers
}
} else {
expressionIdentifiers
val declarationIdentifiers = findIdInDeclarations
if (declarationIdentifiers.isEmpty) {
findIdInExpressions
} else {
declarationIdentifiers
}
}
}

View File

@ -19,11 +19,9 @@ package intellij.haskell.navigation
import com.intellij.navigation.{ChooseByNameContributor, ItemPresentation, NavigationItem}
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import intellij.haskell.external.component.NameInfoComponentResult.{LibraryNameInfo, ProjectNameInfo}
import intellij.haskell.external.component._
import intellij.haskell.psi.{HaskellDeclarationElement, HaskellPsiUtil}
import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.index.HaskellModuleNameIndex
import intellij.haskell.util.{HaskellFileUtil, StringUtil}
import javax.swing.Icon
class HoogleByNameContributor extends ChooseByNameContributor {
@ -37,10 +35,6 @@ class HoogleByNameContributor extends ChooseByNameContributor {
}
override def getItemsByName(name: String, pattern: String, project: Project, includeNonProjectItems: Boolean): Array[NavigationItem] = {
def NotFoundResult(moduleName: String, declaration: String): Seq[NotFoundNavigationItem] = {
Seq(NotFoundNavigationItem(declaration, Some(moduleName)))
}
val hooglePattern =
if (includeNonProjectItems) {
pattern
@ -50,7 +44,7 @@ class HoogleByNameContributor extends ChooseByNameContributor {
ProgressManager.checkCanceled()
val navigationItems = HoogleComponent.runHoogle(project, hooglePattern, count = 25).getOrElse(Seq()).flatMap {
val navigationItems: Seq[NavigationItem] = HoogleComponent.runHoogle(project, hooglePattern, count = 25).getOrElse(Seq()).flatMap {
case ModulePattern(moduleName) =>
ProgressManager.checkCanceled()
HaskellModuleNameIndex.findFilesByModuleName(project, moduleName) match {
@ -59,42 +53,25 @@ class HoogleByNameContributor extends ChooseByNameContributor {
}
case PackagePattern(packageName) =>
ProgressManager.checkCanceled()
Seq(NotFoundNavigationItem(packageName))
Seq()
case DeclarationPattern(moduleName, declaration) =>
ProgressManager.checkCanceled()
DeclarationLineUtil.findName(declaration).toSeq.flatMap(nd => {
val name = StringUtil.removeOuterParens(nd.name)
ProgressManager.checkCanceled()
val result = HaskellComponentsManager.findNameInfoByModuleName(project, moduleName, name)
ProgressManager.checkCanceled()
val navigationItemByNameInfo = result.toOption.flatMap(_.headOption) match {
case Some(lni: LibraryNameInfo) => HaskellReference.findIdentifiersByLibraryNameInfo(project, lni, name).toOption.
flatMap(x => HaskellPsiUtil.findDeclarationElement(x._2)).map(d => createLibraryNavigationItem(d, moduleName)).toSeq
case Some(pni: ProjectNameInfo) =>
HaskellFileUtil.findFileInRead(project, pni.filePath) match {
case (Some(virtualFile), Right(psiFile)) => HaskellReference.findIdentifierByLocation(project, virtualFile, psiFile, pni.lineNr, pni.columnNr, name).flatMap(HaskellPsiUtil.findDeclarationElement).toSeq
case (_, _) => Seq()
}
case _ => Seq()
}
ProgressManager.checkCanceled()
if (navigationItemByNameInfo.nonEmpty) {
navigationItemByNameInfo
} else {
val identifiers = HaskellReference.findIdentifiersByModuleAndName(project, moduleName, name).toOption.map(_._2).toSeq
DeclarationLineUtil.findName(declaration) match {
case Some(nameAndShortDeclaration) =>
val identifiers = HaskellReference.findIdentifiersByModulesAndName(project, Seq(moduleName), nameAndShortDeclaration.name, prioIdInExpression = false).toOption.map(_._2).toSeq
if (identifiers.isEmpty) {
NotFoundResult(moduleName, declaration)
Seq()
} else {
identifiers.flatMap(e => HaskellPsiUtil.findDeclarationElement(e))
identifiers.map(e => HaskellPsiUtil.findDeclarationElement(e).getOrElse(e)).map(d => createLibraryNavigationItem(d, moduleName))
}
}
})
case None => Seq()
}
case d =>
ProgressManager.checkCanceled()
Seq(NotFoundNavigationItem(d))
Seq()
}
navigationItems.groupBy(_.getPresentation.getLocationString).flatMap(_._2.headOption).zipWithIndex.map({ case (item, i) => new NavigationItem {
navigationItems.zipWithIndex.map({ case (item, i) => new NavigationItem {
// Hack to display items in same order as given by Hoogle
override def getName: String = {
@ -112,7 +89,7 @@ class HoogleByNameContributor extends ChooseByNameContributor {
}).toArray
}
private def createLibraryNavigationItem(namedElement: HaskellDeclarationElement, moduleNameFromHoogle: String): NavigationItem = {
private def createLibraryNavigationItem(namedElement: NavigationItem, moduleNameFromHoogle: String): NavigationItem = {
new NavigationItem {
override def getName: String = namedElement.getName
@ -132,23 +109,4 @@ class HoogleByNameContributor extends ChooseByNameContributor {
override def canNavigateToSource: Boolean = namedElement.canNavigateToSource
}
}
case class NotFoundNavigationItem(declaration: String, moduleName: Option[String] = None) extends NavigationItem {
override def getName: String = declaration
override def getPresentation: ItemPresentation = new ItemPresentation {
override def getIcon(unused: Boolean): Icon = null
override def getLocationString: String = moduleName.getOrElse("")
override def getPresentableText: String = getName
}
override def canNavigateToSource: Boolean = false
override def canNavigate: Boolean = false
override def navigate(requestFocus: Boolean): Unit = ()
}
}