Various performance improvements.

This commit is contained in:
Rik van der Kleij 2020-02-09 17:28:24 +01:00
parent caf009f18d
commit efe93aaf39
20 changed files with 321 additions and 385 deletions

View File

@ -17,15 +17,14 @@
package intellij.haskell.editor
import com.intellij.codeInsight.completion._
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.application.ApplicationManager
import com.intellij.codeInsight.lookup.{LookupElement, LookupElementBuilder, LookupElementPresentation}
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.impl.source.tree.TreeUtil
import com.intellij.psi.{PsiElement, PsiFile, TokenType}
import com.intellij.util.{ProcessingContext, WaitFor}
import com.intellij.util.ProcessingContext
import icons.HaskellIcons
import intellij.haskell.annotator.HaskellAnnotator
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
@ -34,11 +33,9 @@ import intellij.haskell.psi.HaskellTypes._
import intellij.haskell.psi._
import intellij.haskell.runconfig.console.{HaskellConsoleView, HaskellConsoleViewMap}
import intellij.haskell.util._
import intellij.haskell.{HaskellFile, HaskellNotificationGroup, HaskellParserDefinition}
import intellij.haskell.{HaskellFile, HaskellParserDefinition}
import org.apache.commons.lang.StringEscapeUtils
import scala.concurrent._
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
class HaskellCompletionContributor extends CompletionContributor {
@ -218,6 +215,7 @@ class HaskellCompletionContributor extends CompletionContributor {
ProgressManager.checkCanceled()
val localLookupElements = currentElement.map(ce => findLocalElements(ce).filterNot(_ == ce).map(createLocalLookupElement)).getOrElse(LazyList())
ProgressManager.checkCanceled()
resultSet.addAllElements(localLookupElements.asJavaCollection)
}
}
@ -249,7 +247,7 @@ class HaskellCompletionContributor extends CompletionContributor {
if (typedHoleSuggestionsWithHeader.nonEmpty) {
val typedHoleSuggestionLines = typedHoleSuggestionsWithHeader.tail
val typedHoleSuggestions = typedHoleSuggestionLines.filterNot(_.trim.startsWith("("))
val lookupElements = typedHoleSuggestions.flatMap(createLookupElement _).to(LazyList)
val lookupElements = typedHoleSuggestions.flatMap(createLookupElement).to(LazyList)
originalResultSet.addAllElements(lookupElements.asJavaCollection)
}
case None => ()
@ -339,26 +337,10 @@ class HaskellCompletionContributor extends CompletionContributor {
}
private def getGlobalInfo(psiFile: PsiFile): Option[(StackComponentInfo, StackComponentGlobalInfo)] = {
val infos = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
for {
info <- HaskellComponentsManager.findStackComponentInfo(psiFile)
globalInfo <- HaskellComponentsManager.findStackComponentGlobalInfo(info)
} yield (info, globalInfo)
})
new WaitFor(ApplicationUtil.timeout, 1) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
infos.isDone
}
}
if (infos.isDone) {
infos.get()
} else {
HaskellNotificationGroup.logInfoEvent(psiFile.getProject, "Timeout in getGlobalInfo for " + psiFile.getName)
None
}
for {
info <- HaskellComponentsManager.findStackComponentInfo(psiFile)
globalInfo <- HaskellComponentsManager.findStackComponentGlobalInfo(info)
} yield (info, globalInfo)
}
private def findAvailableIdsForImportModuleSpec(stackComponentGlobalInfo: StackComponentGlobalInfo, psiFile: PsiFile, element: PsiElement) = {
@ -366,24 +348,15 @@ class HaskellCompletionContributor extends CompletionContributor {
HaskellPsiUtil.findImportDeclaration(element).flatMap(_.getModuleName) match {
case Some(moduleName) =>
val ids = blocking {
HaskellComponentsManager.findModuleIdentifiers(psiFile.getProject, moduleName).map(_.map(i => i.map(x => createLookupElement(x, addParens = true))).getOrElse(Iterable()))
}
new WaitFor(ApplicationUtil.timeout, 1) {
override def condition(): Boolean = {
ScalaFutureUtil.waitForValue(psiFile.getProject,
HaskellComponentsManager.findModuleIdentifiers(psiFile.getProject, moduleName)
, "Getting module identifiers").flatten match {
case Some(ids) => ids.map(x => {
ProgressManager.checkCanceled()
ids.isCompleted
}
createLookupElement(x, addParens = true)
})
case None => Iterable()
}
if (ids.isCompleted) {
Await.result(ids, 1.milli)
} else {
HaskellNotificationGroup.logInfoEvent(psiFile.getProject, "Timeout in findAvailableIdsForImportModuleSpec for " + psiFile.getName)
Iterable()
}
case None => Iterable()
}
}
@ -392,7 +365,7 @@ class HaskellCompletionContributor extends CompletionContributor {
LookupElementBuilder.create(moduleName).withTailText(" module", true)
}
private def getInsideImportClausesLookupElements = {
private lazy val getInsideImportClausesLookupElements = {
InsideImportKeywords.map(c => LookupElementBuilder.create(c).withTailText(" clause", true))
}
@ -400,23 +373,23 @@ class HaskellCompletionContributor extends CompletionContributor {
HaskellComponentsManager.getSupportedLanguageExtension(project).map(n => LookupElementBuilder.create(n).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" language extension", true))
}
private def getPragmaStartEndIdsLookupElements = {
private lazy val getPragmaStartEndIdsLookupElements = {
PragmaIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" pragma", true))
}
private def getFileHeaderPragmaIdsLookupElements = {
private lazy val getFileHeaderPragmaIdsLookupElements = {
FileHeaderPragmaIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" pragma", true))
}
private def getModulePragmaIdsLookupElements = {
private lazy val getModulePragmaIdsLookupElements = {
ModulePragmaIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" pragma", true))
}
private def getCommentIdsLookupElements = {
private lazy val getCommentIdsLookupElements = {
CommentIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" comment", true))
}
private def getHaddockIdsLookupElements = {
private lazy val getHaddockIdsLookupElements = {
HaddockIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" haddock", true))
}
@ -426,7 +399,7 @@ class HaskellCompletionContributor extends CompletionContributor {
FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).map(createLookupElement(_)) ++ findTopLeveLookupElements(psiFile, moduleName, currentElement)
}
private def getKeywordLookupElements = {
private lazy val getKeywordLookupElements = {
Keywords.map(createKeywordLookupElement)
}
@ -434,7 +407,7 @@ class HaskellCompletionContributor extends CompletionContributor {
LookupElementBuilder.create(keyword).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" keyword", true)
}
private def getSpecialReservedIdLookupElements = {
private lazy val getSpecialReservedIdLookupElements = {
SpecialReservedIds.map(sr => LookupElementBuilder.create(sr).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" special keyword", true))
}
@ -475,8 +448,15 @@ class HaskellCompletionContributor extends CompletionContributor {
}
def createLocalLookupElement(namedElement: HaskellNamedElement): LookupElementBuilder = {
val typeSignature = HaskellComponentsManager.findTypeInfoForElement(namedElement).map(_.typeSignature)
LookupElementBuilder.create(namedElement.getName).withTypeText(typeSignature.map(StringUtil.unescapeXmlEntities).getOrElse("")).withIcon(HaskellIcons.HaskellSmallBlueLogo)
ProgressManager.checkCanceled()
def typeSignature = HaskellComponentsManager.findTypeInfoForElement(namedElement).map(_.typeSignature).map(StringUtil.unescapeXmlEntities).getOrElse("")
LookupElementBuilder.create(namedElement.getName).withIcon(HaskellIcons.HaskellSmallBlueLogo).withRenderer((element: LookupElement, presentation: LookupElementPresentation) => {
presentation.setTypeText(typeSignature)
presentation.setItemText(namedElement.getName)
presentation.setIcon(HaskellIcons.HaskellSmallBlueLogo)
})
}
private def createLocalTopLevelLookupElement(name: String, declaration: String, module: String): LookupElementBuilder = {

View File

@ -18,17 +18,14 @@ package intellij.haskell.external.component
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.{IndexNotReadyException, Project}
import com.intellij.psi.search.{FileTypeIndex, GlobalSearchScope}
import com.intellij.util.WaitFor
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
import intellij.haskell.external.repl.StackRepl.{BenchmarkType, TestSuiteType}
import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil}
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, ScalaFutureUtil}
import intellij.haskell.{HaskellFileType, HaskellNotificationGroup}
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
@ -38,7 +35,7 @@ private[component] object AvailableModuleNamesComponent {
private case class Key(stackComponentInfo: StackComponentInfo)
private final val Cache: AsyncLoadingCache[Key, Iterable[String]] = Scaffeine().expireAfterWrite(10.seconds).buildAsync((k: Key) => findAvailableProjectModuleNamesWithIndex(k.stackComponentInfo))
private final val Cache: AsyncLoadingCache[Key, Iterable[String]] = Scaffeine().refreshAfterWrite(10.seconds).buildAsync((k: Key) => findAvailableProjectModuleNamesWithIndex(k.stackComponentInfo))
def findAvailableModuleNamesWithIndex(stackComponentInfo: StackComponentInfo): Iterable[String] = {
// A module can be a project module AND library module
@ -51,21 +48,11 @@ private[component] object AvailableModuleNamesComponent {
def findAvailableProjectModuleNames(stackComponentInfo: StackComponentInfo): Iterable[String] = {
val key = Key(stackComponentInfo)
val f = Cache.get(key)
new WaitFor(ApplicationUtil.timeout, 1) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
f.isCompleted
}
}
if (f.isCompleted) {
Await.result(f, 1.milli)
} else {
HaskellNotificationGroup.logInfoEvent(stackComponentInfo.module.getProject, "Timeout in findAvailableProjectModuleNames " + stackComponentInfo)
Cache.synchronous().invalidate(key)
Iterable()
ScalaFutureUtil.waitForValue(stackComponentInfo.module.getProject, Cache.get(key), s"getting project module names for ${key.stackComponentInfo.packageName}", 1.second) match {
case Some(files) => files
case _ =>
Cache.synchronous().invalidate(key)
Iterable()
}
}
@ -98,7 +85,7 @@ private[component] object AvailableModuleNamesComponent {
HaskellNotificationGroup.logInfoEvent(project, s"Index not ready while findHaskellFiles for module ${currentModule.getName} ")
Iterable()
}
}, s"find Haskell files for module ${currentModule.getName}", 5.seconds).toOption.toIterable.flatten
}, s"find Haskell files for module ${currentModule.getName}").toOption.toIterable.flatten
}

