From 17a55b3a812e3da0ed71c74c8d80607c032e016f Mon Sep 17 00:00:00 2001
From: Rik van der Kleij
Date: Sun, 4 Nov 2018 18:43:23 +0100
Subject: [PATCH] Various improvements and some decoupling from stack commands.
---
intellij-haskell/META-INF/plugin.xml | 2 +-
.../intellij/haskell/action/AboutAction.scala | 8 +-
.../action/HindentReformatAction.scala | 2 +-
.../action/StylishHaskellReformatAction.scala | 4 +-
.../haskell/annotator/HaskellAnnotator.scala | 14 ++-
.../intellij/haskell/cabal/CabalInfo.scala | 5 +
.../editor/HaskellCompletionContributor.scala | 2 +-
.../AvailableModuleNamesComponent.scala | 2 +-
.../component/BrowseModuleComponent.scala | 29 +++--
.../DefinitionLocationComponent.scala | 4 +-
.../GlobalProjectInfoComponent.scala | 68 +++++++----
.../external/component/HLintComponent.scala | 2 +-
.../component/HaskellComponentsManager.scala | 51 +++++----
.../external/component/HoogleComponent.scala | 4 +-
.../LibraryModuleNamesComponent.scala | 102 -----------------
.../LibraryPackageInfoComponent.scala | 106 ++++++++++++++++++
.../component/NameInfoComponent.scala | 2 +
.../component/ProjectLibraryFileWatcher.scala | 2 +-
.../StackComponentGlobalInfoComponent.scala | 4 +-
.../component/StackProjectManager.scala | 28 +++--
.../component/TypeInfoComponent.scala | 10 +-
.../external/execution/CommandLine.scala | 22 +++-
.../external/execution/StackCommandLine.scala | 6 +-
.../haskell/module/HaskellModuleBuilder.scala | 51 ++++-----
.../haskell/psi/HaskellElementFactory.scala | 8 +-
.../intellij/haskell/sdk/HaskellSdkType.scala | 3 +-
.../haskell/util/HaskellProjectUtil.scala | 4 +
.../intellij/haskell/util/ScalaUtil.scala | 15 +++
28 files changed, 321 insertions(+), 239 deletions(-)
delete mode 100644 src/main/scala/intellij/haskell/external/component/LibraryModuleNamesComponent.scala
create mode 100644 src/main/scala/intellij/haskell/external/component/LibraryPackageInfoComponent.scala
diff --git a/intellij-haskell/META-INF/plugin.xml b/intellij-haskell/META-INF/plugin.xml
index 092257f2d..1ce5d93aa 100644
--- a/intellij-haskell/META-INF/plugin.xml
+++ b/intellij-haskell/META-INF/plugin.xml
@@ -11,7 +11,7 @@
]]>
1.0.0-beta30
+ 1.0.0-beta31
IMPORTANT: Stack version should be > 1.7.0 and please reimport your Haskell Stack project after updating the plugin.
- Improved responsiveness, especially for multi-package projects
diff --git a/src/main/scala/intellij/haskell/action/AboutAction.scala b/src/main/scala/intellij/haskell/action/AboutAction.scala
index a8679c6e9..4edebe868 100644
--- a/src/main/scala/intellij/haskell/action/AboutAction.scala
+++ b/src/main/scala/intellij/haskell/action/AboutAction.scala
@@ -19,8 +19,8 @@ package intellij.haskell.action
import com.intellij.openapi.actionSystem.{AnAction, AnActionEvent}
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo
-import intellij.haskell.external.component.{HLintComponent, HoogleComponent, StackProjectManager}
-import intellij.haskell.external.execution.StackCommandLine
+import intellij.haskell.external.component.{HLintComponent, HaskellComponentsManager, HoogleComponent, StackProjectManager}
+import intellij.haskell.external.execution.{CommandLine, StackCommandLine}
import intellij.haskell.util.HaskellEditorUtil
import scala.collection.mutable.ArrayBuffer
@@ -43,8 +43,8 @@ class AboutAction extends AnAction {
val messages = new ArrayBuffer[String]
val project = actionEvent.getProject
messages.+=(s"${boldToolName("Stack")} version: " + StackCommandLine.run(project, Seq("--numeric-version")).map(_.getStdout).getOrElse("-"))
- messages.+=(s"${boldToolName("GHC")}: " + StackCommandLine.run(project, Seq("exec", "--", "ghc", "--version")).map(_.getStdout).getOrElse("-"))
- messages.+=(s"${boldToolName("Intero")}: " + StackCommandLine.run(project, Seq("exec", "--", "intero", "--version")).map(_.getStdout).getOrElse("-"))
+ messages.+=(s"${boldToolName("GHC")}: " + HaskellComponentsManager.getGhcVersion(project).map(_.prettyString).getOrElse("-"))
+ messages.+=(s"${boldToolName("Intero")}: " + HaskellComponentsManager.getInteroPath(project).map(p => CommandLine.run(project, p, Seq("--version")).getStdout).getOrElse("-"))
messages.+=(s"${boldToolName("HLint")}: " + HLintComponent.versionInfo(project))
messages.+=(s"${boldToolName("Hoogle")}: " + HoogleComponent.versionInfo(project))
messages.+=(s"${boldToolName("Hindent")}: " + HindentReformatAction.versionInfo(project))
diff --git a/src/main/scala/intellij/haskell/action/HindentReformatAction.scala b/src/main/scala/intellij/haskell/action/HindentReformatAction.scala
index 2ef15071b..8684019b5 100644
--- a/src/main/scala/intellij/haskell/action/HindentReformatAction.scala
+++ b/src/main/scala/intellij/haskell/action/HindentReformatAction.scala
@@ -91,7 +91,7 @@ object HindentReformatAction {
def versionInfo(project: Project): String = {
if (StackProjectManager.isHindentAvailable(project)) {
- CommandLine.run(Some(project), project.getBasePath, HindentPath, Seq("--version")).getStdout
+ CommandLine.run(project, HindentPath, Seq("--version")).getStdout
} else {
"-"
}
diff --git a/src/main/scala/intellij/haskell/action/StylishHaskellReformatAction.scala b/src/main/scala/intellij/haskell/action/StylishHaskellReformatAction.scala
index 2fd2937ec..50b9515b7 100644
--- a/src/main/scala/intellij/haskell/action/StylishHaskellReformatAction.scala
+++ b/src/main/scala/intellij/haskell/action/StylishHaskellReformatAction.scala
@@ -45,7 +45,7 @@ object StylishHaskellReformatAction {
def versionInfo(project: Project): String = {
if (StackProjectManager.isStylishHaskellAvailable(project)) {
- CommandLine.run(Some(project), project.getBasePath, StylishHaskellPath, Seq("--version")).getStdout
+ CommandLine.run(project, StylishHaskellPath, Seq("--version")).getStdout
} else {
"-"
}
@@ -58,7 +58,7 @@ object StylishHaskellReformatAction {
HaskellFileUtil.getAbsolutePath(psiFile) match {
case Some(path) =>
val processOutputFuture = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable[ProcessOutput] {
- CommandLine.run(Some(project), project.getBasePath, StylishHaskellPath, Seq(path))
+ CommandLine.run(project, StylishHaskellPath, Seq(path))
})
FutureUtil.waitForValue(project, processOutputFuture, s"reformatting by $StylishHaskellName") match {
diff --git a/src/main/scala/intellij/haskell/annotator/HaskellAnnotator.scala b/src/main/scala/intellij/haskell/annotator/HaskellAnnotator.scala
index ddfdbb5b8..fcdc361b4 100644
--- a/src/main/scala/intellij/haskell/annotator/HaskellAnnotator.scala
+++ b/src/main/scala/intellij/haskell/annotator/HaskellAnnotator.scala
@@ -432,10 +432,10 @@ class NotInScopeIntentionAction(identifier: String, moduleName: String, psiFile:
val commaElement = importIdsSpec.addAfter(HaskellElementFactory.createComma(project), importId)
HaskellElementFactory.createImportId(project, identifier).foreach(mi => importIdsSpec.addAfter(mi, commaElement))
})
- case None => createImportDeclaration(importDeclarationElement, ids)
+ case None => createImportDeclaration(importDeclarationElement, ids, project)
}
case None =>
- createImportDeclaration(importDeclarationElement, ids)
+ createImportDeclaration(importDeclarationElement, ids, project)
}
case _ =>
HaskellPsiUtil.findModuleDeclaration(psiFile) match {
@@ -448,9 +448,13 @@ class NotInScopeIntentionAction(identifier: String, moduleName: String, psiFile:
)
}
- private def createImportDeclaration(importDeclarationElement: HaskellImportDeclaration, ids: HaskellImportDeclarations) = {
- val lastImportDeclaration = HaskellPsiUtil.findImportDeclarations(psiFile).lastOption.orNull
- ids.addAfter(importDeclarationElement, lastImportDeclaration)
+ private def createImportDeclaration(importDeclarationElement: HaskellImportDeclaration, ids: HaskellImportDeclarations, project: Project) = {
+ HaskellPsiUtil.findImportDeclarations(psiFile).lastOption match {
+ case Some(id) =>
+ val newLine = ids.addAfter(HaskellElementFactory.createNewLine(project), id)
+ ids.addAfter(importDeclarationElement, newLine)
+ case None => ids.addAfter(importDeclarationElement, null)
+ }
}
}
diff --git a/src/main/scala/intellij/haskell/cabal/CabalInfo.scala b/src/main/scala/intellij/haskell/cabal/CabalInfo.scala
index ca26a5f0e..6e8bce047 100644
--- a/src/main/scala/intellij/haskell/cabal/CabalInfo.scala
+++ b/src/main/scala/intellij/haskell/cabal/CabalInfo.scala
@@ -58,6 +58,11 @@ class CabalInfo(cabalFile: CabalFile, modulePath: String) {
ff <- HaskellPsiUtil.getChildOfType(pkgName, classOf[Freeform])
} yield ff.getText).getOrElse(throw new IllegalStateException(s"Can not find package name in Cabal file ${cabalFile.getName}"))
+ val packageVersion: String = (for {
+ pkgVersion <- HaskellPsiUtil.getChildOfType(cabalFile, classOf[PkgVersion])
+ ff <- HaskellPsiUtil.getChildOfType(pkgVersion, classOf[Freeform])
+ } yield ff.getText).getOrElse(throw new IllegalStateException(s"Can not find package version in Cabal file ${cabalFile.getName}"))
+
lazy val library: Option[LibraryCabalStanza] = {
cabalFile.getChildren.collectFirst {
case c: Library => LibraryCabalStanza(c, packageName, modulePath)
diff --git a/src/main/scala/intellij/haskell/editor/HaskellCompletionContributor.scala b/src/main/scala/intellij/haskell/editor/HaskellCompletionContributor.scala
index 4edc88e99..2a6d49c25 100644
--- a/src/main/scala/intellij/haskell/editor/HaskellCompletionContributor.scala
+++ b/src/main/scala/intellij/haskell/editor/HaskellCompletionContributor.scala
@@ -440,7 +440,7 @@ object HaskellCompletionContributor {
f4 <- idsF4
} yield doIt(f1, f2, f3, f4)
- new WaitFor(2000, 10) {
+ new WaitFor(4000, 10) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
f.isCompleted
diff --git a/src/main/scala/intellij/haskell/external/component/AvailableModuleNamesComponent.scala b/src/main/scala/intellij/haskell/external/component/AvailableModuleNamesComponent.scala
index d8f3b392b..6906242ea 100644
--- a/src/main/scala/intellij/haskell/external/component/AvailableModuleNamesComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/AvailableModuleNamesComponent.scala
@@ -75,7 +75,7 @@ private[component] object AvailableModuleNamesComponent {
}
private def findAvailableLibraryModuleNames(stackComponentInfo: StackComponentInfo): Iterable[String] = {
- HaskellComponentsManager.findStackComponentGlobalInfo(stackComponentInfo).map(_.libraryModuleNames.flatMap(_.exposed)).getOrElse(Iterable())
+ HaskellComponentsManager.findStackComponentGlobalInfo(stackComponentInfo).map(_.libraryModuleNames.flatMap(_.exposedModuleNames)).getOrElse(Iterable())
}
private def findModuleNamesInModule(project: Project, currentModule: Module, modules: Seq[Module], includeTests: Boolean): Iterable[String] = {
diff --git a/src/main/scala/intellij/haskell/external/component/BrowseModuleComponent.scala b/src/main/scala/intellij/haskell/external/component/BrowseModuleComponent.scala
index 1b4e381b5..90361adb8 100644
--- a/src/main/scala/intellij/haskell/external/component/BrowseModuleComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/BrowseModuleComponent.scala
@@ -66,7 +66,7 @@ private[component] object BrowseModuleComponent {
}
def findExportedIdentifiers(stackComponentGlobalInfo: StackComponentGlobalInfo, psiFile: PsiFile, moduleName: String)(implicit ec: ExecutionContext): Future[Iterable[ModuleIdentifier]] = {
- if (stackComponentGlobalInfo.libraryModuleNames.exists(_.exposed.toSeq.contains(moduleName))) {
+ if (stackComponentGlobalInfo.libraryModuleNames.exists(_.exposedModuleNames.contains(moduleName))) {
findLibraryModuleIdentifiers(psiFile.getProject, moduleName)
} else {
val key = Key(psiFile.getProject, moduleName, Some(psiFile), exported = true)
@@ -114,9 +114,11 @@ private[component] object BrowseModuleComponent {
case Some(repl) =>
if (repl.isBusy) {
Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
} else {
repl.getLocalModuleIdentifiers(moduleName, psiFile) match {
- case Some(output) if output.stderrLines.isEmpty => Right(output.stdoutLines.takeWhile(l => !l.startsWith("-- imported via")).flatMap(l => findModuleIdentifiers(project, l, moduleName)))
+ case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.takeWhile(l => !l.startsWith("-- imported via")).flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
}
@@ -130,9 +132,11 @@ private[component] object BrowseModuleComponent {
case Some(repl) =>
if (repl.isBusy) {
Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
} else {
repl.getModuleIdentifiers(moduleName, psiFile) match {
- case Some(output) if output.stderrLines.isEmpty => Right(output.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName)))
+ case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
}
@@ -144,15 +148,18 @@ private[component] object BrowseModuleComponent {
private def findLibModuleIdentifiers(project: Project, moduleName: String): Either[NoInfo, Seq[ModuleIdentifier]] = {
StackReplsManager.getGlobalRepl(project) match {
- case Some(repl) => if (repl.isBusy) {
- Left(ReplIsBusy)
- } else {
- repl.getModuleIdentifiers(moduleName) match {
- case None => Left(ReplNotAvailable)
- case Some(o) if o.stdoutLines.nonEmpty => Right(o.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName).toSeq))
- case _ => Left(NoInfoAvailable(moduleName, "-"))
+ case Some(repl) =>
+ if (repl.isBusy) {
+ Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
+ } else {
+ repl.getModuleIdentifiers(moduleName) match {
+ case None => Left(ReplNotAvailable)
+ case Some(o) if o.stdoutLines.nonEmpty => Right(o.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName).toSeq))
+ case _ => Left(NoInfoAvailable(moduleName, "-"))
+ }
}
- }
case None => Left(ReplNotAvailable)
}
}
diff --git a/src/main/scala/intellij/haskell/external/component/DefinitionLocationComponent.scala b/src/main/scala/intellij/haskell/external/component/DefinitionLocationComponent.scala
index 1ba03c36e..b5053f08e 100644
--- a/src/main/scala/intellij/haskell/external/component/DefinitionLocationComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/DefinitionLocationComponent.scala
@@ -173,9 +173,11 @@ private[component] object DefinitionLocationComponent {
case Some(repl) =>
if (repl.isBusy) {
Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
} else {
f(repl) match {
- case Some(o) if o.stderrLines.isEmpty => Right(o)
+ case Some(o) if o.stderrLines.isEmpty && o.stdoutLines.nonEmpty => Right(o)
case _ => Left(ReplNotAvailable)
}
}
diff --git a/src/main/scala/intellij/haskell/external/component/GlobalProjectInfoComponent.scala b/src/main/scala/intellij/haskell/external/component/GlobalProjectInfoComponent.scala
index 6c5880a5a..604be58e1 100644
--- a/src/main/scala/intellij/haskell/external/component/GlobalProjectInfoComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/GlobalProjectInfoComponent.scala
@@ -16,12 +16,12 @@
package intellij.haskell.external.component
+import java.nio.file.Paths
+
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.intellij.openapi.project.Project
import intellij.haskell.external.execution.{CommandLine, StackCommandLine}
-import intellij.haskell.module.HaskellModuleBuilder
-import intellij.haskell.module.HaskellModuleBuilder.HaskellDependency
-import intellij.haskell.util.GhcVersion
+import intellij.haskell.util.{GhcVersion, ScalaUtil}
import scala.collection.JavaConverters._
@@ -41,16 +41,13 @@ private[component] object GlobalProjectInfoComponent {
}
}
- def getSupportedLanguageExtensions(project: Project): Option[Iterable[String]] = {
- findGhcPath(project).map(ghcPath => {
- CommandLine.run(
- Some(project),
- project.getBasePath,
- ghcPath,
- Seq("--supported-languages"),
- notifyBalloonError = true
- ).getStdoutLines.asScala
- })
+ def getSupportedLanguageExtensions(project: Project, ghcPath: String): Seq[String] = {
+ CommandLine.run(
+ project,
+ ghcPath,
+ Seq("--supported-languages"),
+ notifyBalloonError = true
+ ).getStdoutLines.asScala
}
def getAvailableStackagesPackages(project: Project): Iterable[String] = {
@@ -65,22 +62,47 @@ private[component] object GlobalProjectInfoComponent {
private def createGlobalProjectInfo(key: Key): Option[GlobalProjectInfo] = {
val project = key.project
for {
- extensions <- getSupportedLanguageExtensions(project)
+ pathLines <- findPathLines(project)
+ pathInfoMap = ScalaUtil.linesToMap(pathLines)
+ binPaths <- findBinPaths(pathInfoMap)
+ packageDbPaths <- findPackageDbPaths(pathInfoMap)
+ ghcPath = Paths.get(binPaths.compilerBinPath, "ghc").toString
+ ghcPkgPath = Paths.get(binPaths.compilerBinPath, "ghc-pkg").toString
+ interoPath = Paths.get(binPaths.localBinPath, "intero").toString
+ extensions = getSupportedLanguageExtensions(project, ghcPath)
stackagePackageNames = getAvailableStackagesPackages(project)
- ghcVersion <- findGhcVersion(project)
- projectDependencies = HaskellModuleBuilder.getProjectLibraryDependencies(project)
- } yield GlobalProjectInfo(ghcVersion, extensions, projectDependencies, stackagePackageNames)
+ ghcVersion = findGhcVersion(project, ghcPath)
+ } yield GlobalProjectInfo(ghcVersion, ghcPath, ghcPkgPath, interoPath, packageDbPaths, binPaths, extensions, stackagePackageNames)
}
- private def findGhcPath(project: Project) = {
- StackCommandLine.run(project, Seq("path", "--compiler-exe")).flatMap(_.getStdoutLines.asScala.headOption)
+ private def findPathLines(project: Project) = {
+ StackCommandLine.run(project, Seq("path")).map(_.getStdoutLines.asScala.toSeq)
}
- private def findGhcVersion(project: Project): Option[GhcVersion] = {
- StackCommandLine.run(project, Seq("exec", "--", "ghc", "--numeric-version"))
- .map(o => GhcVersion.parse(o.getStdout.trim))
+ private def findGhcVersion(project: Project, ghcPath: String): GhcVersion = {
+ val output = CommandLine.run(project, ghcPath, Seq("--numeric-version"))
+ GhcVersion.parse(output.getStdout.trim)
+ }
+
+ private def findBinPaths(pathInfoMap: Map[String, String]): Option[ProjectBinPaths] = {
+ for {
+ compilerBinPath <- pathInfoMap.get("compiler-bin")
+ localBinPath <- pathInfoMap.get("local-install-root").map(p => Paths.get(p, "bin").toString)
+ } yield ProjectBinPaths(compilerBinPath, localBinPath)
+ }
+
+ private def findPackageDbPaths(pathInfoMap: Map[String, String]): Option[PackageDbPaths] = {
+ for {
+ globalPackageDbPath <- pathInfoMap.get("global-pkg-db")
+ snapshotPackageDbPath <- pathInfoMap.get("snapshot-pkg-db")
+ localPackageDbPath <- pathInfoMap.get("local-pkg-db")
+ } yield PackageDbPaths(globalPackageDbPath, snapshotPackageDbPath, localPackageDbPath)
}
}
-case class GlobalProjectInfo(ghcVersion: GhcVersion, supportedLanguageExtensions: Iterable[String], dependencies: Iterable[HaskellDependency], availableStackagePackageNames: Iterable[String])
+case class GlobalProjectInfo(ghcVersion: GhcVersion, ghcPath: String, ghcPkgPath: String, interoPath: String, packageDbPaths: PackageDbPaths, projectBinPaths: ProjectBinPaths, supportedLanguageExtensions: Iterable[String], availableStackagePackageNames: Iterable[String])
+
+case class PackageDbPaths(globalPackageDbPath: String, snapshotPackageDbPath: String, localPackageDbPath: String)
+
+case class ProjectBinPaths(compilerBinPath: String, localBinPath: String)
diff --git a/src/main/scala/intellij/haskell/external/component/HLintComponent.scala b/src/main/scala/intellij/haskell/external/component/HLintComponent.scala
index 86a902bb1..529766ec0 100644
--- a/src/main/scala/intellij/haskell/external/component/HLintComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/HLintComponent.scala
@@ -87,7 +87,7 @@ object HLintComponent {
}
private def runHLint(project: Project, arguments: Seq[String], ignoreExitCode: Boolean) = {
- CommandLine.run(Some(project), project.getBasePath, HLintPath, arguments, logOutput = true, ignoreExitCode = ignoreExitCode)
+ CommandLine.run(project, HLintPath, arguments, logOutput = true, ignoreExitCode = ignoreExitCode)
}
private object HlintJsonProtocol extends DefaultJsonProtocol {
diff --git a/src/main/scala/intellij/haskell/external/component/HaskellComponentsManager.scala b/src/main/scala/intellij/haskell/external/component/HaskellComponentsManager.scala
index ceece3e79..d0f33dbd4 100644
--- a/src/main/scala/intellij/haskell/external/component/HaskellComponentsManager.scala
+++ b/src/main/scala/intellij/haskell/external/component/HaskellComponentsManager.scala
@@ -32,7 +32,6 @@ import intellij.haskell.external.component.TypeInfoComponentResult.TypeInfoResul
import intellij.haskell.external.execution.CompilationResult
import intellij.haskell.external.repl.StackRepl.StanzaType
import intellij.haskell.external.repl.StackReplsManager
-import intellij.haskell.module.HaskellModuleBuilder.HaskellDependency
import intellij.haskell.psi.{HaskellPsiUtil, HaskellQualifiedNameElement}
import intellij.haskell.util.index.{HaskellFileIndex, HaskellModuleNameIndex}
import intellij.haskell.util.{GhcVersion, HaskellProjectUtil, ScalaUtil}
@@ -129,6 +128,10 @@ object HaskellComponentsManager {
HaskellProjectFileInfoComponent.findHaskellProjectFileInfo(project, filePath).map(_.stackComponentInfo)
}
+ def getGlobalProjectInfo(project: Project): Option[GlobalProjectInfo] = {
+ GlobalProjectInfoComponent.findGlobalProjectInfo(project)
+ }
+
def getSupportedLanguageExtension(project: Project): Iterable[String] = {
GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.supportedLanguageExtensions).getOrElse(Iterable())
}
@@ -137,12 +140,12 @@ object HaskellComponentsManager {
GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.ghcVersion)
}
- def getAvailableStackagePackages(project: Project): Iterable[String] = {
- GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.availableStackagePackageNames).getOrElse(Iterable())
+ def getInteroPath(project: Project): Option[String] = {
+ GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.interoPath)
}
- def getDependencies(project: Project): Iterable[HaskellDependency] = {
- GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.dependencies).getOrElse(Iterable())
+ def getAvailableStackagePackages(project: Project): Iterable[String] = {
+ GlobalProjectInfoComponent.findGlobalProjectInfo(project).map(_.availableStackagePackageNames).getOrElse(Iterable())
}
def findProjectPackageNames(project: Project): Option[Iterable[String]] = {
@@ -173,23 +176,25 @@ object HaskellComponentsManager {
DefinitionLocationComponent.findReferencesInCache(targetFile)
}
+ def findPackageInfo(project: Project, packageName: String): Option[PackageInfo] = {
+ LibraryPackageInfoComponent.findLibraryPackageInfo(project, packageName)
+ }
+
def invalidateCachesForModules(project: Project, moduleNames: Seq[String]): Unit = {
- // DefinitionLocationComponent.invalidate(psiFile)
- // TypeInfoComponent.invalidate(psiFile)
moduleNames.foreach(mn => BrowseModuleComponent.invalidateForModuleName(project, mn))
}
def invalidateGlobalCaches(project: Project): Unit = {
HaskellNotificationGroup.logInfoEvent(project, "Start to invalidate cache")
GlobalProjectInfoComponent.invalidate(project)
- LibraryModuleNamesComponent.invalidate(project)
+ LibraryPackageInfoComponent.invalidate(project)
HaskellProjectFileInfoComponent.invalidate(project)
BrowseModuleComponent.invalidate(project)
NameInfoComponent.invalidateAll(project)
DefinitionLocationComponent.invalidateAll(project)
TypeInfoComponent.invalidateAll(project)
HaskellPsiUtil.invalidateAllModuleNames(project)
- LibraryModuleNamesComponent.invalidate(project)
+ LibraryPackageInfoComponent.invalidate(project)
HaskellNotificationGroup.logInfoEvent(project, "Finished with invalidating cache")
}
@@ -199,16 +204,15 @@ object HaskellComponentsManager {
HaskellNotificationGroup.logInfoEvent(project, "Finished with preloading library identifiers cache")
}
- def preloadLibraryModuleNamesCache(project: Project): Iterable[LibraryModuleNames] = {
- HaskellNotificationGroup.logInfoEvent(project, "Start to preload library modules cache")
- val result = preloadLibraryModuleNames(project)
- HaskellNotificationGroup.logInfoEvent(project, "Finished with preloading library modules cache")
- result
+ def preloadStackComponentInfoCache(project: Project): Unit = {
+ HaskellNotificationGroup.logInfoEvent(project, "Start to preload stack component info cache")
+ preloadStackComponentInfos(project)
+ HaskellNotificationGroup.logInfoEvent(project, "Finished with preloading stack component info cache")
}
- def preloadLibraryFilesCache(project: Project, libraryModuleNames: Iterable[LibraryModuleNames]): Unit = {
+ def preloadLibraryFilesCache(project: Project): Unit = {
HaskellNotificationGroup.logInfoEvent(project, "Start to preload library files cache")
- preloadAllLibraryFiles(project, libraryModuleNames)
+ preloadLibraryFiles(project)
HaskellNotificationGroup.logInfoEvent(project, "Finished with preloading library files cache")
}
@@ -220,23 +224,18 @@ object HaskellComponentsManager {
TypeInfoComponent.findTypeInfoForSelection(psiFile, selectionModel)
}
- private def preloadLibraryModuleNames(project: Project): Seq[LibraryModuleNames] = {
+ private def preloadStackComponentInfos(project: Project): Unit = {
if (!project.isDisposed) {
- val projectLibraryModuleNames = getDependencies(project).flatMap(d => LibraryModuleNamesComponent.findLibraryModuleNames(project, d.name))
-
StackReplsManager.getReplsManager(project).foreach(_.stackComponentInfos.foreach(findStackComponentGlobalInfo))
-
- projectLibraryModuleNames.toSeq
- } else {
- Seq()
}
}
- private def preloadAllLibraryFiles(project: Project, libraryModuleNames: Iterable[LibraryModuleNames]): Unit = {
+ private def preloadLibraryFiles(project: Project): Unit = {
if (!project.isDisposed) {
DumbService.getInstance(project).waitForSmartMode()
if (!project.isDisposed) {
- HaskellModuleNameIndex.fillCache(project, libraryModuleNames.flatMap(libraryModuleNames => libraryModuleNames.exposed ++ libraryModuleNames.hidden))
+ val libraryPackageInfos = LibraryPackageInfoComponent.libraryPackageInfos(project)
+ HaskellModuleNameIndex.fillCache(project, libraryPackageInfos.flatMap(libraryModuleNames => libraryModuleNames.exposedModuleNames ++ libraryModuleNames.hiddenModuleNames))
}
}
}
@@ -269,7 +268,7 @@ object HaskellComponentsManager {
} else {
val libraryModuleNames = componentInfos.flatMap(HaskellComponentsManager.findStackComponentGlobalInfo).flatMap(_.libraryModuleNames)
- val exposedlibraryModuleNames = libraryModuleNames.flatMap(_.exposed).distinct
+ val exposedlibraryModuleNames = libraryModuleNames.flatMap(_.exposedModuleNames).distinct
DumbService.getInstance(project).runReadActionInSmartMode(ScalaUtil.computable {
HaskellPsiUtil.findImportDeclarations(f).flatMap(_.getModuleName).filter(mn => exposedlibraryModuleNames.contains(mn)).filterNot(_ == HaskellProjectUtil.Prelude)
})
diff --git a/src/main/scala/intellij/haskell/external/component/HoogleComponent.scala b/src/main/scala/intellij/haskell/external/component/HoogleComponent.scala
index 22714b969..dee87c3e1 100644
--- a/src/main/scala/intellij/haskell/external/component/HoogleComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/HoogleComponent.scala
@@ -158,7 +158,7 @@ object HoogleComponent {
def versionInfo(project: Project): String = {
if (StackProjectManager.isHoogleAvailable(project)) {
- CommandLine.run(Some(project), project.getBasePath, HooglePath, Seq("--version")).getStdout
+ CommandLine.run(project, HooglePath, Seq("--version")).getStdout
} else {
"-"
}
@@ -168,7 +168,7 @@ object HoogleComponent {
ProgressManager.checkCanceled()
val hoogleFuture = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable[ProcessOutput] {
- CommandLine.run(Some(project), project.getBasePath, HooglePath, Seq(s"--database=${hoogleDbPath(project)}") ++ arguments, logOutput = true)
+ CommandLine.run(project, HooglePath, Seq(s"--database=${hoogleDbPath(project)}") ++ arguments, logOutput = true)
})
ProgressManager.checkCanceled()
diff --git a/src/main/scala/intellij/haskell/external/component/LibraryModuleNamesComponent.scala b/src/main/scala/intellij/haskell/external/component/LibraryModuleNamesComponent.scala
deleted file mode 100644
index 335c4922b..000000000
--- a/src/main/scala/intellij/haskell/external/component/LibraryModuleNamesComponent.scala
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2014-2018 Rik van der Kleij
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package intellij.haskell.external.component
-
-import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.util.text.StringUtil
-import intellij.haskell.HaskellNotificationGroup
-import intellij.haskell.external.execution.StackCommandLine
-
-private[component] object LibraryModuleNamesComponent {
-
- import scala.collection.JavaConverters._
-
- private case class Key(project: Project, packageName: String)
-
- private type Result = Option[LibraryModuleNames]
-
- private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => findAvailableModuleNames(k))
-
- private def splitLines(s: String, excludeEmptyLines: Boolean) = {
- val converted = StringUtil.convertLineSeparators(s)
- StringUtil.split(converted, "\n", true, excludeEmptyLines).asScala
- }
-
- import scala.concurrent.duration._
-
- def preloadLibraryModuleNames(project: Project): Unit = {
- val result = StackCommandLine.run(project, Seq("exec", "--", "ghc-pkg", "dump"), notifyBalloonError = true, timeoutInMillis = 60.seconds.toMillis).map { processOutput =>
- val packageOutputs = processOutput.getStdout.split("(?m)^---\n")
- packageOutputs.map(o => {
- val outputLines = splitLines(o, excludeEmptyLines = true)
- val (packageName, exposedModuleNames, hiddenModuleNames) = findPackageModuleNames(outputLines)
- (packageName, LibraryModuleNames(exposedModuleNames, hiddenModuleNames))
- })
- }
-
- result match {
- case Some(r) =>
- r.foreach { case (name, lmn) =>
- name.foreach { n =>
- Cache.put(Key(project, n), Some(lmn))
- }
- }
- case None => HaskellNotificationGroup.logErrorBalloonEvent(project, "No module names")
- }
- }
-
- def findLibraryModuleNames(project: Project, packageName: String): Option[LibraryModuleNames] = {
- val key = Key(project, packageName)
- Cache.get(key) match {
- case result@Some(_) => result
- case _ => None
- }
- }
-
- private def findAvailableModuleNames(key: Key): Option[LibraryModuleNames] = {
- // Because preloadLibraryModuleNames should already have done all the work, something is wrong if this method is called
- HaskellNotificationGroup.logErrorBalloonEvent(key.project, s"Package ${key.packageName} is not in library module names cache")
- None
- }
-
- private final val NameKey = "name: "
-
- private def findPackageModuleNames(lines: Seq[String]): (Option[String], Iterable[String], Iterable[String]) = {
- val name = lines.find(_.startsWith(NameKey)).map(_.replace(NameKey, ""))
- val exposedModuleNameLines = lines.dropWhile(_ != "exposed-modules:").drop(1)
- val hiddenModuleNameLines = lines.dropWhile(_ != "hidden-modules:").drop(1)
-
- def findModuleNames(lines: Iterable[String]) = {
- lines.takeWhile(_.startsWith(" ")).mkString(" ").trim.split("""\s+""")
- }
-
- val exposedModulenames = findModuleNames(exposedModuleNameLines)
- val hiddenModulenames = findModuleNames(hiddenModuleNameLines)
- (name, exposedModulenames.toIterable, hiddenModulenames.toIterable)
- }
-
- def invalidate(project: Project): Unit = {
- val keys = Cache.asMap().keys.filter(_.project == project)
- keys.foreach(Cache.invalidate)
- }
-}
-
-
-case class Dependency(packageName: String, libraryModuleNames: LibraryModuleNames)
-
-case class LibraryModuleNames(exposed: Iterable[String], hidden: Iterable[String])
\ No newline at end of file
diff --git a/src/main/scala/intellij/haskell/external/component/LibraryPackageInfoComponent.scala b/src/main/scala/intellij/haskell/external/component/LibraryPackageInfoComponent.scala
new file mode 100644
index 000000000..c24d0a36d
--- /dev/null
+++ b/src/main/scala/intellij/haskell/external/component/LibraryPackageInfoComponent.scala
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014-2018 Rik van der Kleij
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package intellij.haskell.external.component
+
+import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.text.StringUtil
+import intellij.haskell.HaskellNotificationGroup
+import intellij.haskell.external.execution.CommandLine
+import intellij.haskell.util.ScalaUtil
+
+private[component] object LibraryPackageInfoComponent {
+
+ import scala.collection.JavaConverters._
+
+ private case class Key(project: Project, packageName: String)
+
+ private type Result = Option[PackageInfo]
+
+ private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => findPackageInfo(k))
+
+ private def splitLines(s: String, excludeEmptyLines: Boolean) = {
+ val converted = StringUtil.convertLineSeparators(s)
+ StringUtil.split(converted, "\n", true, excludeEmptyLines).asScala
+ }
+
+ import scala.concurrent.duration._
+
+ def preloadLibraryPackageInfos(project: Project): Unit = {
+ val projectPackageNames = HaskellComponentsManager.findProjectModulePackageNames(project).map(_._2)
+ val globalProjectInfo = HaskellComponentsManager.getGlobalProjectInfo(project)
+
+ val result = globalProjectInfo.map(info => CommandLine.run(project, info.ghcPkgPath,
+ Seq("dump",
+ s"--package-db=${info.packageDbPaths.globalPackageDbPath}",
+ s"--package-db=${info.packageDbPaths.snapshotPackageDbPath}",
+ s"--package-db=${info.packageDbPaths.localPackageDbPath}"), notifyBalloonError = true, timeoutInMillis = 60.seconds.toMillis)).map { processOutput =>
+
+ val packageOutputs = processOutput.getStdout.split("(?m)^---\n")
+ packageOutputs.map(o => {
+ val outputLines = splitLines(o, excludeEmptyLines = true)
+ findPackageInfo(outputLines)
+ })
+ }
+
+ result match {
+ case Some(r) => r.foreach {
+ case d@Some(packageInfo) => if (!projectPackageNames.toSeq.contains(packageInfo.packageName)) Cache.put(Key(project, packageInfo.packageName), d)
+ case None => HaskellNotificationGroup.logInfoBalloonEvent(project, s"Could not retrieve all package information via `ghc-pkg dump`")
+ }
+ case None => HaskellNotificationGroup.logErrorBalloonEvent(project, "Executing `ghc-pkg dunp` failed")
+ }
+ }
+
+ def findLibraryPackageInfo(project: Project, packageName: String): Result = {
+ val key = Key(project, packageName)
+ Cache.get(key) match {
+ case result@Some(_) => result
+ case _ => None
+ }
+ }
+
+ def libraryPackageInfos(project: Project): Iterable[PackageInfo] = {
+ Cache.asMap().values.flatten
+ }
+
+ private def findPackageInfo(key: Key): Result = {
+ // Because preloadLibraryModuleNames should already have done all the work, something is wrong if this method is called
+ HaskellNotificationGroup.logErrorBalloonEvent(key.project, s"Package ${key.packageName} is not in library module names cache")
+ None
+ }
+
+ private def findPackageInfo(lines: Seq[String]): Option[PackageInfo] = {
+ val packageInfoMap = ScalaUtil.linesToMap(lines)
+
+ for {
+ name <- packageInfoMap.get("name")
+ version <- packageInfoMap.get("version")
+ id <- packageInfoMap.get("id")
+ exposedModuleNames = packageInfoMap.get("exposed-modules").map(_.split("""\s+""").toSeq).getOrElse(Seq())
+ hiddenModuleNames = packageInfoMap.get("hidden-modules").map(_.split("""\s+""").toSeq).getOrElse(Seq())
+ } yield PackageInfo(name, version, id, exposedModuleNames, hiddenModuleNames)
+ }
+
+ def invalidate(project: Project): Unit = {
+ val keys = Cache.asMap().keys.filter(_.project == project)
+ keys.foreach(Cache.invalidate)
+ }
+}
+
+
+case class PackageInfo(packageName: String, version: String, id: String, exposedModuleNames: Seq[String], hiddenModuleNames: Seq[String])
diff --git a/src/main/scala/intellij/haskell/external/component/NameInfoComponent.scala b/src/main/scala/intellij/haskell/external/component/NameInfoComponent.scala
index 7094daf8f..f98ecbeda 100644
--- a/src/main/scala/intellij/haskell/external/component/NameInfoComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/NameInfoComponent.scala
@@ -52,6 +52,8 @@ private[component] object NameInfoComponent {
StackReplsManager.getProjectRepl(psiFile) match {
case Some(repl) => if (repl.isBusy) {
Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
} else {
repl.findInfo(psiFile, name) match {
case None => Left(ReplNotAvailable)
diff --git a/src/main/scala/intellij/haskell/external/component/ProjectLibraryFileWatcher.scala b/src/main/scala/intellij/haskell/external/component/ProjectLibraryFileWatcher.scala
index 67965642c..656534278 100644
--- a/src/main/scala/intellij/haskell/external/component/ProjectLibraryFileWatcher.scala
+++ b/src/main/scala/intellij/haskell/external/component/ProjectLibraryFileWatcher.scala
@@ -40,7 +40,7 @@ import scala.collection.concurrent
object ProjectLibraryFileWatcher {
def isBuilding(project: Project): Boolean = {
- ProjectLibraryFileWatcher.buildStatus.get(project).isDefined
+ StackProjectManager.getProjectLibraryFileWatcher(project).exists(_.currentlyBuildLibComponents.nonEmpty)
}
sealed trait BuildStatus
diff --git a/src/main/scala/intellij/haskell/external/component/StackComponentGlobalInfoComponent.scala b/src/main/scala/intellij/haskell/external/component/StackComponentGlobalInfoComponent.scala
index f97430e84..eafc9732a 100644
--- a/src/main/scala/intellij/haskell/external/component/StackComponentGlobalInfoComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/StackComponentGlobalInfoComponent.scala
@@ -64,7 +64,7 @@ private[component] object StackComponentGlobalInfoComponent {
if (project.isDisposed) {
None
} else {
- LibraryModuleNamesComponent.findLibraryModuleNames(project, packageName)
+ LibraryPackageInfoComponent.findLibraryPackageInfo(project, packageName)
}
}
}
@@ -81,4 +81,4 @@ private[component] object StackComponentGlobalInfoComponent {
}
}
-case class StackComponentGlobalInfo(stackComponentInfo: StackComponentInfo, libraryModuleNames: Iterable[LibraryModuleNames])
+case class StackComponentGlobalInfo(stackComponentInfo: StackComponentInfo, libraryModuleNames: Iterable[PackageInfo])
diff --git a/src/main/scala/intellij/haskell/external/component/StackProjectManager.scala b/src/main/scala/intellij/haskell/external/component/StackProjectManager.scala
index 40f2e6e99..3c41bbec4 100644
--- a/src/main/scala/intellij/haskell/external/component/StackProjectManager.scala
+++ b/src/main/scala/intellij/haskell/external/component/StackProjectManager.scala
@@ -168,20 +168,20 @@ object StackProjectManager {
})
}
}
-
- progressIndicator.setText("Busy with downloading library sources")
- HaskellModuleBuilder.addLibrarySources(project, update = true)
}
- progressIndicator.setText("Busy with preloading library module names")
- LibraryModuleNamesComponent.preloadLibraryModuleNames(project)
-
progressIndicator.setText("Busy with preloading global project info")
GlobalProjectInfoComponent.findGlobalProjectInfo(project)
- progressIndicator.setText("Busy with preloading library files")
- val preloadLibraryModuleNamesCache = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
- HaskellComponentsManager.preloadLibraryModuleNamesCache(project)
+ progressIndicator.setText("Busy with preloading library packages info")
+ LibraryPackageInfoComponent.preloadLibraryPackageInfos(project)
+
+ progressIndicator.setText("Busy with downloading library sources")
+ HaskellModuleBuilder.addLibrarySources(project, update = restart)
+
+ progressIndicator.setText("Busy with preloading stack component info cache")
+ val preloadStackComponentInfoCache = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
+ HaskellComponentsManager.preloadStackComponentInfoCache(project)
})
if (!project.isDisposed) {
@@ -192,13 +192,10 @@ object StackProjectManager {
progressIndicator.setText("Busy with starting global Stack REPL")
StackReplsManager.getGlobalRepl(project).foreach(_.start())
- progressIndicator.setText("Busy with downloading library sources")
- HaskellModuleBuilder.addLibrarySources(project, update = false)
-
+ StackReplsManager.getReplsManager(project).foreach(_.stackComponentInfos.filter(_.stanzaType == LibType).foreach(info => StackReplsManager.getProjectRepl(project, info).foreach(_.start())))
val preloadLibraryFilesCacheFuture = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.runnable {
- val result = FutureUtil.waitForValue(project, preloadLibraryModuleNamesCache, "preloading library module names", 600)
- result.foreach(libraryModuleNames => HaskellComponentsManager.preloadLibraryFilesCache(project, libraryModuleNames))
+ HaskellComponentsManager.preloadLibraryFilesCache(project)
})
progressIndicator.setText("Busy with preloading libraries")
@@ -216,7 +213,8 @@ object StackProjectManager {
}
progressIndicator.setText("Busy with preloading library caches")
- if (!preloadCacheFuture.isDone || !preloadLibraryFilesCacheFuture.isDone) {
+ if (!preloadCacheFuture.isDone || !preloadLibraryFilesCacheFuture.isDone || !preloadStackComponentInfoCache.isDone) {
+ FutureUtil.waitForValue(project, preloadStackComponentInfoCache, "preloading library caches", 600)
FutureUtil.waitForValue(project, preloadLibraryFilesCacheFuture, "preloading library caches", 600)
FutureUtil.waitForValue(project, preloadCacheFuture, "preloading library caches", 600)
}
diff --git a/src/main/scala/intellij/haskell/external/component/TypeInfoComponent.scala b/src/main/scala/intellij/haskell/external/component/TypeInfoComponent.scala
index 11e8f5079..a02cc20fd 100644
--- a/src/main/scala/intellij/haskell/external/component/TypeInfoComponent.scala
+++ b/src/main/scala/intellij/haskell/external/component/TypeInfoComponent.scala
@@ -83,9 +83,13 @@ private[component] object TypeInfoComponent {
}
}
+ private def checkValidKey(key: Key): Boolean = {
+ ApplicationUtil.runReadAction(key.qualifiedNameElement.isValid && key.qualifiedNameElement.getIdentifierElement.isValid)
+ }
+
def invalidate(psiFile: PsiFile): Unit = {
val keys = Cache.synchronous().asMap().filter(_._1.psiFile == psiFile).flatMap { case (k, v) =>
- if (ApplicationUtil.runReadAction(k.qualifiedNameElement.getIdentifierElement.isValid)) {
+ if (checkValidKey(k)) {
v.toOption match {
case Some(_) =>
val definedInSameFile = DefinitionLocationComponent.findDefinitionLocation(psiFile, k.qualifiedNameElement).toOption match {
@@ -116,7 +120,7 @@ private[component] object TypeInfoComponent {
def invalidate(moduleName: String): Unit = {
val keys = Cache.synchronous().asMap().flatMap { case (k, v) =>
- if (ApplicationUtil.runReadAction(k.qualifiedNameElement.getIdentifierElement.isValid)) {
+ if (checkValidKey(k)) {
v.toOption match {
case Some(_) =>
val sameModule = DefinitionLocationComponent.findDefinitionLocation(k.psiFile, k.qualifiedNameElement).toOption match {
@@ -168,6 +172,8 @@ private[component] object TypeInfoComponent {
case Some(repl) =>
if (repl.isBusy) {
Left(ReplIsBusy)
+ } else if (!repl.available) {
+ Left(ReplNotAvailable)
} else {
f(repl) match {
case Some(output) => output.stdoutLines.filterNot(_.trim.isEmpty).headOption.map(ti => Right(TypeInfo(ti, output.stderrLines.nonEmpty))).getOrElse(Left(NoInfoAvailable(key.expression, key.psiFile.getName)))
diff --git a/src/main/scala/intellij/haskell/external/execution/CommandLine.scala b/src/main/scala/intellij/haskell/external/execution/CommandLine.scala
index b031228dd..1d9b8f981 100644
--- a/src/main/scala/intellij/haskell/external/execution/CommandLine.scala
+++ b/src/main/scala/intellij/haskell/external/execution/CommandLine.scala
@@ -30,9 +30,27 @@ import scala.concurrent.duration._
object CommandLine {
val DefaultTimeout: FiniteDuration = 30.seconds
+ val DefaultNotifyBalloonError = false
+ val DefaultIgnoreExitCode = false
+ val DefaultLogOutput = false
- def run(project: Option[Project], workDir: String, commandPath: String, arguments: Seq[String], timeoutInMillis: Long = DefaultTimeout.toMillis,
- notifyBalloonError: Boolean = false, ignoreExitCode: Boolean = false, logOutput: Boolean = false): ProcessOutput = {
+ def run(project: Project, commandPath: String, arguments: Seq[String], timeoutInMillis: Long = DefaultTimeout.toMillis,
+ notifyBalloonError: Boolean = DefaultNotifyBalloonError, ignoreExitCode: Boolean = DefaultIgnoreExitCode, logOutput: Boolean = DefaultLogOutput): ProcessOutput = {
+ run2(Some(project), project.getBasePath, commandPath, arguments, timeoutInMillis, notifyBalloonError, ignoreExitCode, logOutput)
+ }
+
+ def run0(workDir: String, commandPath: String, arguments: Seq[String], timeoutInMillis: Long = DefaultTimeout.toMillis,
+ notifyBalloonError: Boolean = DefaultNotifyBalloonError, ignoreExitCode: Boolean = DefaultIgnoreExitCode, logOutput: Boolean = DefaultLogOutput): ProcessOutput = {
+ run2(None, workDir, commandPath, arguments, timeoutInMillis, notifyBalloonError, ignoreExitCode, logOutput)
+ }
+
+ def run1(project: Project, workDir: String, commandPath: String, arguments: Seq[String], timeoutInMillis: Long = DefaultTimeout.toMillis,
+ notifyBalloonError: Boolean = DefaultNotifyBalloonError, ignoreExitCode: Boolean = DefaultIgnoreExitCode, logOutput: Boolean = DefaultLogOutput): ProcessOutput = {
+ run2(Some(project), workDir, commandPath, arguments, timeoutInMillis, notifyBalloonError, ignoreExitCode, logOutput)
+ }
+
+ private def run2(project: Option[Project], workDir: String, commandPath: String, arguments: Seq[String], timeoutInMillis: Long = DefaultTimeout.toMillis,
+ notifyBalloonError: Boolean = DefaultNotifyBalloonError, ignoreExitCode: Boolean = DefaultIgnoreExitCode, logOutput: Boolean = DefaultLogOutput): ProcessOutput = {
val commandLine = createCommandLine(workDir, commandPath, arguments)
diff --git a/src/main/scala/intellij/haskell/external/execution/StackCommandLine.scala b/src/main/scala/intellij/haskell/external/execution/StackCommandLine.scala
index 671161726..0a04c31f4 100644
--- a/src/main/scala/intellij/haskell/external/execution/StackCommandLine.scala
+++ b/src/main/scala/intellij/haskell/external/execution/StackCommandLine.scala
@@ -40,8 +40,8 @@ object StackCommandLine {
def run(project: Project, arguments: Seq[String], timeoutInMillis: Long = CommandLine.DefaultTimeout.toMillis,
ignoreExitCode: Boolean = false, logOutput: Boolean = false, workDir: Option[String] = None, notifyBalloonError: Boolean = false): Option[ProcessOutput] = {
HaskellSdkType.getStackPath(project).map(stackPath => {
- CommandLine.run(
- Some(project),
+ CommandLine.run1(
+ project,
workDir.getOrElse(project.getBasePath),
stackPath,
arguments,
@@ -72,7 +72,7 @@ object StackCommandLine {
}
def build(project: Project, buildTargets: Seq[String]): Option[ProcessOutput] = {
- val arguments = Seq("build") ++ buildTargets ++ Seq("--fast")
+ val arguments = Seq("build") ++ buildTargets
val processOutput = run(project, arguments, -1, notifyBalloonError = true)
if (processOutput.isEmpty || processOutput.exists(_.getExitCode != 0)) {
HaskellNotificationGroup.logErrorEvent(project, s"Building `${buildTargets.mkString(", ")}` has failed")
diff --git a/src/main/scala/intellij/haskell/module/HaskellModuleBuilder.scala b/src/main/scala/intellij/haskell/module/HaskellModuleBuilder.scala
index c0d9af2c9..c702760f4 100644
--- a/src/main/scala/intellij/haskell/module/HaskellModuleBuilder.scala
+++ b/src/main/scala/intellij/haskell/module/HaskellModuleBuilder.scala
@@ -39,7 +39,7 @@ import intellij.haskell.external.execution.{CommandLine, StackCommandLine}
import intellij.haskell.sdk.HaskellSdkType
import intellij.haskell.stackyaml.StackYamlComponent
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
-import intellij.haskell.{GlobalInfo, HaskellIcons, HaskellNotificationGroup}
+import intellij.haskell.{GlobalInfo, HaskellIcons}
import javax.swing.Icon
import scala.collection.JavaConverters._
@@ -186,18 +186,29 @@ object HaskellModuleBuilder {
new File(moduleBuilder.getContentEntryPath, GlobalInfo.StackWorkDirName)
}
- def getProjectLibraryDependencies(project: Project): Iterable[HaskellDependency] = {
- for {
- lines <- StackCommandLine.run(project, Seq("ls", "dependencies", "--test", "--bench"), timeoutInMillis = 60.seconds.toMillis).map(_.getStdoutLines).toSeq
- dependencies <- createDependencies(project, lines.asScala).filterNot(d => d.isInstanceOf[HaskellProjectModuleDependency] || d.name == "rts" || d.name == "ghc")
- } yield dependencies
- }
+ def getDependencies(project: Project, module: Module, packageName: String): Seq[HaskellDependency] = {
+ val cabalInfo = HaskellComponentsManager.findCabalInfos(project).find(_.packageName == packageName)
+ val libPackages = cabalInfo.flatMap(_.library.map(_.buildDepends)).getOrElse(Array())
+ val exePackages = cabalInfo.map(_.executables.flatMap(_.buildDepends)).getOrElse(Seq())
+ val testPackages = cabalInfo.map(_.testSuites.flatMap(_.buildDepends)).getOrElse(Seq())
+ val benchPackages = cabalInfo.map(_.benchmarks.flatMap(_.buildDepends)).getOrElse(Seq())
- def getDependencies(project: Project, module: Module, packageName: String): Iterable[HaskellDependency] = {
- for {
- lines <- StackCommandLine.run(project, Seq("ls", "dependencies", packageName, "--test", "--bench"), timeoutInMillis = 60.seconds.toMillis).map(_.getStdoutLines).toSeq
- dependencies <- createDependencies(project, lines.asScala).filterNot(d => d.name == packageName || d.name == "rts" || d.name == "ghc")
- } yield dependencies
+ val packages = (libPackages ++ exePackages ++ testPackages ++ benchPackages).distinct.filterNot(n => n == packageName || n == "rts" || n == "ghc")
+
+ val projectModulePackageNames = HaskellComponentsManager.findProjectModulePackageNames(project)
+
+ packages.flatMap(n => {
+ projectModulePackageNames.find(_._2 == n).map(_._1) match {
+ case None =>
+ HaskellComponentsManager.findPackageInfo(project, n) match {
+ case Some(info) => Some(HaskellLibraryDependency(info.packageName, info.version))
+ case None => None
+ }
+ case Some(m) =>
+ val cabalInfo = HaskellComponentsManager.findCabalInfos(project).find(_.packageName == n)
+ cabalInfo.map(ci => HaskellProjectModuleDependency(ci.packageName, ci.packageVersion, m))
+ }
+ })
}
def getModuleRootDirectory(packagePath: String, modulePath: String): File = {
@@ -257,23 +268,9 @@ object HaskellModuleBuilder {
new File(new File(GlobalInfo.getLibrarySourcesPath), project.getName)
}
- private def createDependencies(project: Project, dependencyLines: Iterable[String]): Iterable[HaskellDependency] = {
- val projectModulePackageNames = HaskellComponentsManager.findProjectModulePackageNames(project)
- dependencyLines.flatMap {
- case PackagePattern(name, version) =>
- projectModulePackageNames.find(_._2 == name).map(_._1) match {
- case Some(pm) => Some(HaskellProjectModuleDependency(name, version, pm))
- case None => Some(HaskellLibraryDependency(name, version))
- }
- case x =>
- HaskellNotificationGroup.logWarningEvent(project, s"Could not determine package for line `$x`")
- None
- }
- }
-
private def downloadHaskellPackageSources(project: Project, projectLibDirectory: File, stackPath: String, libraryDependencies: Seq[HaskellLibraryDependency]): Unit = {
libraryDependencies.filterNot(libraryDependency => getPackageDirectory(projectLibDirectory, libraryDependency).exists()).flatMap(libraryDependency => {
- CommandLine.run(Some(project), projectLibDirectory.getAbsolutePath, stackPath, Seq("--no-nix", "unpack", libraryDependency.nameVersion), 10000).getStderr
+ CommandLine.run1(project, projectLibDirectory.getAbsolutePath, stackPath, Seq("--no-nix", "unpack", libraryDependency.nameVersion), 10000).getStderr
})
}
diff --git a/src/main/scala/intellij/haskell/psi/HaskellElementFactory.scala b/src/main/scala/intellij/haskell/psi/HaskellElementFactory.scala
index 9b3cfd970..50d8cc0c6 100644
--- a/src/main/scala/intellij/haskell/psi/HaskellElementFactory.scala
+++ b/src/main/scala/intellij/haskell/psi/HaskellElementFactory.scala
@@ -120,15 +120,15 @@ object HaskellElementFactory {
}
def createImportDeclaration(project: Project, moduleName: String, identifier: String): Option[HaskellImportDeclaration] = {
- val haskellImportDeclaration = createElementFromText(project, s"import $moduleName (${surroundWithParensIfSymbol(project, identifier)}) \n", HS_IMPORT_DECLARATION)
+ val haskellImportDeclaration = createElementFromText(project, s"import $moduleName (${surroundWithParensIfSymbol(project, identifier)})", HS_IMPORT_DECLARATION)
haskellImportDeclaration.map(_.asInstanceOf[HaskellImportDeclaration])
}
private def surroundWithParensIfSymbol(project: Project, identifier: String) = {
- if (createVarsym(project, identifier).isDefined || createConsym(project, identifier).isDefined) {
- s"($identifier)"
- } else {
+ if (createVarid(project, identifier).isDefined || createConid(project, identifier).isDefined) {
identifier
+ } else {
+ s"($identifier)"
}
}
diff --git a/src/main/scala/intellij/haskell/sdk/HaskellSdkType.scala b/src/main/scala/intellij/haskell/sdk/HaskellSdkType.scala
index 4b9d91c9c..699aa42a2 100644
--- a/src/main/scala/intellij/haskell/sdk/HaskellSdkType.scala
+++ b/src/main/scala/intellij/haskell/sdk/HaskellSdkType.scala
@@ -103,8 +103,7 @@ object HaskellSdkType {
def getNumericVersion(stackPath: String): Option[String] = {
val workDir = new File(stackPath).getParent
- val output = CommandLine.run(
- None,
+ val output = CommandLine.run0(
workDir,
stackPath,
Seq("--numeric-version"),
diff --git a/src/main/scala/intellij/haskell/util/HaskellProjectUtil.scala b/src/main/scala/intellij/haskell/util/HaskellProjectUtil.scala
index 43ea648c5..b13996b70 100644
--- a/src/main/scala/intellij/haskell/util/HaskellProjectUtil.scala
+++ b/src/main/scala/intellij/haskell/util/HaskellProjectUtil.scala
@@ -218,6 +218,10 @@ object HaskellProjectUtil {
case class GhcVersion(major: Int, minor: Int, patch: Int) extends Ordered[GhcVersion] {
def compare(that: GhcVersion): Int = GhcVersion.asc.compare(this, that)
+
+ def prettyString = {
+ s"$major.$minor.$patch"
+ }
}
object GhcVersion {
diff --git a/src/main/scala/intellij/haskell/util/ScalaUtil.scala b/src/main/scala/intellij/haskell/util/ScalaUtil.scala
index 4f7246da3..8b360e89b 100644
--- a/src/main/scala/intellij/haskell/util/ScalaUtil.scala
+++ b/src/main/scala/intellij/haskell/util/ScalaUtil.scala
@@ -21,6 +21,7 @@ import java.util.concurrent.Callable
import com.intellij.openapi.util.{Computable, Condition}
import scala.collection.mutable
+import scala.collection.mutable.ListBuffer
object ScalaUtil {
@@ -63,4 +64,18 @@ object ScalaUtil {
}
maxElems.keys
}
+
+ def linesToMap(lines: Seq[String]): Map[String, String] = {
+ val linePerKey = lines.foldLeft(ListBuffer[String]()) { case (xs, s) =>
+ if (s.startsWith(" ")) {
+ xs.update(xs.length - 1, xs.last + s)
+ xs
+ } else xs.+=(s)
+ }
+
+ linePerKey.map(x => {
+ val keyValuePair = x.split(": ", 2)
+ (keyValuePair(0), keyValuePair(1))
+ }).toMap
+ }
}