mirror of
https://github.com/ilyakooo0/intellij-haskell.git
synced 2024-09-11 14:56:19 +03:00
Refactor project library watcher.
This commit is contained in:
parent
fbb79d9f33
commit
35ce113f58
@ -20,7 +20,7 @@ import com.intellij.openapi.actionSystem.{AnAction, AnActionEvent}
|
||||
import com.intellij.openapi.progress.{ProgressIndicator, ProgressManager, Task}
|
||||
import com.intellij.openapi.project.Project
|
||||
import intellij.haskell.HaskellNotificationGroup
|
||||
import intellij.haskell.external.component.{HoogleComponent, ProjectLibraryFileWatcher, StackProjectManager}
|
||||
import intellij.haskell.external.component.{HoogleComponent, ProjectLibraryBuilder, StackProjectManager}
|
||||
import intellij.haskell.util.HaskellEditorUtil
|
||||
|
||||
class BuildHoogleDbAction extends AnAction {
|
||||
@ -29,7 +29,7 @@ class BuildHoogleDbAction extends AnAction {
|
||||
HaskellEditorUtil.enableExternalAction(actionEvent, (project: Project) =>
|
||||
!StackProjectManager.isInitializing(project) &&
|
||||
StackProjectManager.isHoogleAvailable(project) &&
|
||||
!ProjectLibraryFileWatcher.isBuilding(project) &&
|
||||
!ProjectLibraryBuilder.isBuilding(project) &&
|
||||
!StackProjectManager.isHaddockBuilding(project))
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ class BuildHoogleDbAction extends AnAction {
|
||||
|
||||
def run(progressIndicator: ProgressIndicator): Unit = {
|
||||
HaskellNotificationGroup.logInfoEvent(project, message)
|
||||
ProjectLibraryBuilder.resetBuildStatus(project)
|
||||
HoogleComponent.rebuildHoogle(project)
|
||||
}
|
||||
})
|
||||
|
@ -18,19 +18,20 @@ package intellij.haskell.action
|
||||
|
||||
import com.intellij.openapi.actionSystem.{AnAction, AnActionEvent}
|
||||
import com.intellij.openapi.project.Project
|
||||
import intellij.haskell.external.component.{ProjectLibraryFileWatcher, StackProjectManager}
|
||||
import intellij.haskell.external.component.{ProjectLibraryBuilder, StackProjectManager}
|
||||
import intellij.haskell.util.HaskellEditorUtil
|
||||
|
||||
class RestartStackReplsAction extends AnAction {
|
||||
|
||||
override def update(actionEvent: AnActionEvent): Unit = {
|
||||
HaskellEditorUtil.enableExternalAction(actionEvent, (p: Project) => !StackProjectManager.isInitializing(p) &&
|
||||
!ProjectLibraryFileWatcher.isBuilding(p) &&
|
||||
!ProjectLibraryBuilder.isBuilding(p) &&
|
||||
!StackProjectManager.isHaddockBuilding(p) &&
|
||||
!StackProjectManager.isPreloadingAllLibraryIdentifiers(p))
|
||||
}
|
||||
|
||||
override def actionPerformed(e: AnActionEvent): Unit = {
|
||||
StackProjectManager.restart(e.getProject)
|
||||
override def actionPerformed(actionEvent: AnActionEvent): Unit = {
|
||||
ProjectLibraryBuilder.resetBuildStatus(actionEvent.getProject)
|
||||
StackProjectManager.restart(actionEvent.getProject)
|
||||
}
|
||||
}
|
@ -49,7 +49,7 @@ private[component] object LoadComponent {
|
||||
}
|
||||
}
|
||||
|
||||
ProjectLibraryFileWatcher.checkLibraryBuild(project, projectRepl.stackComponentInfo)
|
||||
ProjectLibraryBuilder.checkLibraryBuild(project, projectRepl.stackComponentInfo)
|
||||
|
||||
projectRepl.load(psiFile, fileModified, mustBeByteCode = false) match {
|
||||
case Some((loadOutput, loadFailed)) =>
|
||||
|
155
src/main/scala/intellij/haskell/external/component/ProjectLibraryBuilder.scala
vendored
Normal file
155
src/main/scala/intellij/haskell/external/component/ProjectLibraryBuilder.scala
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2014-2019 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 java.util
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.module.{Module, ModuleUtilCore}
|
||||
import com.intellij.openapi.progress.{PerformInBackgroundOption, ProgressIndicator, ProgressManager, Task}
|
||||
import com.intellij.openapi.project.Project
|
||||
import intellij.haskell.HaskellNotificationGroup
|
||||
import intellij.haskell.annotator.HaskellAnnotator
|
||||
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
|
||||
import intellij.haskell.external.execution.StackCommandLine
|
||||
import intellij.haskell.external.repl.StackRepl.LibType
|
||||
import intellij.haskell.external.repl.StackReplsManager
|
||||
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, HaskellProjectUtil}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
object ProjectLibraryBuilder {
|
||||
|
||||
private val buildStatus = new ConcurrentHashMap[Project, BuildStatus].asScala
|
||||
|
||||
def isBuilding(project: Project): Boolean = {
|
||||
buildStatus.get(project).exists {
|
||||
case Building(_) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
def resetBuildStatus(project: Project): Option[BuildStatus] = {
|
||||
buildStatus.remove(project)
|
||||
}
|
||||
|
||||
sealed trait BuildStatus
|
||||
|
||||
case class Building(stackComponentInfos: Set[StackComponentInfo]) extends BuildStatus
|
||||
|
||||
case class Build(stackComponentInfos: Set[StackComponentInfo]) extends BuildStatus
|
||||
|
||||
def addBuild(project: Project, libComponentInfos: Set[StackComponentInfo]): Option[BuildStatus] = {
|
||||
synchronized {
|
||||
buildStatus.get(project) match {
|
||||
case Some(Building(_)) => buildStatus.put(project, Build(libComponentInfos))
|
||||
case Some(Build(componentInfos)) => buildStatus.put(project, Build(componentInfos.++(libComponentInfos)))
|
||||
case None => buildStatus.put(project, Build(libComponentInfos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def checkLibraryBuild(project: Project, currentInfo: StackComponentInfo): Unit = synchronized {
|
||||
if (!StackProjectManager.isInitializing(project) && !StackProjectManager.isHaddockBuilding(project) && !project.isDisposed) {
|
||||
buildStatus.get(project) match {
|
||||
case Some(Build(infos)) if !isBuilding(project) && infos.exists(_ != currentInfo) => build(project, infos)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def build(project: Project, libComponentInfos: Set[StackComponentInfo]): Unit = {
|
||||
buildStatus.put(project, Building(libComponentInfos))
|
||||
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Building project", false, PerformInBackgroundOption.ALWAYS_BACKGROUND) {
|
||||
|
||||
@tailrec
|
||||
def run(progressIndicator: ProgressIndicator): Unit = {
|
||||
|
||||
// Forced `-Wwarn` otherwise build will fail in case of warnings and that will cause that REPLs of dependent targets will not start anymore
|
||||
val projectLibTargets = HaskellComponentsManager.findStackComponentInfos(project).filter(_.stanzaType == LibType).map(_.target)
|
||||
|
||||
val buildMessage = s"Building targets: " + projectLibTargets.mkString(", ")
|
||||
HaskellNotificationGroup.logInfoEvent(project, buildMessage)
|
||||
progressIndicator.setText(buildMessage)
|
||||
|
||||
val output = StackCommandLine.buildInBackground(project, projectLibTargets ++ Seq("--ghc-options", "-Wwarn"))
|
||||
if (output.contains(true) && !project.isDisposed) {
|
||||
val projectRepls = StackReplsManager.getRunningProjectRepls(project)
|
||||
val openFiles = FileEditorManager.getInstance(project).getOpenFiles.filter(HaskellFileUtil.isHaskellFile)
|
||||
val openProjectFiles = openFiles.filter(vf => HaskellProjectUtil.isSourceFile(project, vf))
|
||||
val openInfoFiles = openProjectFiles.toSeq.flatMap(f =>
|
||||
HaskellComponentsManager.findStackComponentInfo(project, HaskellFileUtil.getAbsolutePath(f)) match {
|
||||
case Some(i) => Some((i, f))
|
||||
case None => None
|
||||
})
|
||||
|
||||
val isDependentResult = libComponentInfos.map(libInfo => {
|
||||
val module = libInfo.module
|
||||
val dependentModules = ApplicationUtil.runReadAction(ModuleUtilCore.getAllDependentModules(module))
|
||||
|
||||
val dependentFiles = openInfoFiles.filter { case (info, _) => isDependent(libInfo, dependentModules, info) }.map(_._2)
|
||||
val dependentRepls = projectRepls.filter(r => isDependent(libInfo, dependentModules, r.stackComponentInfo))
|
||||
(dependentFiles, dependentRepls)
|
||||
})
|
||||
|
||||
val dependentFiles = isDependentResult.flatMap(_._1)
|
||||
val dependentRepls = isDependentResult.flatMap(_._2)
|
||||
|
||||
dependentRepls.foreach { repl =>
|
||||
repl.restart()
|
||||
}
|
||||
|
||||
// When project is opened and has build errors some REPLs could not have been started
|
||||
StackReplsManager.getReplsManager(project).foreach(_.stackComponentInfos.filter(_.stanzaType == LibType).foreach { info =>
|
||||
StackReplsManager.getProjectRepl(project, info).foreach { repl =>
|
||||
if (!repl.available && !repl.starting) {
|
||||
repl.start()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
HaskellComponentsManager.invalidateBrowseInfo(project, libComponentInfos.flatMap(_.exposedModuleNames).toSeq)
|
||||
|
||||
dependentFiles.foreach { vf =>
|
||||
HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toOption match {
|
||||
case Some(psiFile) =>
|
||||
HaskellAnnotator.restartDaemonCodeAnalyzerForFile(psiFile)
|
||||
case None => HaskellNotificationGroup.logInfoEvent(project, s"Could not invalidate cache and restart daemon analyzer for file ${vf.getName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.isDisposed) {
|
||||
buildStatus.get(project) match {
|
||||
case Some(Build(componentInfos)) =>
|
||||
buildStatus.put(project, Building(componentInfos))
|
||||
run(progressIndicator)
|
||||
case _ =>
|
||||
buildStatus.remove(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private def isDependent(libInfo: StackComponentInfo, dependentModules: util.List[Module], info: StackComponentInfo) = {
|
||||
(info.module == libInfo.module && info.stanzaType != LibType) || dependentModules.contains(info.module)
|
||||
}
|
||||
}
|
@ -17,149 +17,17 @@
|
||||
package intellij.haskell.external.component
|
||||
|
||||
import java.util
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.module.{Module, ModuleUtilCore}
|
||||
import com.intellij.openapi.progress.{PerformInBackgroundOption, ProgressIndicator, ProgressManager, Task}
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.newvfs.BulkFileListener
|
||||
import com.intellij.openapi.vfs.newvfs.events.{VFileContentChangeEvent, VFileEvent}
|
||||
import intellij.haskell.HaskellNotificationGroup
|
||||
import intellij.haskell.annotator.HaskellAnnotator
|
||||
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
|
||||
import intellij.haskell.external.component.ProjectLibraryFileWatcher.{Build, Building}
|
||||
import intellij.haskell.external.execution.StackCommandLine
|
||||
import intellij.haskell.external.repl.StackRepl.LibType
|
||||
import intellij.haskell.external.repl.StackReplsManager
|
||||
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, HaskellProjectUtil}
|
||||
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.concurrent
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
object ProjectLibraryFileWatcher {
|
||||
|
||||
def isBuilding(project: Project): Boolean = {
|
||||
StackProjectManager.getProjectLibraryFileWatcher(project).exists(_.currentlyBuildLibComponents.nonEmpty)
|
||||
}
|
||||
|
||||
sealed trait BuildStatus
|
||||
|
||||
case class Building(stackComponentInfos: Set[StackComponentInfo]) extends BuildStatus
|
||||
|
||||
case class Build(stackComponentInfos: Set[StackComponentInfo]) extends BuildStatus
|
||||
|
||||
private val buildStatus: concurrent.Map[Project, BuildStatus] = new ConcurrentHashMap[Project, BuildStatus]().asScala
|
||||
|
||||
def addBuild(project: Project, libComponentInfos: Set[StackComponentInfo]): Option[BuildStatus] = {
|
||||
synchronized {
|
||||
ProjectLibraryFileWatcher.buildStatus.get(project) match {
|
||||
case Some(Building(_)) => ProjectLibraryFileWatcher.buildStatus.put(project, Build(libComponentInfos))
|
||||
case Some(Build(componentInfos)) => ProjectLibraryFileWatcher.buildStatus.put(project, Build(componentInfos.++(libComponentInfos)))
|
||||
case None => ProjectLibraryFileWatcher.buildStatus.put(project, Build(libComponentInfos))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def checkLibraryBuild(project: Project, currentInfo: StackComponentInfo): Unit = synchronized {
|
||||
if (!StackProjectManager.isInitializing(project) && !StackProjectManager.isHaddockBuilding(project) && !project.isDisposed) {
|
||||
ProjectLibraryFileWatcher.buildStatus.get(project) match {
|
||||
case Some(Build(infos)) if !isBuilding(project) && infos.exists(_ != currentInfo) => build(project, infos)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def build(project: Project, libComponentInfos: Set[StackComponentInfo]): Unit = {
|
||||
StackProjectManager.getProjectLibraryFileWatcher(project).foreach { watcher =>
|
||||
watcher.currentlyBuildLibComponents = libComponentInfos
|
||||
ProjectLibraryFileWatcher.buildStatus.put(project, Building(libComponentInfos))
|
||||
|
||||
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Building project", false, PerformInBackgroundOption.ALWAYS_BACKGROUND) {
|
||||
|
||||
@tailrec
|
||||
def run(progressIndicator: ProgressIndicator): Unit = {
|
||||
val buildMessage = s"Building project"
|
||||
HaskellNotificationGroup.logInfoEvent(project, buildMessage)
|
||||
progressIndicator.setText(buildMessage)
|
||||
|
||||
// Forced `-Wwarn` otherwise build will fail in case of warnings and that will cause that REPLs of dependent targets will not start anymore
|
||||
val projectLibTargets = HaskellComponentsManager.findStackComponentInfos(project).filter(_.stanzaType == LibType).map(_.target)
|
||||
val output = StackCommandLine.buildInBackground(project, projectLibTargets ++ Seq("--ghc-options", "-Wwarn"))
|
||||
if (output.contains(true) && !project.isDisposed) {
|
||||
val projectRepls = StackReplsManager.getRunningProjectRepls(project)
|
||||
val openFiles = FileEditorManager.getInstance(project).getOpenFiles.filter(HaskellFileUtil.isHaskellFile)
|
||||
val openProjectFiles = openFiles.filter(vf => HaskellProjectUtil.isSourceFile(project, vf))
|
||||
val openInfoFiles = openProjectFiles.flatMap(f =>
|
||||
HaskellComponentsManager.findStackComponentInfo(project, HaskellFileUtil.getAbsolutePath(f)) match {
|
||||
case Some(i) => Some((i, f))
|
||||
case None => None
|
||||
})
|
||||
|
||||
val isDependentResult = libComponentInfos.map(libInfo => {
|
||||
val module = libInfo.module
|
||||
val dependentModules = ApplicationUtil.runReadAction(ModuleUtilCore.getAllDependentModules(module))
|
||||
|
||||
val dependentFiles = openInfoFiles.filter { case (info, _) => isDependent(libInfo, dependentModules, info) }.map(_._2)
|
||||
val dependentRepls = projectRepls.filter(r => isDependent(libInfo, dependentModules, r.stackComponentInfo))
|
||||
(dependentFiles, dependentRepls)
|
||||
})
|
||||
|
||||
val dependentFiles = isDependentResult.flatMap(_._1)
|
||||
val dependentRepls = isDependentResult.flatMap(_._2)
|
||||
|
||||
dependentRepls.foreach { repl =>
|
||||
repl.restart()
|
||||
}
|
||||
|
||||
// When project is opened and has build errors some REPLs could not have been started
|
||||
StackReplsManager.getReplsManager(project).foreach(_.stackComponentInfos.filter(_.stanzaType == LibType).foreach { info =>
|
||||
StackReplsManager.getProjectRepl(project, info).foreach { repl =>
|
||||
if (!repl.available && !repl.starting) {
|
||||
repl.start()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
HaskellComponentsManager.invalidateBrowseInfo(project, libComponentInfos.flatMap(_.exposedModuleNames).toSeq)
|
||||
|
||||
dependentFiles.foreach { vf =>
|
||||
HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toOption match {
|
||||
case Some(psiFile) =>
|
||||
HaskellAnnotator.restartDaemonCodeAnalyzerForFile(psiFile)
|
||||
case None => HaskellNotificationGroup.logInfoEvent(project, s"Could not invalidate cache and restart daemon analyzer for file ${vf.getName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.isDisposed) {
|
||||
val buildStatus = ProjectLibraryFileWatcher.buildStatus.get(project)
|
||||
buildStatus match {
|
||||
case Some(Build(componentInfos)) =>
|
||||
watcher.currentlyBuildLibComponents = componentInfos
|
||||
ProjectLibraryFileWatcher.buildStatus.put(project, Building(componentInfos))
|
||||
run(progressIndicator)
|
||||
case _ =>
|
||||
watcher.currentlyBuildLibComponents = Set()
|
||||
ProjectLibraryFileWatcher.buildStatus.remove(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private def isDependent(libInfo: StackComponentInfo, dependentModules: util.List[Module], info: StackComponentInfo) = {
|
||||
(info.module == libInfo.module && info.stanzaType != LibType) || dependentModules.contains(info.module)
|
||||
}
|
||||
}
|
||||
|
||||
class ProjectLibraryFileWatcher(project: Project) extends BulkFileListener {
|
||||
|
||||
@volatile
|
||||
private var currentlyBuildLibComponents: Set[StackComponentInfo] = Set()
|
||||
|
||||
override def before(events: util.List[_ <: VFileEvent]): Unit = {}
|
||||
|
||||
override def after(events: util.List[_ <: VFileEvent]): Unit = {
|
||||
@ -171,14 +39,8 @@ class ProjectLibraryFileWatcher(project: Project) extends BulkFileListener {
|
||||
} yield componentInfo).toSet
|
||||
|
||||
if (libComponentInfos.nonEmpty) {
|
||||
synchronized {
|
||||
ProjectLibraryFileWatcher.buildStatus.get(project) match {
|
||||
case Some(Building(_)) => ProjectLibraryFileWatcher.buildStatus.put(project, Build(libComponentInfos))
|
||||
case Some(Build(componentInfos)) => ProjectLibraryFileWatcher.buildStatus.put(project, Build(componentInfos.++(libComponentInfos)))
|
||||
case None => ProjectLibraryFileWatcher.buildStatus.put(project, Build(libComponentInfos))
|
||||
}
|
||||
}
|
||||
ProjectLibraryBuilder.addBuild(project, libComponentInfos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.{PsiElement, PsiFile}
|
||||
import com.intellij.refactoring.listeners.RefactoringElementListener
|
||||
import com.intellij.refactoring.rename.RenamePsiElementProcessor
|
||||
import intellij.haskell.external.component.{HaskellComponentsManager, ProjectLibraryFileWatcher}
|
||||
import intellij.haskell.external.component.{HaskellComponentsManager, ProjectLibraryBuilder}
|
||||
import intellij.haskell.external.repl.StackRepl.LibType
|
||||
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
|
||||
|
||||
@ -40,7 +40,7 @@ class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
|
||||
targetInfo <- HaskellComponentsManager.findStackComponentInfo(tf)
|
||||
currentInfo <- HaskellComponentsManager.findStackComponentInfo(cf)
|
||||
} yield if (targetInfo != currentInfo && targetInfo.stanzaType == LibType)
|
||||
ProjectLibraryFileWatcher.addBuild(project, Set(targetInfo)) else ()
|
||||
ProjectLibraryBuilder.addBuild(project, Set(targetInfo)) else ()
|
||||
}
|
||||
|
||||
override def canProcessElement(psiElement: PsiElement): Boolean = {
|
||||
|
Loading…
Reference in New Issue
Block a user