View File

@ -25,7 +25,7 @@ import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.index.HaskellModuleNameIndex
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, HaskellProjectUtil, StringUtil}
import scala.concurrent.{ExecutionContext, Future, blocking}
import scala.concurrent.{ExecutionContext, Future}
private[component] object BrowseModuleComponent {
@ -44,14 +44,14 @@ private[component] object BrowseModuleComponent {
def findModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(project, moduleName)
val result = Cache.get(key)
blocking(result.map {
result.map {
case Right(ids) => Some(ids)
case Left(NoInfoAvailable(_, _)) | Left(NoMatchingExport) =>
None
case Left(ReplNotAvailable) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
Cache.synchronous().invalidate(key)
None
})
}
}
def findModuleIdentifiersSync(project: Project, moduleName: String): BrowseModuleInternalResult = {
@ -126,8 +126,9 @@ private[component] object BrowseModuleComponent {
projectRepl match {
case Some(repl) =>
if (repl.available) {
repl.getModuleIdentifiers(moduleName, psiFile) match {
repl.getModuleIdentifiers(project, moduleName, psiFile) match {
case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => createProjectModuleIdentifier(project, l, moduleName)))
case None => Left(ReplNotAvailable)
case _ => Left(ModuleNotAvailable(moduleName))
}
} else {
@ -144,6 +145,7 @@ private[component] object BrowseModuleComponent {
(repl.getModuleIdentifiers(moduleName), moduleFile) match {
case (Some(o), _) if o.stderrLines.isEmpty && o.stdoutLines.nonEmpty => Right(o.stdoutLines.flatMap(l => createLibraryModuleIdentifier(project, l, moduleName)))
case (_, Some(f)) => Right(ApplicationUtil.runReadAction(HaskellPsiUtil.findTopLevelDeclarations(f)).map(d => ApplicationUtil.runReadAction(d.getName)).flatMap(d => createLibraryModuleIdentifier(project, d, moduleName)).toSeq)
case (None, _) => Left(ReplNotAvailable)
case _ => Left(ModuleNotAvailable(moduleName))
}
} else {

View File

@ -26,6 +26,8 @@ import intellij.haskell.psi._
import intellij.haskell.util._
import intellij.haskell.util.index.HaskellModuleNameIndex._
import scala.concurrent.TimeoutException
private[component] object DefinitionLocationComponent {
private final val LocAtPattern = """(.+):\(([\d]+),([\d]+)\)-\(([\d]+),([\d]+)\)""".r
private final val PackageModulePattern = """([\w\-\d.]+)(?:-.*)?:([\w.\-]+)""".r
@ -40,29 +42,17 @@ private[component] object DefinitionLocationComponent {
def findDefinitionLocation(psiFile: PsiFile, qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String]): DefinitionLocationResult = {
val key = Key(psiFile, qualifiedNameElement, importQualifier)
Cache.getIfPresent(key) match {
case Some(v) =>
val result = v
result match {
case Right(_) => result
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.invalidate(key)
result
case _ => result
}
case None =>
// Not using Cache.get otherwise the threads which have read access are waiting and on the other hand, each background thread of the Cache needs a read action.
val result = findDefinitionLocationResult(key)
result match {
case Right(_) =>
Cache.put(key, result)
result
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotAvailable(_)) =>
result
case Left(_) =>
Cache.put(key, result)
result
}
try {
val result = ApplicationUtil.runReadAction(Cache.get(key))
result match {
case Right(_) => result
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.invalidate(key)
result
case _ => result
}
} catch {
case e: TimeoutException => Left(ReadActionTimeout(e.getMessage))
}
}
@ -168,6 +158,8 @@ private[component] object DefinitionLocationComponent {
Option(namedElement.getContainingFile).flatMap(HaskellPsiUtil.findModuleName).getOrElse("-")
}
import scala.concurrent.ExecutionContext.Implicits.global
private def findLocationByImportedIdentifiers(project: Project, key: Key, name: String): Either[NoInfo, PackageModuleLocation] = {
ProgressManager.checkCanceled()
@ -179,7 +171,15 @@ private[component] object DefinitionLocationComponent {
case Some(q) => q + "." + qNameName
}
val moduleIdentifiers = FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).filter(_.name == qName)
val moduleIdentifiers = {
if (HaskellProjectUtil.isSourceFile(psiFile)) {
FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile)
} else {
HaskellPsiUtil.findModuleName(psiFile).flatMap(mn => ScalaFutureUtil.waitForValue(project,
HaskellComponentsManager.findModuleIdentifiers(project, mn)
, "find module identifiers in DefinitionLocationComponent")).flatten.getOrElse(Iterable())
}.filter(_.name == qName)
}
ProgressManager.checkCanceled()
@ -200,6 +200,7 @@ private[component] object DefinitionLocationComponent {
sp <- LineColumnPosition.fromOffset(vf, qualifiedNameElement.getTextRange.getStartOffset)
ep <- LineColumnPosition.fromOffset(vf, qualifiedNameElement.getTextRange.getEndOffset)
endColumnNr = if (withoutLastColumn) ep.columnNr - 1 else ep.columnNr
if key.qualifiedNameElement.isValid
} yield {
repl: ProjectStackRepl => repl.findLocationInfo(moduleName, psiFile, sp.lineNr, sp.columnNr, ep.lineNr, endColumnNr, name)
}

View File

@ -20,7 +20,6 @@ import java.util
import java.util.concurrent.TimeoutException
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.HaskellNotificationGroup
@ -30,7 +29,7 @@ import intellij.haskell.psi.{HaskellImportDeclaration, HaskellImportId, HaskellP
import intellij.haskell.util.{ApplicationUtil, HaskellProjectUtil, ScalaFutureUtil}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future, blocking}
import scala.concurrent.{Await, Future}
import scala.jdk.CollectionConverters._
object FileModuleIdentifiers {
@ -64,28 +63,22 @@ object FileModuleIdentifiers {
}
def findAvailableModuleIdentifiers(psiFile: PsiFile): Iterable[ModuleIdentifier] = {
val message = s"find available module identifiers for ${psiFile.getName}"
if (ApplicationManager.getApplication.isReadAccessAllowed) {
val moduleIdentifiers = getModuleIdentifiers(psiFile)
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, moduleIdentifiers, message).getOrElse(Iterable())
} else {
ScalaFutureUtil.waitForValue(psiFile.getProject, getModuleIdentifiers(psiFile), message).getOrElse(Iterable())
}
val message = s"find available module identifiers for ${psiFile.getVirtualFile.getPath}"
val moduleIdentifiers = getModuleIdentifiers(psiFile)
ScalaFutureUtil.waitForValue(psiFile.getProject, moduleIdentifiers, message).getOrElse(Iterable())
}
private def getModuleIdentifiers(psiFile: PsiFile): Future[Iterable[ModuleIdentifier]] = {
val key = Key(psiFile)
blocking {
Cache.get(key).map {
case Some(mids) =>
if (mids.toSeq.contains(None)) {
Cache.synchronous.invalidate(key)
}
mids.flatten.flatten
case None =>
Cache.get(key).map {
case Some(mids) =>
if (mids.toSeq.contains(None)) {
Cache.synchronous.invalidate(key)
Iterable()
}
}
mids.flatten.flatten
case None =>
Cache.synchronous.invalidate(key)
Iterable()
}
}
@ -95,36 +88,31 @@ object FileModuleIdentifiers {
val project = k.psiFile.getProject
val psiFile = k.psiFile
ApplicationUtil.runInReadActionWithWriteActionPriority(project, findImportDeclarations(psiFile), "In findModuleIdentifiers") match {
case Right(importDeclarations) =>
val noImplicitPrelude = if (HaskellProjectUtil.isSourceFile(psiFile)) {
HaskellComponentsManager.findStackComponentInfo(psiFile).exists(info => isNoImplicitPreludeActive(info, psiFile))
} else {
// TODO: This can give unexpected behavior when finding references in library files
false
}
val idsF1 = getModuleIdentifiersFromFullImportedModules(noImplicitPrelude, psiFile, importDeclarations)
val importDeclarations = ApplicationUtil.runReadAction(findImportDeclarations(psiFile))
val noImplicitPrelude = if (HaskellProjectUtil.isSourceFile(psiFile)) {
HaskellComponentsManager.findStackComponentInfo(psiFile).exists(info => isNoImplicitPreludeActive(info, psiFile))
} else {
// TODO: This can give unexpected behavior when finding references in library files
false
}
val idsF1 = getModuleIdentifiersFromFullImportedModules(noImplicitPrelude, psiFile, importDeclarations)
val idsF2 = getModuleIdentifiersFromHidingIdsImportedModules(psiFile, importDeclarations)
val idsF2 = getModuleIdentifiersFromHidingIdsImportedModules(psiFile, importDeclarations)
val idsF3 = getModuleIdentifiersFromSpecIdsImportedModules(psiFile, importDeclarations)
val idsF3 = getModuleIdentifiersFromSpecIdsImportedModules(psiFile, importDeclarations)
val f = for {
f1 <- idsF1
f2 <- idsF2
f3 <- idsF3
} yield (f1, f2, f3)
val f = for {
f1 <- idsF1
f2 <- idsF2
f3 <- idsF3
} yield (f1, f2, f3)
try {
val (x, y, z) = Await.result(f, 10.seconds)
Some(x ++ y ++ z)
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout while find module identifiers for file ${k.psiFile.getName}")
None
}
case Left(noInfo) =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout while find import declarations in findModuleIdentifiers for file ${psiFile.getName}: ${noInfo.message}")
try {
val (x, y, z) = Await.result(f, 10.seconds)
Some(x ++ y ++ z)
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout while find module identifiers for file ${k.psiFile.getVirtualFile.getPath}")
None
}
}
@ -132,34 +120,28 @@ object FileModuleIdentifiers {
private def getModuleIdentifiersFromFullImportedModules(noImplicitPrelude: Boolean, psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getFullImportedModules(noImplicitPrelude, psiFile, importDeclarations)
blocking {
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(mi => mi.map(i => createQualifiedModuleIdentifiers(importInfo, i)))
}))
}
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(mi => mi.map(i => createQualifiedModuleIdentifiers(importInfo, i)))
}))
}
private def getModuleIdentifiersFromHidingIdsImportedModules(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getImportedModulesWithHidingIdsSpec(psiFile, importDeclarations)
blocking {
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filterNot(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filterNot(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
private def getModuleIdentifiersFromSpecIdsImportedModules(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getImportedModulesWithSpecIds(psiFile, importDeclarations)
blocking {
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
private sealed trait ImportInfo {

View File

@ -47,7 +47,7 @@ object HaskellComponentsManager {
BrowseModuleComponent.findModuleIdentifiersInCache(project)
}
}
ScalaFutureUtil.waitWithCheckCancelled(project, f, "find module identifiers in cache") match {
ScalaFutureUtil.waitForValue(project, f, "find module identifiers in cache") match {
case Some(ids) => ids
case None => Iterable()
}
@ -70,10 +70,6 @@ object HaskellComponentsManager {
NameInfoComponent.findNameInfo(psiElement)
}
def findNameInfoByModuleName(project: Project, moduleName: String, name: String): NameInfoResult = {
NameInfoComponent.NameInfoByModuleComponent.findNameInfoByModuleName(project, moduleName, name)
}
def findAvailableModuleNamesWithIndex(stackComponentInfo: StackComponentInfo): Iterable[String] = {
AvailableModuleNamesComponent.findAvailableModuleNamesWithIndex(stackComponentInfo)
}

View File

@ -18,7 +18,7 @@ package intellij.haskell.external.component
import java.nio.file.Paths
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiFile
@ -27,33 +27,29 @@ import intellij.haskell.external.component.HaskellComponentsManager.StackCompone
import intellij.haskell.external.repl.StackRepl.LibType
import intellij.haskell.external.repl.StackReplsManager
import intellij.haskell.runconfig.console.HaskellConsoleView
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaFutureUtil, ScalaUtil}
private[component] object HaskellModuleInfoComponent {
private case class Key(project: Project, filePath: String)
private final val Cache: LoadingCache[Key, Option[InternalHaskellModuleInfo]] = Scaffeine().build((k: Key) => createFileInfo(k))
private final val Cache: AsyncLoadingCache[Key, Option[InternalHaskellModuleInfo]] = Scaffeine().buildAsync((k: Key) => createFileInfo(k))
def findHaskellProjectFileInfo(project: Project, filePath: String): Option[StackComponentInfo] = {
val key = Key(project, filePath)
Cache.getIfPresent(key) match {
case Some(info) => info.flatMap(_.stackComponentInfo)
case None =>
Cache.get(key) match {
case Some(internalInfo) =>
internalInfo.message match {
case Some(m) => HaskellNotificationGroup.warningEvent(project, m)
case None => None
}
internalInfo.stackComponentInfo match {
case Some(info) => Some(info)
case _ => None
}
case _ =>
Cache.invalidate(key)
None
ScalaFutureUtil.waitForValue(project, Cache.get(key), "Getting project file info").flatten match {
case Some(internalInfo) =>
internalInfo.message match {
case Some(m) => HaskellNotificationGroup.warningEvent(project, m)
case None => None
}
internalInfo.stackComponentInfo match {
case Some(info) => Some(info)
case _ => None
}
case _ =>
Cache.synchronous.invalidate(key)
None
}
}
@ -71,12 +67,12 @@ private[component] object HaskellModuleInfoComponent {
}
def invalidate(project: Project): Unit = {
val keys = Cache.asMap().keys.filter(_.project == project)
keys.foreach(Cache.invalidate)
val keys = Cache.synchronous.asMap().keys.filter(_.project == project)
keys.foreach(Cache.synchronous.invalidate)
}
def invalidate(psiFile: PsiFile): Unit = {
HaskellFileUtil.getAbsolutePath(psiFile).foreach(fp => Cache.invalidate(Key(psiFile.getProject, fp)))
HaskellFileUtil.getAbsolutePath(psiFile).foreach(fp => Cache.synchronous.invalidate(Key(psiFile.getProject, fp)))
}
private def createFileInfo(key: Key): Option[InternalHaskellModuleInfo] = {

View File

@ -35,6 +35,7 @@ import scala.jdk.CollectionConverters._
object HoogleComponent {
private def hooglePath = HaskellSettingsState.hooglePath.getOrElse(GlobalInfo.defaultHooglePath.toString)
private final val HoogleDbName = "hoogle"
def runHoogle(project: Project, pattern: String, count: Int = 100): Option[Seq[String]] = {
@ -157,7 +158,7 @@ object HoogleComponent {
private def runHoogle(project: Project, arguments: Seq[String]): Option[ProcessOutput] = {
ProgressManager.checkCanceled()
ScalaFutureUtil.waitWithCheckCancelled(project,
ScalaFutureUtil.waitForValue(project,
Future {
blocking {
CommandLine.run(project, hooglePath, Seq(s"--database=${hoogleDbPath(project)}") ++ arguments, logOutput = true)

View File

@ -16,7 +16,7 @@
package intellij.haskell.external.component
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.psi.{PsiElement, PsiFile}
@ -24,7 +24,7 @@ import intellij.haskell.external.repl.StackRepl.StackReplOutput
import intellij.haskell.external.repl.StackReplsManager
import intellij.haskell.psi._
import intellij.haskell.util.StringUtil.escapeString
import intellij.haskell.util.{HaskellProjectUtil, StringUtil}
import intellij.haskell.util.{HaskellProjectUtil, ScalaFutureUtil, StringUtil}
private[component] object NameInfoComponent {
@ -35,7 +35,7 @@ private[component] object NameInfoComponent {
private final val ModuleInfoPattern = """(.+)-- Defined in [`]([\w\.\-]+)[']""".r
private final val InfixInfoPattern = """(infix.+)""".r
private final val Cache: LoadingCache[Key, NameInfoResult] = Scaffeine().build((k: Key) => findNameInfoResult(k))
private final val Cache: AsyncLoadingCache[Key, NameInfoResult] = Scaffeine().buildAsync((k: Key) => findNameInfoResult(k))
private case class Key(psiFile: PsiFile, name: String)
@ -47,12 +47,11 @@ private[component] object NameInfoComponent {
}
def invalidate(psiFile: PsiFile): Unit = {
Cache.asMap().filter(_._1.psiFile == psiFile).keys.foreach(Cache.invalidate)
Cache.synchronous.asMap().filter(_._1.psiFile == psiFile).keys.foreach(Cache.synchronous.invalidate)
}
def invalidateAll(project: Project): Unit = {
Cache.asMap().map(_._1.psiFile).filter(_.getProject == project).foreach(invalidate)
NameInfoByModuleComponent.invalidateAll(project)
Cache.synchronous.asMap().map(_._1.psiFile).filter(_.getProject == project).foreach(invalidate)
}
private def findNameInfo(qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String]): NameInfoResult = {
@ -68,46 +67,32 @@ private[component] object NameInfoComponent {
ProgressManager.checkCanceled()
Cache.getIfPresent(key) match {
ScalaFutureUtil.waitForValue(qualifiedNameElement.getProject, Cache.get(key), s"Getting name info for $name") match {
case Some(result) =>
result match {
case Right(_) => result
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.invalidate(key)
Cache.synchronous.invalidate(key)
result
case _ => result
}
case None =>
ProgressManager.checkCanceled()
val result = findNameInfoResult(key)
result match {
case Right(_) =>
Cache.put(key, result)
result
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotAvailable(_)) =>
result
case Left(_) =>
Cache.put(key, result)
result
}
Cache.synchronous.invalidate(key)
Left(ReadActionTimeout("Getting name info"))
}
}
private def findNameInfoResult(key: Key): NameInfoResult = {
ProgressManager.checkCanceled()
val psiFile = key.psiFile
val project = psiFile.getProject
val name = key.name
val isSourceFile = HaskellProjectUtil.isSourceFile(psiFile)
ProgressManager.checkCanceled()
if (isSourceFile) {
StackReplsManager.getProjectRepl(psiFile) match {
case Some(repl) =>
if (!repl.available) {
Left(ReplNotAvailable)
} else {
ProgressManager.checkCanceled()
repl.findInfo(psiFile, name) match {
case Some(output) if output.stdoutLines.nonEmpty & output.stderrLines.isEmpty => Right(createNameInfos(project, output))
case None => Left(ReplNotAvailable)
@ -120,7 +105,6 @@ private[component] object NameInfoComponent {
HaskellPsiUtil.findModuleName(psiFile) match {
case None => Left(NoInfoAvailable(key.name, psiFile.getName))
case Some(mn) =>
ProgressManager.checkCanceled()
StackReplsManager.getGlobalRepl(project) match {
case Some(repl) =>
repl.findInfo(mn, name) match {
@ -153,41 +137,6 @@ private[component] object NameInfoComponent {
}
result
}
object NameInfoByModuleComponent {
private case class Key(project: Project, moduleName: String, name: String)
private final val Cache: LoadingCache[Key, NameInfoResult] = Scaffeine().build((k: Key) => loadByModuleAndName(k))
private def loadByModuleAndName(key: Key): NameInfoResult = {
findNameInfos(key)
}
private def findNameInfos(key: Key): Either[NoInfo, Iterable[NameInfo]] = {
val output = StackReplsManager.getGlobalRepl(key.project).flatMap(_.findInfo(key.moduleName, key.name))
output match {
case Some(o) => Right(createNameInfos(key.project, o))
case None => Left(ReplNotAvailable)
}
}
def findNameInfoByModuleName(project: Project, moduleName: String, name: String): NameInfoResult = {
val key = Key(project, moduleName, name)
val result = Cache.get(key)
result match {
case Right(_) => result
case Left(_) =>
Cache.invalidate(key)
result
}
}
private[component] def invalidateAll(project: Project): Unit = {
Cache.asMap().filter(_._1.project == project).keys.foreach(Cache.invalidate)
}
}
}
object NameInfoComponentResult {

View File

@ -16,10 +16,10 @@
package intellij.haskell.external.component
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.project.Project
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
import intellij.haskell.util.HaskellProjectUtil
import intellij.haskell.util.{HaskellProjectUtil, ScalaFutureUtil}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future, blocking}
@ -32,14 +32,14 @@ private[component] object StackComponentGlobalInfoComponent {
private type Result = Option[StackComponentGlobalInfo]
private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => createStackInfo(k))
private final val Cache: AsyncLoadingCache[Key, Result] = Scaffeine().buildAsync((k: Key) => createStackInfo(k))
def findStackComponentGlobalInfo(stackComponentInfo: StackComponentInfo): Option[StackComponentGlobalInfo] = {
val key = Key(stackComponentInfo)
Cache.get(key) match {
ScalaFutureUtil.waitForValue(stackComponentInfo.module.getProject, Cache.get(key), "Getting global info").flatten match {
case result@Some(_) => result
case _ =>
Cache.invalidate(key)
Cache.synchronous.invalidate(key)
None
}
}
@ -74,8 +74,8 @@ private[component] object StackComponentGlobalInfoComponent {
}
def invalidate(project: Project): Unit = {
val keys = Cache.asMap().keys.filter(_.stackComponentInfo.module.getProject == project)
keys.foreach(Cache.invalidate)
val keys = Cache.synchronous.asMap().keys.filter(_.stackComponentInfo.module.getProject == project)
keys.foreach(Cache.synchronous.invalidate)
}
}

View File

@ -27,6 +27,7 @@ import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.roots.{ModifiableRootModel, ModuleRootModificationUtil}
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.{PsiManager, PsiTreeChangeAdapter, PsiTreeChangeEvent}
import intellij.haskell.action.HaskellReformatAction
import intellij.haskell.annotator.HaskellAnnotator
import intellij.haskell.external.execution.StackCommandLine
@ -243,7 +244,9 @@ object StackProjectManager {
projectFilesWithImportedModuleNames match {
case Some(fm) =>
fm.foreach { case (_, moduleNames) =>
fm.foreach { case (pf, moduleNames) =>
HaskellPsiUtil.findModuleName(pf).foreach(BrowseModuleComponent.findModuleIdentifiersSync(project, _))
moduleNames.foreach(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn))
HaskellNotificationGroup.logInfoEvent(project, "Loading module identifiers " + moduleNames.mkString(", "))
moduleNames.foreach(m => BrowseModuleComponent.findModuleIdentifiersSync(project, m))
@ -305,6 +308,25 @@ object StackProjectManager {
}
}
if (!project.isDisposed) {
PsiManager.getInstance(project).addPsiTreeChangeListener(new PsiTreeChangeAdapter {
private def invalidateTypeInfo(event: PsiTreeChangeEvent): Unit = {
val element = Option(event.getOldChild).orElse(Option(event.getNewChild)).flatMap(e => HaskellPsiUtil.findExpression(e)).orElse(Option(event.getParent))
val elements = element.map(e => HaskellPsiUtil.findQualifiedNamedElements(e)).getOrElse(Seq()).toSeq
TypeInfoComponent.invalidateElements(event.getFile, elements)
}
override def childReplaced(event: PsiTreeChangeEvent): Unit = {
invalidateTypeInfo(event)
}
override def childrenChanged(event: PsiTreeChangeEvent): Unit = {
invalidateTypeInfo(event)
}
})
}
progressIndicator.setText("Busy preloading caches")
if (!preloadLibraryFilesCacheFuture.isDone || !preloadStackComponentInfoCache.isDone || !preloadLibraryIdentifiersCacheFuture.isDone || !replsLoad.isDone) {
FutureUtil.waitForValue(project, preloadStackComponentInfoCache, "preloading project cache", 600)

View File

@ -23,7 +23,9 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.{PsiElement, PsiFile}
import intellij.haskell.external.repl.{ProjectStackRepl, StackReplsManager}
import intellij.haskell.psi._
import intellij.haskell.util.{HaskellFileUtil, LineColumnPosition}
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, LineColumnPosition}
import scala.concurrent.TimeoutException
private[component] object TypeInfoComponent {
@ -64,14 +66,18 @@ private[component] object TypeInfoComponent {
case None => Left(ReplNotAvailable)
}
}
}.getOrElse(Left(NoInfoAvailable(selectionModel.getSelectedText, psiFile.getName)))
}.getOrElse(Left(NoInfoAvailable(selectionModel.getSelectedText, psiFile.getName)))
} else {
Left(ModuleNotAvailable(moduleName.getOrElse(psiFile.getName)))
}
}
def invalidateElements(psiFile: PsiFile, namedElements: Seq[HaskellQualifiedNameElement]): Unit = {
namedElements.foreach(qne => Cache.invalidate(Key(psiFile, qne)))
}
def invalidate(psiFile: PsiFile): Unit = {
val keys = Cache.asMap().filter(_._1.psiFile == psiFile).keys
val keys = Cache.asMap().filter(_._1.psiFile == psiFile).keys.filterNot(_.qualifiedNameElement.isValid)
Cache.invalidateAll(keys)
}
@ -93,6 +99,7 @@ private[component] object TypeInfoComponent {
t = qne.getText
_ = ProgressManager.checkCanceled()
mn = HaskellPsiUtil.findModuleName(psiFile)
if key.qualifiedNameElement.isValid
} yield {
ProgressManager.checkCanceled()
repl: ProjectStackRepl => repl.findTypeInfo(mn, key.psiFile, sp.lineNr, sp.columnNr, ep.lineNr, ep.columnNr, t)
@ -116,26 +123,18 @@ private[component] object TypeInfoComponent {
}
private def findTypeInfo(key: Key): TypeInfoResult = {
Cache.getIfPresent(key) match {
case Some(result) => result match {
try {
val result = ApplicationUtil.runReadAction(Cache.get(key))
result match {
case Right(_) => result
case Left(NoInfoAvailable(_, _)) | Left(NoMatchingExport) =>
result
case Left(ReplNotAvailable) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.invalidate(key)
result
case _ => result
}
case None =>
val result = Cache.get(key)
result match {
case Right(_) =>
result
case Left(NoInfoAvailable(_, _)) | Left(NoMatchingExport) =>
result
case Left(ReplNotAvailable) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
Cache.invalidate(key)
result
}
} catch {
case e: TimeoutException => Left(ReadActionTimeout(e.getMessage))
}
}
}

View File

@ -18,28 +18,45 @@ package intellij.haskell.external.repl
import com.intellij.openapi.project.Project
import intellij.haskell.external.repl.StackRepl.StackReplOutput
import intellij.haskell.util.ScalaFutureUtil
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
case class GlobalStackRepl(project: Project, replTimeout: Int) extends StackRepl(project, None, Seq("--no-package-hiding", "--no-load"), replTimeout) {
@volatile
private[this] var loadedModuleName: Option[String] = None
def clearLoadedModules(): Unit = {
loadedModuleName = None
}
def getModuleIdentifiers(moduleName: String): Option[StackReplOutput] = synchronized {
execute(s":browse! $moduleName")
def getModuleIdentifiers(moduleName: String): Option[StackReplOutput] = {
ScalaFutureUtil.waitForValue(project, Future {
blocking {
synchronized {
execute(s":browse! $moduleName")
}
}
}, ":browse in GlobalStackRepl").flatten
}
def findInfo(moduleName: String, name: String): Option[StackReplOutput] = synchronized {
loadModule(moduleName)
def findInfo(moduleName: String, name: String): Option[StackReplOutput] = {
ScalaFutureUtil.waitForValue(project, Future {
blocking {
synchronized {
loadModule(moduleName)
if (loadedModuleName.contains(moduleName)) {
execute(s":info $name")
} else {
// No info means never info because it's library
Some(StackReplOutput())
}
if (loadedModuleName.contains(moduleName)) {
execute(s":info $name")
} else {
// No info means NEVER info because it's library
Some(StackReplOutput())
}
}
}
}, ":info in GlobalStackRepl").flatten
}
override def restart(forceExit: Boolean): Unit = synchronized {

View File

@ -18,7 +18,6 @@ package intellij.haskell.external.repl
import java.util.concurrent.ConcurrentHashMap
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.HaskellNotificationGroup
@ -26,7 +25,6 @@ import intellij.haskell.external.component.HaskellComponentsManager.StackCompone
import intellij.haskell.external.repl.StackRepl.StackReplOutput
import intellij.haskell.util.{HaskellFileUtil, ScalaFutureUtil}
import scala.concurrent.duration._
import scala.concurrent.{Future, blocking}
import scala.jdk.CollectionConverters._
@ -66,8 +64,6 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
import scala.concurrent.ExecutionContext.Implicits.global
private def isReadAccessAllowed = ApplicationManager.getApplication.isReadAccessAllowed
def findTypeInfo(moduleName: Option[String], psiFile: PsiFile, startLineNr: Int, startColumnNr: Int, endLineNr: Int, endColumnNr: Int, expression: String): Option[StackReplOutput] = {
val filePath = getFilePath(psiFile)
@ -77,11 +73,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
if (isReadAccessAllowed) {
ScalaFutureUtil.waitWithCheckCancelled(project, Future(execute), "Wait on :type-at in ProjectStackRepl", 30.seconds).flatten
} else {
execute
}
ScalaFutureUtil.waitForValue(project, Future(execute), ":type-at in ProjectStackRepl").flatten
}
def findLocationInfo(moduleName: Option[String], psiFile: PsiFile, startLineNr: Int, startColumnNr: Int, endLineNr: Int, endColumnNr: Int, expression: String): Option[StackReplOutput] = {
@ -93,11 +85,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
if (isReadAccessAllowed) {
ScalaFutureUtil.waitWithCheckCancelled(project, Future(execute), "Wait on :loc-at in ProjectStackRepl", timeout = 30.seconds).flatten
} else {
execute
}
ScalaFutureUtil.waitForValue(project, Future(execute), ":loc-at in ProjectStackRepl").flatten
}
def findInfo(psiFile: PsiFile, name: String): Option[StackReplOutput] = {
@ -107,11 +95,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
if (isReadAccessAllowed) {
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, Future(execute), "Wait on :info in ProjectStackRepl", 30.seconds).flatten
} else {
execute
}
ScalaFutureUtil.waitForValue(psiFile.getProject, Future(execute), ":info in ProjectStackRepl").flatten
}
def isModuleLoaded(moduleName: String): Boolean = {
@ -195,13 +179,20 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
def getModuleIdentifiers(moduleName: String, psiFile: Option[PsiFile]): Option[StackReplOutput] = synchronized {
if (psiFile.isEmpty || isBrowseModuleLoaded(moduleName) || psiFile.exists(pf => load(pf, fileModified = false, mustBeByteCode = false).exists(_._2 == false))) {
execute(s":browse! $moduleName")
} else {
HaskellNotificationGroup.logInfoEvent(project, s"Couldn't get module identifiers for module $moduleName because file ${psiFile.map(_.getName).getOrElse("-")} isn't loaded")
None
}
def getModuleIdentifiers(project: Project, moduleName: String, psiFile: Option[PsiFile]): Option[StackReplOutput] = {
ScalaFutureUtil.waitForValue(project,
Future {
blocking {
synchronized {
if (psiFile.isEmpty || isBrowseModuleLoaded(moduleName) || psiFile.exists(pf => load(pf, fileModified = false, mustBeByteCode = false).exists(_._2 == false))) {
execute(s":browse! $moduleName")
} else {
HaskellNotificationGroup.logInfoEvent(project, s"Couldn't get module identifiers for module $moduleName because file ${psiFile.map(_.getName).getOrElse("-")} isn't loaded")
None
}
}
}
}, "getModuleIdentifiers in ProjectStackRepl").flatten
}
override def restart(forceExit: Boolean): Unit = synchronized {

View File

@ -158,7 +158,7 @@ abstract class StackRepl(project: Project, componentInfo: Option[StackComponentI
val timeout = if (ghciCommand == GhciCommand.Load || ghciCommand == GhciCommand.Browse) LoadTimeout else DefaultTimeout
val deadline = timeout.fromNow
while (deadline.hasTimeLeft && !hasReachedEndOfOutput) {
while (deadline.hasTimeLeft && !hasReachedEndOfOutput && !project.isDisposed) {
drainQueues()
// We have to wait...
@ -255,7 +255,7 @@ abstract class StackRepl(project: Project, componentInfo: Option[StackComponentI
}
val deadline = DefaultTimeout.fromNow
while (process.isAlive() && deadline.hasTimeLeft && !isStarted && !hasDependencyError) {
while (process.isAlive() && deadline.hasTimeLeft && !isStarted && !hasDependencyError && !project.isDisposed) {
// We have to wait till REPL is started
Thread.sleep(DelayBetweenReadsInMillis)
}

View File

@ -17,11 +17,13 @@
package intellij.haskell.inspection
import com.intellij.codeInspection._
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.tree.IElementType
import com.intellij.psi.{PsiElement, PsiFile, TokenType}
import com.intellij.util.WaitFor
import com.intellij.util.concurrency.AppExecutorUtil
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.external.component.{HLintComponent, HLintInfo}
import intellij.haskell.psi.HaskellTypes._
@ -45,21 +47,24 @@ class HLintInspectionTool extends LocalInspectionTool {
ProgressManager.checkCanceled()
new WaitFor(500, 1) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
!HaskellFileUtil.isDocumentUnsaved(document)
ReadAction.nonBlocking {
ScalaUtil.callable {
new WaitFor(500, 1) {
override def condition(): Boolean = {
!HaskellFileUtil.isDocumentUnsaved(document)
}
}
}
}
}.expireWith(psiFile.getProject).cancelWith(ProgressManager.getInstance().getProgressIndicator).submit(AppExecutorUtil.getAppExecutorService).get
ProgressManager.checkCanceled()
val result = ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject,
val result = ScalaFutureUtil.waitForValue(psiFile.getProject,
Future {
blocking {
HLintComponent.check(psiFile)
}
}, "Running HLint", timeout = 2.seconds) match {
}, "Running HLint", 5.second) match {
case Some(r) => r
case None => Seq()
}

View File

@ -216,7 +216,10 @@ object HaskellReference {
}.getOrElse(Left(ModuleNotAvailable(moduleNames.mkString(" | "))))
}
import scala.concurrent.ExecutionContext.Implicits.global
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 {
@ -226,11 +229,24 @@ object HaskellReference {
val identifiers = files.flatMap(f => findIdentifierInFileByName(f, name, prioIdInExpression))
if (identifiers.isEmpty) {
val importedModuleNames = files.flatMap(f => FileModuleIdentifiers.findAvailableModuleIdentifiers(f).filter(mid => mid.name == name || mid.name == "_" + name || mid.name == mid.moduleName + "." + name).map(_.moduleName))
val importedModuleNames = files.flatMap { f => {
if (HaskellProjectUtil.isSourceFile(f)) {
FileModuleIdentifiers.findAvailableModuleIdentifiers(f)
} else {
HaskellPsiUtil.findModuleName(f).flatMap(mn => ScalaFutureUtil.waitForValue(project,
HaskellComponentsManager.findModuleIdentifiers(project, mn), "find module identifiers in HaskellReference")).flatten.getOrElse(Iterable())
}
}
}.filter(mid => mid.name == name || mid.name == "_" + name || mid.name == mid.moduleName + "." + name).map(_.moduleName)
if (importedModuleNames.isEmpty) {
Seq()
} else {
findIdentifiersByModuleAndName2(project, importedModuleNames, name, prioIdInExpression)
if (moduleNames == Seq(mn)) {
val moduleNames = files.flatMap(HaskellPsiUtil.findImportDeclarations).flatMap(_.getModuleName)
findIdentifiersByModuleAndName2(project, moduleNames, name, prioIdInExpression)
} else {
findIdentifiersByModuleAndName2(project, importedModuleNames, name, prioIdInExpression)
}
}
} else {
Seq((mn, identifiers))

View File

@ -16,13 +16,15 @@
package intellij.haskell.util
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.{ApplicationManager, ReadAction}
import com.intellij.openapi.progress.util.ReadTask.Continuation
import com.intellij.openapi.progress.util.{ProgressIndicatorUtils, ReadTask}
import com.intellij.openapi.progress.{ProgressIndicator, ProgressManager}
import com.intellij.openapi.project.{DumbService, Project}
import com.intellij.util.concurrency.AppExecutorUtil
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.external.component.{NoInfo, ReadActionTimeout}
@ -30,14 +32,6 @@ import scala.concurrent.duration._
object ApplicationUtil {
def timeout: Int = {
if (ApplicationManager.getApplication.isDispatchThread) {
50
} else {
60000
}
}
private def isReadAccessAllowed = {
ApplicationManager.getApplication.isReadAccessAllowed
}
@ -46,13 +40,14 @@ object ApplicationUtil {
if (isReadAccessAllowed) {
f
} else {
ApplicationManager.getApplication.runReadAction(ScalaUtil.computable(f))
val progressIndicator = Option(ProgressManager.getInstance().getProgressIndicator)
val readAction = ReadAction.nonBlocking(ScalaUtil.callable(f))
progressIndicator.foreach(readAction.cancelWith)
readAction.submit(AppExecutorUtil.getAppExecutorService).get(2, TimeUnit.SECONDS)
}
}
private final val RunInReadActionTimeout = 50.millis
def runInReadActionWithWriteActionPriority[A](project: Project, f: => A, readActionDescription: => String, timeout: FiniteDuration = RunInReadActionTimeout): Either[NoInfo, A] = {
def runInReadActionWithWriteActionPriority[A](project: Project, f: => A, readActionDescription: => String): Either[NoInfo, A] = {
val r = new AtomicReference[A]
if (isReadAccessAllowed) {
@ -67,15 +62,15 @@ object ApplicationUtil {
}
}
val deadline = timeout.fromNow
val deadline = 100.millis.fromNow
while (!run() && deadline.hasTimeLeft && !project.isDisposed) {
Thread.sleep(1)
Thread.sleep(2)
}
val result = r.get()
if (result == null) {
HaskellNotificationGroup.logInfoEvent(project, s"Timeout ($timeout) in runInReadActionWithWriteActionPriority while $readActionDescription")
HaskellNotificationGroup.logInfoEvent(project, s"Timeout in runInReadActionWithWriteActionPriority while $readActionDescription")
Left(ReadActionTimeout(readActionDescription))
} else {
Right(result)
@ -83,9 +78,7 @@ object ApplicationUtil {
}
}
private final val ScheduleInReadActionTimeout = 60.seconds
def scheduleInReadActionWithWriteActionPriority[A](project: Project, f: => A, scheduleInReadActionDescription: => String, timeout: FiniteDuration = ScheduleInReadActionTimeout, reschedule: Boolean = true): Either[NoInfo, A] = {
def scheduleInReadActionWithWriteActionPriority[A](project: Project, f: => A, scheduleInReadActionDescription: => String, timeout: FiniteDuration = 60.seconds, reschedule: Boolean = true): Either[NoInfo, A] = {
val r = new AtomicReference[A]
var cancelled = false
@ -127,7 +120,7 @@ object ApplicationUtil {
val result = r.get()
if (result == null) {
HaskellNotificationGroup.logInfoEvent(project, s"Timeout ($timeout) in scheduleInReadActionWithWriteActionPriority while $scheduleInReadActionDescription and reschedule $reschedule")
HaskellNotificationGroup.logInfoEvent(project, s"Timeout in scheduleInReadActionWithWriteActionPriority while $scheduleInReadActionDescription and reschedule $reschedule")
Left(ReadActionTimeout(scheduleInReadActionDescription))
} else {
Right(result)

View File

@ -1,5 +1,6 @@
package intellij.haskell.util
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.util.WaitFor
@ -10,35 +11,34 @@ import scala.concurrent.{Await, Future, TimeoutException}
object ScalaFutureUtil {
def waitForValue[T](project: Project, future: Future[T], actionDescription: String, timeout: FiniteDuration = 5.seconds): Option[T] = {
try {
Option(Await.result(future, timeout))
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout while $actionDescription")
None
}
}
def waitWithCheckCancelled[T](project: Project, future: Future[T], actionDescription: String, timeout: FiniteDuration = 5.seconds): Option[T] = {
try {
new WaitFor(timeout.toMillis.toInt, 1) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
future.isCompleted || project.isDisposed
def waitForValue[T](project: Project, future: Future[T], actionDescription: String, timeout: FiniteDuration = 200.millis): Option[T] = {
if (ApplicationManager.getApplication.isReadAccessAllowed) {
try {
new WaitFor(timeout.toMillis.toInt, 10) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
future.isCompleted || project.isDisposed
}
}
}
if (project.isDisposed) {
None
} else {
Option(Await.result(future, 1.milli))
if (project.isDisposed) {
None
} else {
Option(Await.result(future, 1.milli))
}
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout in read waitForValue while $actionDescription")
None
}
} else {
try {
Option(Await.result(future, 10.second))
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout in waitForValue while $actionDescription")
None
}
} catch {
case _: TimeoutException =>
HaskellNotificationGroup.logInfoEvent(project, s"Timeout in waitWithCheckCancelled while $actionDescription")
None
}
}
}

View File

@ -52,7 +52,7 @@ object HaskellModuleNameIndex {
type Result = Either[NoInfo, Seq[(PsiFile, Boolean)]]
private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => find(k, 2.second, reschedule = false))
private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => find(k, 5.second, reschedule = false))
private def find(key: Key, timeout: FiniteDuration, reschedule: Boolean): Either[NoInfo, Seq[(PsiFile, Boolean)]] = {
val project = key.project
@ -68,7 +68,8 @@ object HaskellModuleNameIndex {
Right(psiFiles.flatMap { case (pf, isPf) => pf.toSeq.map((_, isPf)) })
}
}
case Left(noInfo) => Left(noInfo)
case Left(noInfo) =>
Left(noInfo)
}
}
@ -94,16 +95,14 @@ object HaskellModuleNameIndex {
// So using Cache is solution because Cache.get blocks next request for same key while busy.
def findFilesByModuleName2(project: Project, moduleName: String): Either[NoInfo, Seq[(PsiFile, isProjectFile)]] = {
val key = Key(project, moduleName)
Cache.getIfPresent(key) match {
case Some(r@Right(_)) => r
case _ =>
Cache.get(key) match {
case r@Right(_) => r
case Left(noInfo) =>
// No invalidate here to prevent UI becomes unresponsive after many calls for same module name which module does not exists
// In LoadComponent the "not found" entries will be invalidated eventually
Left(noInfo)
}
val result = Cache.get(key)
result match {
case Right(_) => result
case Left(_) =>
// No invalidate here to prevent UI becomes unresponsive after many calls for same module name which module does not exists
// In LoadComponent the "not found" entries will be invalidated eventually
result
case _ => result
}
}