Various improvements/fixes including changes to support -fobject-code (issue #386) and changes to make renaming more solid (issues #384 and #385)

This commit is contained in:
Rik van der Kleij 2019-03-10 15:35:12 +01:00
parent 3f73155359
commit a15653f1bd
53 changed files with 984 additions and 1158 deletions

View File

@ -47,6 +47,8 @@ public class HaskellParser implements PsiParser, LightPsiParser {
r = conid(b, 0);
} else if (t == HS_CONOP) {
r = conop(b, 0);
} else if (t == HS_CONSTR) {
r = constr(b, 0);
} else if (t == HS_CONSTR_1) {
r = constr1(b, 0);
} else if (t == HS_CONSTR_2) {
@ -1547,13 +1549,15 @@ public class HaskellParser implements PsiParser, LightPsiParser {
/* ********************************************************** */
// type_signature | constr1 | constr2 | constr3
static boolean constr(PsiBuilder b, int l) {
public static boolean constr(PsiBuilder b, int l) {
if (!recursion_guard_(b, l, "constr")) return false;
boolean r;
Marker m = enter_section_(b, l, _NONE_, HS_CONSTR, "<constr>");
r = type_signature(b, l + 1);
if (!r) r = constr1(b, l + 1);
if (!r) r = constr2(b, l + 1);
if (!r) r = constr3(b, l + 1);
exit_section_(b, l, m, r, false, null);
return r;
}

View File

@ -0,0 +1,20 @@
// This is a generated file. Not intended for manual editing.
package intellij.haskell.psi;
import org.jetbrains.annotations.Nullable;
public interface HaskellConstr extends HaskellCompositeElement {
@Nullable
HaskellConstr1 getConstr1();
@Nullable
HaskellConstr2 getConstr2();
@Nullable
HaskellConstr3 getConstr3();
@Nullable
HaskellTypeSignature getTypeSignature();
}

View File

@ -15,13 +15,7 @@ public interface HaskellDataDeclaration extends HaskellDataConstructorDeclaratio
HaskellCcontext getCcontext();
@NotNull
List<HaskellConstr1> getConstr1List();
@NotNull
List<HaskellConstr2> getConstr2List();
@NotNull
List<HaskellConstr3> getConstr3List();
List<HaskellConstr> getConstrList();
@Nullable
HaskellDataDeclarationDeriving getDataDeclarationDeriving();

View File

@ -6,7 +6,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import org.jetbrains.annotations.NotNull;
public interface HaskellQConQualifier1 extends HaskellQualifierElement {
public interface HaskellQConQualifier1 extends HaskellQualifierElement, HaskellNamedElement {
@NotNull
HaskellConid getConid();

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface HaskellQConQualifier2 extends HaskellQualifierElement {
public interface HaskellQConQualifier2 extends HaskellQualifierElement, HaskellNamedElement {
@NotNull
List<HaskellConid> getConidList();

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface HaskellQConQualifier3 extends HaskellQualifierElement {
public interface HaskellQConQualifier3 extends HaskellQualifierElement, HaskellNamedElement {
@NotNull
List<HaskellConid> getConidList();

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface HaskellQConQualifier4 extends HaskellQualifierElement {
public interface HaskellQConQualifier4 extends HaskellQualifierElement, HaskellNamedElement {
@NotNull
List<HaskellConid> getConidList();

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface HaskellQualifier extends HaskellQualifierElement {
public interface HaskellQualifier extends HaskellQualifierElement, HaskellNamedElement {
@NotNull
List<HaskellConid> getConidList();

View File

@ -20,6 +20,7 @@ public interface HaskellTypes {
IElementType HS_CON = new HaskellCompositeElementType("HS_CON");
IElementType HS_CONID = HaskellElementTypeFactory.factory("HS_CONID");
IElementType HS_CONOP = new HaskellCompositeElementType("HS_CONOP");
IElementType HS_CONSTR = new HaskellCompositeElementType("HS_CONSTR");
IElementType HS_CONSTR_1 = new HaskellCompositeElementType("HS_CONSTR_1");
IElementType HS_CONSTR_2 = new HaskellCompositeElementType("HS_CONSTR_2");
IElementType HS_CONSTR_3 = new HaskellCompositeElementType("HS_CONSTR_3");
@ -193,6 +194,8 @@ public interface HaskellTypes {
return new HaskellConidImpl(node);
} else if (type == HS_CONOP) {
return new HaskellConopImpl(node);
} else if (type == HS_CONSTR) {
return new HaskellConstrImpl(node);
} else if (type == HS_CONSTR_1) {
return new HaskellConstr1Impl(node);
} else if (type == HS_CONSTR_2) {

View File

@ -54,6 +54,10 @@ public class HaskellVisitor extends PsiElementVisitor {
visitCNameElement(o);
}
public void visitConstr(@NotNull HaskellConstr o) {
visitCompositeElement(o);
}
public void visitConstr1(@NotNull HaskellConstr1 o) {
visitCompositeElement(o);
}
@ -229,18 +233,22 @@ public class HaskellVisitor extends PsiElementVisitor {
public void visitQConQualifier1(@NotNull HaskellQConQualifier1 o) {
visitQualifierElement(o);
// visitNamedElement(o);
}
public void visitQConQualifier2(@NotNull HaskellQConQualifier2 o) {
visitQualifierElement(o);
// visitNamedElement(o);
}
public void visitQConQualifier3(@NotNull HaskellQConQualifier3 o) {
visitQualifierElement(o);
// visitNamedElement(o);
}
public void visitQConQualifier4(@NotNull HaskellQConQualifier4 o) {
visitQualifierElement(o);
// visitNamedElement(o);
}
public void visitQName(@NotNull HaskellQName o) {
@ -257,6 +265,7 @@ public class HaskellVisitor extends PsiElementVisitor {
public void visitQualifier(@NotNull HaskellQualifier o) {
visitQualifierElement(o);
// visitNamedElement(o);
}
public void visitReservedId(@NotNull HaskellReservedId o) {

View File

@ -0,0 +1,50 @@
// This is a generated file. Not intended for manual editing.
package intellij.haskell.psi.impl;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import intellij.haskell.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class HaskellConstrImpl extends HaskellCompositeElementImpl implements HaskellConstr {
public HaskellConstrImpl(ASTNode node) {
super(node);
}
public void accept(@NotNull HaskellVisitor visitor) {
visitor.visitConstr(this);
}
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof HaskellVisitor) accept((HaskellVisitor) visitor);
else super.accept(visitor);
}
@Override
@Nullable
public HaskellConstr1 getConstr1() {
return PsiTreeUtil.getChildOfType(this, HaskellConstr1.class);
}
@Override
@Nullable
public HaskellConstr2 getConstr2() {
return PsiTreeUtil.getChildOfType(this, HaskellConstr2.class);
}
@Override
@Nullable
public HaskellConstr3 getConstr3() {
return PsiTreeUtil.getChildOfType(this, HaskellConstr3.class);
}
@Override
@Nullable
public HaskellTypeSignature getTypeSignature() {
return PsiTreeUtil.getChildOfType(this, HaskellTypeSignature.class);
}
}

View File

@ -36,20 +36,8 @@ public class HaskellDataDeclarationImpl extends HaskellCompositeElementImpl impl
@Override
@NotNull
public List<HaskellConstr1> getConstr1List() {
return PsiTreeUtil.getChildrenOfTypeAsList(this, HaskellConstr1.class);
}
@Override
@NotNull
public List<HaskellConstr2> getConstr2List() {
return PsiTreeUtil.getChildrenOfTypeAsList(this, HaskellConstr2.class);
}
@Override
@NotNull
public List<HaskellConstr3> getConstr3List() {
return PsiTreeUtil.getChildrenOfTypeAsList(this, HaskellConstr3.class);
public List<HaskellConstr> getConstrList() {
return PsiTreeUtil.getChildrenOfTypeAsList(this, HaskellConstr.class);
}
@Override

View File

@ -21,8 +21,7 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.{PsiElement, PsiFile}
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.editor.HaskellCompletionContributor
import intellij.haskell.external.component.{HaskellComponentsManager, StackProjectManager}
import intellij.haskell.external.component.{FileModuleIdentifiers, HaskellComponentsManager, StackProjectManager}
import intellij.haskell.psi._
import intellij.haskell.util.{HaskellEditorUtil, StringUtil}
@ -93,23 +92,16 @@ object ShowTypeAction {
}
private def findTypeSignatureFromScope(psiFile: PsiFile, psiElement: PsiElement) = {
if (HaskellPsiUtil.findExpressionParent(psiElement).isDefined) {
HaskellPsiUtil.findQualifiedNameParent(psiElement).flatMap(qualifiedNameElement => {
if (HaskellPsiUtil.findExpression(psiElement).isDefined) {
HaskellPsiUtil.findQualifiedName(psiElement).flatMap(qualifiedNameElement => {
val definedInFile = HaskellComponentsManager.findDefinitionLocation(psiFile, qualifiedNameElement, None).toOption.map(_.namedElement.getContainingFile)
if (definedInFile.contains(psiFile)) {
// To prevent stale type info while compilation errors
None
} else {
val name = qualifiedNameElement.getName
val result = for {
moduleName <- HaskellPsiUtil.findModuleName(psiFile)
stackInfo <- HaskellComponentsManager.findStackComponentInfo(psiFile)
globalInfo <- HaskellComponentsManager.findStackComponentGlobalInfo(stackInfo)
declaration <- HaskellCompletionContributor.getAvailableModuleIdentifiers(globalInfo, stackInfo, psiFile, Some(moduleName)).find(_.name == name).map(_.declaration)
} yield {
declaration
}
result.orElse(HaskellPsiUtil.findHaskellDeclarationElements(psiFile).find(_.getIdentifierElements.exists(_.getName == name)).map(_.getText.replaceAll("""\s+""", " ")))
val declaration = FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).find(_.name == name).map(_.declaration)
declaration.orElse(HaskellPsiUtil.findHaskellDeclarationElements(psiFile).find(_.getIdentifierElements.exists(_.getName == name)).map(_.getText.replaceAll("""\s+""", " ")))
}
})
} else {

View File

@ -43,7 +43,7 @@ class ShowTypeStickyAction extends AnAction {
private def untilNameElementBackwards(element: Option[PsiElement]): Option[HaskellQualifiedNameElement] = {
element match {
case Some(e) =>
HaskellPsiUtil.findQualifiedNameParent(e) match {
HaskellPsiUtil.findQualifiedName(e) match {
case None => untilNameElementBackwards(Option(e.getPrevSibling))
case qualifiedName => qualifiedName
}

View File

@ -63,7 +63,7 @@ class HaskellAnnotator extends ExternalAnnotator[(PsiFile, Option[PsiElement]),
case (_, Some(_)) if !psiFile.isValid => null
case (_, Some(_)) =>
val currentElement = Option(psiFile.findElementAt(editor.getCaretModel.getOffset)).
find(e => HaskellPsiUtil.findExpressionParent(e).isDefined).
find(e => HaskellPsiUtil.findExpression(e).isDefined).
flatMap(e => Option(PsiTreeUtil.prevVisibleLeaf(e))).filter(_.isValid)
(psiFile, currentElement)
}
@ -381,7 +381,7 @@ class DeprecatedUseAction(name: String, suggestion: String) extends HaskellBaseI
private object IntentionHelper {
def replace(project: Project, editor: Editor, file: PsiFile, newName: String): Unit = {
val offset = editor.getCaretModel.getOffset
Option(file.findElementAt(offset)).flatMap(HaskellPsiUtil.findQualifiedNameParent) match {
Option(file.findElementAt(offset)).flatMap(HaskellPsiUtil.findQualifiedName) match {
case Some(e) =>
if (e.getText.startsWith("`") && e.getText.endsWith("`")) {
e.replace(HaskellElementFactory.createQualifiedNameElement(project, s"`$newName`"))
@ -494,8 +494,8 @@ class ImportAloneInstancesAction(importDecl: String) extends HaskellBaseIntentio
Option(file.findElementAt(offset)) match {
case Some(e) =>
for {
importDeclarations <- HaskellPsiUtil.findImportDeclarationsParent(e)
importDeclaration <- HaskellPsiUtil.findImportDeclarationParent(e)
importDeclarations <- HaskellPsiUtil.findImportDeclarations(e)
importDeclaration <- HaskellPsiUtil.findImportDeclaration(e)
importDeclElement <- HaskellElementFactory.createImportDeclaration(project, importDecl)
} yield importDeclarations.getNode.replaceChild(importDeclaration.getNode, importDeclElement.getNode)
case None => ()

View File

@ -78,7 +78,7 @@ trait ModulePartImpl extends CabalNamedElementImpl {
case Some(m) => GlobalSearchScope.moduleScope(m)
case None => GlobalSearchScopesCore.projectProductionScope(getProject)
}
val haskellFile = HaskellModuleNameIndex.findFileByModuleName(getProject, moduleName).toOption.flatMap(_.headOption)
val haskellFile = HaskellModuleNameIndex.findFilesByModuleName(getProject, moduleName).toOption.flatMap(_.headOption)
haskellFile.flatMap(f => HaskellPsiUtil.findModuleDeclaration(f).find(_.getModuleName.contains(moduleName)).flatMap(_.getIdentifierElements.headOption))
}
}

View File

@ -16,10 +16,6 @@
package intellij.haskell.editor
import java.util
import java.util.concurrent.TimeoutException
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.intellij.codeInsight.completion._
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.openapi.application.ApplicationManager
@ -28,15 +24,12 @@ 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.util.PsiTreeUtil
import com.intellij.psi.{PsiElement, PsiFile, TokenType}
import com.intellij.util.{ProcessingContext, WaitFor}
import icons.HaskellIcons
import intellij.haskell.annotator.HaskellAnnotator
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
import intellij.haskell.external.component._
import intellij.haskell.psi.HaskellElementCondition._
import intellij.haskell.psi.HaskellPsiUtil._
import intellij.haskell.psi.HaskellTypes._
import intellij.haskell.psi._
import intellij.haskell.runconfig.console.{HaskellConsoleView, HaskellConsoleViewMap}
@ -81,23 +74,23 @@ class HaskellCompletionContributor extends CompletionContributor {
private final val CommentIds = Stream("{-", "-}", "--")
private final val HaddockIds = Stream("{-|", "-- |", "-- ^")
def findQualifiedNamedElementToComplete(element: PsiElement): Option[HaskellQualifiedNameElement] = {
private def findQualifiedNamedElementToComplete(element: PsiElement): Option[HaskellQualifiedNameElement] = {
val elementType = Option(element.getNode.getElementType)
val psiFile = element.getContainingFile.getOriginalFile
(for {
et <- elementType
if et == HS_DOT
e <- Option(psiFile.findElementAt(element.getTextOffset - 1))
p <- HaskellPsiUtil.findQualifiedNameParent(e)
p <- HaskellPsiUtil.findQualifiedName(e)
} yield p).orElse(for {
et <- elementType
if et == HS_NEWLINE || et == TokenType.WHITE_SPACE
d <- Option(psiFile.findElementAt(element.getTextOffset - 1))
if d.getNode.getElementType == HS_DOT
e <- Option(psiFile.findElementAt(element.getTextOffset - 2))
p <- HaskellPsiUtil.findQualifiedNameParent(e)
p <- HaskellPsiUtil.findQualifiedName(e)
} yield p).
orElse(HaskellPsiUtil.findQualifiedNameParent(element))
orElse(HaskellPsiUtil.findQualifiedName(element))
}
@ -210,12 +203,9 @@ class HaskellCompletionContributor extends CompletionContributor {
ProgressManager.checkCanceled()
for {
file <- projectFile
info <- stackComponentInfo
gInfo <- globalInfo
} yield
resultSet.addAllElements(getAvailableLookupElements(gInfo, info, file).asJava)
val currentElement = op.flatMap(HaskellPsiUtil.findNamedElement)
projectFile.foreach(f => resultSet.addAllElements(getAvailableLookupElements(f, currentElement).asJava))
ProgressManager.checkCanceled()
@ -227,19 +217,14 @@ class HaskellCompletionContributor extends CompletionContributor {
ProgressManager.checkCanceled()
op.foreach(element => {
val localElements = HaskellPsiUtil.findNamedElement(element) match {
case Some(ne) => findLocalElements(element).filterNot(_ == ne)
case None => findLocalElements(element)
}
resultSet.addAllElements(localElements.map(HaskellCompletionContributor.createLocalLookupElement).asJavaCollection)
})
val localLookupElements = currentElement.map(ce => findLocalElements(ce).filterNot(_ == ce).map(createLocalLookupElement)).getOrElse(Stream())
resultSet.addAllElements(localLookupElements.asJavaCollection)
}
}
}
}
val smartProvider: CompletionProvider[CompletionParameters] = new CompletionProvider[CompletionParameters] {
private val smartProvider: CompletionProvider[CompletionParameters] = new CompletionProvider[CompletionParameters] {
import intellij.haskell.external.execution.HaskellCompilationResultHelper.LayoutSpaceChar
@ -252,7 +237,7 @@ class HaskellCompletionContributor extends CompletionContributor {
HaskellEditorUtil.showHaskellSupportIsNotAvailableWhileInitializing(project)
} else {
Option(parameters.getOriginalPosition) match {
case Some(position) if HaskellPsiUtil.findExpressionParent(position).isDefined & position.getNode.getElementType == HaskellTypes.HS_UNDERSCORE =>
case Some(position) if HaskellPsiUtil.findExpression(position).isDefined & position.getNode.getElementType == HaskellTypes.HS_UNDERSCORE =>
val offset = position.getTextOffset
val editor = parameters.getEditor
HaskellAnnotator.findHighlightInfo(project, offset, editor) match {
@ -318,10 +303,8 @@ class HaskellCompletionContributor extends CompletionContributor {
}
}
import HaskellCompletionContributor._
private def findLocalElements(element: PsiElement) = {
HaskellPsiUtil.findExpressionParent(element).toStream.flatMap(e => HaskellPsiUtil.findNamedElements(e)).filter {
HaskellPsiUtil.findExpression(element).toStream.flatMap(e => HaskellPsiUtil.findNamedElements(e)).filter {
case _: HaskellVarid => true
case _: HaskellVarsym => true
case _: HaskellConsym => true
@ -330,11 +313,11 @@ class HaskellCompletionContributor extends CompletionContributor {
}
private def isImportSpecInProgress(element: PsiElement): Boolean = {
Option(PsiTreeUtil.findFirstParent(element, ImportSpecCondition)).isDefined
Option(TreeUtil.findParent(element.getNode, HaskellTypes.HS_IMPORT_SPEC)).isDefined
}
private def isFileHeaderPragmaInProgress(element: PsiElement): Boolean = {
Option(PsiTreeUtil.findFirstParent(element, FileHeaderCondition)).isDefined
Option(TreeUtil.findParent(element.getNode, HaskellTypes.HS_FILE_HEADER)).isDefined
}
private def isPragmaInProgress(element: PsiElement): Boolean = {
@ -342,14 +325,14 @@ class HaskellCompletionContributor extends CompletionContributor {
}
private def isImportModuleDeclarationInProgress(element: PsiElement): Boolean = {
Option(PsiTreeUtil.findFirstParent(element, ImportDeclarationCondition)).isDefined ||
HaskellPsiUtil.findImportDeclaration(element).isDefined ||
Option(TreeUtil.findSiblingBackward(element.getNode, HS_IMPORT)).isDefined ||
isImportIdInProgressInsideImportModuleDeclaration(element)
}
private def isImportIdInProgressInsideImportModuleDeclaration(element: PsiElement): Boolean = {
val prevNode = Option(TreeUtil.prevLeaf(element.getNode))
prevNode.exists(node => Option(PsiTreeUtil.findFirstParent(node.getPsi, ImportDeclarationCondition)).isDefined)
prevNode.exists(node => HaskellPsiUtil.findImportDeclaration(element).isDefined)
}
private def isNCommentInProgress(element: PsiElement): Boolean = {
@ -357,7 +340,7 @@ class HaskellCompletionContributor extends CompletionContributor {
}
private def getGlobalInfo(psiFile: PsiFile): Option[(StackComponentInfo, StackComponentGlobalInfo)] = {
val globalInfo = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
val infos = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
for {
info <- HaskellComponentsManager.findStackComponentInfo(psiFile)
globalInfo <- HaskellComponentsManager.findStackComponentGlobalInfo(info)
@ -367,12 +350,12 @@ class HaskellCompletionContributor extends CompletionContributor {
new WaitFor(ApplicationUtil.timeout, 1) {
override def condition(): Boolean = {
ProgressManager.checkCanceled()
globalInfo.isDone
infos.isDone
}
}
if (globalInfo.isDone) {
globalInfo.get()
if (infos.isDone) {
infos.get()
} else {
HaskellNotificationGroup.logInfoEvent(psiFile.getProject, "Timeout in getGlobalInfo for " + psiFile.getName)
None
@ -382,9 +365,9 @@ class HaskellCompletionContributor extends CompletionContributor {
private def findAvailableIdsForImportModuleSpec(stackComponentGlobalInfo: StackComponentGlobalInfo, psiFile: PsiFile, element: PsiElement) = {
import scala.concurrent.ExecutionContext.Implicits.global
HaskellPsiUtil.findImportDeclarationParent(element).flatMap(_.getModuleName) match {
HaskellPsiUtil.findImportDeclaration(element).flatMap(_.getModuleName) match {
case Some(moduleName) =>
val ids = HaskellComponentsManager.findExportedModuleIdentifiers(psiFile, moduleName).map(_.map(i => i.map(x => createLookupElement(x, addParens = true))).getOrElse(Iterable()))
val ids = 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 = {
@ -436,11 +419,10 @@ class HaskellCompletionContributor extends CompletionContributor {
HaddockIds.map(p => LookupElementBuilder.create(p).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" haddock", true))
}
private def getAvailableLookupElements(globalInfo: StackComponentGlobalInfo, info: StackComponentInfo, psiFile: PsiFile): Iterable[LookupElementBuilder] = {
private def getAvailableLookupElements(psiFile: PsiFile, currentElement: Option[HaskellNamedElement]): Iterable[LookupElementBuilder] = {
val moduleName = HaskellPsiUtil.findModuleName(psiFile)
ProgressManager.checkCanceled()
useAvailableModuleIdentifiers(globalInfo, info, psiFile, moduleName, (f1, f2) => f1.map(mi => createLookupElement(mi)) ++ getLocalTopLevelLookupElements(psiFile, moduleName, f2))
FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).map(createLookupElement(_)) ++ findTopLeveLookupElements(psiFile, moduleName, currentElement)
}
private def getKeywordLookupElements = {
@ -454,244 +436,48 @@ class HaskellCompletionContributor extends CompletionContributor {
private def getSpecialReservedIdLookupElements = {
SpecialReservedIds.map(sr => LookupElementBuilder.create(sr).withIcon(HaskellIcons.HaskellSmallBlueLogo).withTailText(" special keyword", true))
}
}
object FileModuleIdentifiers {
private case class Key(psiFile: PsiFile)
private type Result = Option[ModuleIdentifiers]
// TODO Maybe use the buildAsyncFuture
private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => findModuleIdentifiers(k))
import scala.concurrent.ExecutionContext.Implicits.global
def invalidate(psiFile: PsiFile): Unit = {
Cache.invalidate(Key(psiFile))
}
def refresh(psiFile: PsiFile): Unit = {
Cache.refresh(Key(psiFile))
}
// Invalidate files which have imported this module
def invalidate(moduleName: String): Unit = {
val keys = Cache.asMap().filter { case (_, v) => v.exists(_.exists(_.exists(_.exists(_.moduleName == moduleName)))) }.keys
Cache.invalidateAll(keys)
}
def invalidateAll(project: Project): Unit = {
Cache.asMap().filter(_._1.psiFile.getProject == project).keys.foreach(Cache.invalidate)
}
def getModuleIdentifiers(psiFile: PsiFile): Option[Iterable[ModuleIdentifier]] = {
val key = Key(psiFile)
Cache.getIfPresent(key) match {
case Some(Some(x)) =>
if (x.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(x.flatten.flatten)
case Some(None) =>
Cache.invalidate(key)
None
case None =>
if (ApplicationManager.getApplication.isReadAccessAllowed) {
val f = Future(Cache.get(key))
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, f, "getModuleIdentifiers", 5.seconds).flatten match {
case Some(x) =>
if (x.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(x.flatten.flatten)
case None =>
Cache.invalidate(key)
None
}
} else {
Cache.get(key) match {
case Some(x) =>
if (x.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(x.flatten.flatten)
case None =>
Cache.invalidate(key)
None
}
}
private def findTopLeveLookupElements(psiFile: PsiFile, moduleName: Option[String], currentElement: Option[HaskellNamedElement]): Iterable[LookupElementBuilder] = {
def createTopLevelDeclarationLookupElement(e: HaskellNamedElement, d: HaskellDeclarationElement) = {
createLocalTopLevelLookupElement(ApplicationUtil.runReadAction(e.getName), ApplicationUtil.runReadAction(d.getPresentation.getPresentableText), moduleName.getOrElse("-"))
}
val expressionLookupElements = HaskellPsiUtil.findTopLevelExpressions(psiFile).flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement)).filterNot(e => currentElement.contains(e)).map(createLocalLookupElement)
ApplicationUtil.runReadAction(HaskellPsiUtil.findTopLevelDeclarations(psiFile)).
flatMap(d => getIdentifiers(d).map { e =>
d match {
case dd: HaskellDataDeclaration =>
val dataType = dd.getSimpletype.getText
val constrType = HaskellComponentsManager.findTypeInfoForElement(e).toOption.filterNot(_.withFailure).getOrElse(
HaskellPsiUtil.findDataFieldDecl(e).orElse(HaskellPsiUtil.findDataConstr(e)) match {
case Some(ce) => ce.getText
case None => createTopLevelDeclarationLookupElement(e, d)
})
LookupElementBuilder.create(e.getName).withTypeText(s"$constrType -> $dataType")
case _ => createTopLevelDeclarationLookupElement(e, d)
}
}) ++ expressionLookupElements
}
private type ModuleIdentifiers = Iterable[Option[Iterable[ModuleIdentifier]]]
private def findModuleIdentifiers(k: Key): Option[ModuleIdentifiers] = {
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 => HaskellCompletionContributor.isNoImplicitPreludeActive(info, psiFile))
} else {
false
}
val idsF1 = HaskellCompletionContributor.getModuleIdentifiersFromFullImportedModules(noImplicitPrelude, psiFile, importDeclarations)
val idsF2 = HaskellCompletionContributor.getModuleIdentifiersFromHidingIdsImportedModules(psiFile, importDeclarations)
val idsF3 = HaskellCompletionContributor.getModuleIdentifiersFromSpecIdsImportedModules(psiFile, importDeclarations)
val f = for {
f1 <- idsF1
f2 <- idsF2
f3 <- idsF3
} yield (f1, f2, f3)
try {
val (x, y, z) = Await.result(f, 5.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}")
None
}
private def getIdentifiers(declarationElement: HaskellDeclarationElement) = {
ApplicationUtil.runReadAction(declarationElement.getIdentifierElements)
}
}
object HaskellCompletionContributor {
private def createLookupElement(moduleIdentifier: ModuleIdentifier, addParens: Boolean = false): LookupElementBuilder = {
addWiths(LookupElementBuilder.create(
if (moduleIdentifier.isOperator && addParens)
s"""(${moduleIdentifier.name})"""
else
moduleIdentifier.name
), moduleIdentifier)
}
private def createLocalLookupElement(namedElement: HaskellNamedElement): LookupElementBuilder = {
def createLocalLookupElement(namedElement: HaskellNamedElement): LookupElementBuilder = {
val typeSignature = HaskellComponentsManager.findTypeInfoForElement(namedElement).map(_.typeSignature)
LookupElementBuilder.create(namedElement.getName).withTypeText(typeSignature.map(StringUtil.unescapeXml).getOrElse("")).withIcon(HaskellIcons.HaskellSmallBlueLogo)
}
import scala.concurrent.ExecutionContext.Implicits.global
// TODO Create cache for this one instead of the downstream one???
private def useAvailableModuleIdentifiers[A](globalInfo: StackComponentGlobalInfo, info: StackComponentInfo, psiFile: PsiFile, moduleName: Option[String],
doIt: (Iterable[ModuleIdentifier], Iterable[ModuleIdentifier]) => Iterable[A]): Iterable[A] = {
val fileModuleIdentifiers = Future(FileModuleIdentifiers.getModuleIdentifiers(psiFile))
val moduleIdentifiers = Future.successful(Some(Iterable()))
val result = for {
r1 <- fileModuleIdentifiers
r2 <- moduleIdentifiers
} yield {
(r1, r2)
}
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, result, s"In useAvailableModuleIdentifiers wait for all module identifiers for ${psiFile.getName}") match {
case Some((result1, result2)) =>
(result1, result2) match {
case (Some(x), Some(y)) => doIt(x, y)
case (_, Some(y)) => doIt(Iterable(), y)
case (Some(x), _) => doIt(x, Iterable())
case (_, _) => Iterable()
}
case _ => Iterable()
}
}
def getAvailableModuleIdentifiers(globalInfo: StackComponentGlobalInfo, info: StackComponentInfo, psiFile: PsiFile, moduleName: Option[String]): Iterable[ModuleIdentifier] = {
useAvailableModuleIdentifiers(globalInfo, info, psiFile, moduleName, (f1, f2) => f1 ++ f2)
}
private sealed trait ImportInfo {
def moduleName: String
def qualified: Boolean
def as: Option[String]
}
private case class ImportFull(moduleName: String, qualified: Boolean, as: Option[String]) extends ImportInfo
private case class ImportWithHiding(moduleName: String, ids: Iterable[String], qualified: Boolean, as: Option[String]) extends ImportInfo
private case class ImportWithIds(moduleName: String, ids: Iterable[String], qualified: Boolean, as: Option[String]) extends ImportInfo
def isNoImplicitPreludeActive(info: StackComponentInfo, psiFile: PsiFile): Boolean = {
info.isImplicitPreludeActive || ApplicationUtil.runReadAction(HaskellPsiUtil.findLanguageExtensions(psiFile)).exists(p => ApplicationUtil.runReadAction(p.getText).contains("NoImplicitPrelude"))
}
private def getFullImportedModules(implicitPreludeActive: Boolean, psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportFull] = {
val moduleNames = for {
id <- importDeclarations
if Option(id.getImportSpec).isEmpty
mn <- ApplicationUtil.runReadAction(id.getModuleName)
} yield ImportFull(mn, Option(id.getImportQualified).isDefined, Option(id.getImportQualifiedAs).map(qa => ApplicationUtil.runReadAction(qa.getQualifier.getName)))
if (moduleNames.map(_.moduleName).toSeq.contains(HaskellProjectUtil.Prelude) || implicitPreludeActive) {
moduleNames
} else {
Iterable(ImportFull(HaskellProjectUtil.Prelude, qualified = false, None)) ++ moduleNames
}
}
private def getImportedModulesWithHidingIdsSpec(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportWithHiding] = {
for {
importDeclaration <- importDeclarations.filter(i => Option(i.getImportSpec).flatMap(is => Option(is.getImportHidingSpec)).isDefined)
importIdList = importDeclaration.getImportSpec.getImportHidingSpec.getImportIdList
mn <- ApplicationUtil.runReadAction(importDeclaration.getModuleName)
} yield ImportWithHiding(
mn,
findImportIds(importIdList),
Option(importDeclaration.getImportQualified).isDefined,
Option(importDeclaration.getImportQualifiedAs).map(_.getQualifier).map(q => ApplicationUtil.runReadAction(q.getName))
)
}
private def getImportedModulesWithSpecIds(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportWithIds] = {
for {
importDeclaration <- importDeclarations.filter(i => Option(i.getImportSpec).flatMap(is => Option(is.getImportIdsSpec)).isDefined)
importIdList = importDeclaration.getImportSpec.getImportIdsSpec.getImportIdList
mn <- ApplicationUtil.runReadAction(importDeclaration.getModuleName)
} yield ImportWithIds(
mn,
findImportIds(importIdList),
Option(importDeclaration.getImportQualified).isDefined,
Option(importDeclaration.getImportQualifiedAs).map(_.getQualifier).map(q => ApplicationUtil.runReadAction(q.getName))
)
}
def getModuleIdentifiersFromFullImportedModules(impliciPrelueActive: Boolean, psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getFullImportedModules(impliciPrelueActive, psiFile, importDeclarations)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.findExportedModuleIdentifiers(psiFile, importInfo.moduleName)
allModuleIdentifiers.map(mi => mi.map(i => createQualifiedModuleIdentifiers(importInfo, i)))
}))
}
def getModuleIdentifiersFromHidingIdsImportedModules(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getImportedModulesWithHidingIdsSpec(psiFile, importDeclarations)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.findExportedModuleIdentifiers(psiFile, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filterNot(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
def getModuleIdentifiersFromSpecIdsImportedModules(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getImportedModulesWithSpecIds(psiFile, importDeclarations)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.findExportedModuleIdentifiers(psiFile, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(id => if (mi.isOperator) s"(${mi.name})" == id else id == mi.name)))))
}))
}
private def createLocalTopLevelLookupElement(moduleIdentifier: ModuleIdentifier): LookupElementBuilder = {
createLocalTopLevelLookupElement(moduleIdentifier.name, moduleIdentifier.declaration, moduleIdentifier.moduleName)
}
private def createLocalTopLevelLookupElement(name: String, declaration: String, module: String): LookupElementBuilder = {
LookupElementBuilder.create(name).withTypeText(declaration).withIcon(findIcon(declaration))
}
@ -719,76 +505,4 @@ object HaskellCompletionContributor {
case _ => HaskellSmallBlueLogo
}
}
private def createLookupElement(moduleIdentifier: ModuleIdentifier, addParens: Boolean = false) = {
addWiths(LookupElementBuilder.create(
if (moduleIdentifier.isOperator && addParens)
s"""(${moduleIdentifier.name})"""
else
moduleIdentifier.name
), moduleIdentifier)
}
private def getLocalTopLevelLookupElements(psiFile: PsiFile, moduleName: Option[String], localIdentifiers: Iterable[ModuleIdentifier]): Iterable[LookupElementBuilder] = {
moduleName match {
case Some(_) =>
ProgressManager.checkCanceled()
val importDeclarations = ApplicationUtil.runReadAction(HaskellPsiUtil.findImportDeclarations(psiFile))
val localLookupElements = localIdentifiers.map(mi =>
if (moduleName.contains(mi.moduleName)) {
createLocalTopLevelLookupElement(mi)
} else {
val qualifier = importDeclarations.find(id => ApplicationUtil.runReadAction(id.getModuleName).contains(mi.moduleName)).flatMap(id => Option(id.getImportQualifiedAs).map(q => ApplicationUtil.runReadAction(q.getQualifier.getName)))
qualifier match {
case Some(q) => createLookupElement(mi.copy(name = s"$q.${mi.name}"))
case None => createLookupElement(mi)
}
}
)
localLookupElements ++ findRemainingTopLeveLookupElements(psiFile, moduleName, localIdentifiers)
case None =>
HaskellNotificationGroup.logWarningEvent(psiFile.getProject, s"No REPL support for suggesting local top level identifiers because no module defined in `${psiFile.getName}`")
findRemainingTopLeveLookupElements(psiFile, None, Iterable())
}
}
private def findRemainingTopLeveLookupElements(psiFile: PsiFile, moduleName: Option[String], localIdentifiers: Iterable[ModuleIdentifier]) = {
def createTopLevelDeclarationLookupElement(e: HaskellNamedElement, d: HaskellDeclarationElement) = {
createLocalTopLevelLookupElement(ApplicationUtil.runReadAction(e.getName), ApplicationUtil.runReadAction(d.getPresentation.getPresentableText), moduleName.getOrElse("-"))
}
ApplicationUtil.runReadAction(HaskellPsiUtil.findTopLevelDeclarations(psiFile)).filterNot(_.isInstanceOf[HaskellModuleDeclaration]).
flatMap(d => getIdentifiers(d).map { e =>
if (HaskellPsiUtil.findDataDeclarationElementParent(e).isDefined | HaskellPsiUtil.findNewTypeDeclarationElementParent(e).isDefined) {
d match {
case _: HaskellDataDeclaration => createTopLevelDeclarationLookupElement(e, d)
case _: HaskellNewtypeDeclaration => createTopLevelDeclarationLookupElement(e, d)
case _ => createLocalLookupElement(e)
}
} else {
createTopLevelDeclarationLookupElement(e, d)
}
})
}
private def getIdentifiers(declarationElement: HaskellDeclarationElement) = {
ApplicationUtil.runReadAction(declarationElement.getIdentifierElements)
}
private def createQualifiedModuleIdentifiers(importInfo: ImportInfo, moduleIdentifiers: Iterable[ModuleIdentifier]): Iterable[ModuleIdentifier] = {
moduleIdentifiers.flatMap(mi => {
(importInfo.as, importInfo.qualified) match {
case (None, false) => Iterable(mi, mi.copy(name = mi.moduleName + "." + mi.name))
case (None, true) => Iterable(mi.copy(name = mi.moduleName + "." + mi.name))
case (Some(q), false) => Iterable(mi, mi.copy(name = q + "." + mi.name))
case (Some(q), true) => Iterable(mi.copy(name = q + "." + mi.name))
}
})
}
private def findImportIds(importIdList: util.List[HaskellImportId]): Iterable[String] = {
importIdList.asScala.flatMap(importId => Iterable(ApplicationUtil.runReadAction(importId.getCname.getName)) ++ importId.getCnameDotDotList.asScala.flatMap(cndd => Option(cndd.getCname).map(cn => ApplicationUtil.runReadAction(cn.getName))))
}
}

View File

@ -39,7 +39,7 @@ class HaskellExtendWordSelectioner extends ExtendWordSelectionHandler {
val startOffset = e.getTextRange.getStartOffset
val nextEndOffsets = getOffsets(Some(e), ListBuffer.empty[Int], (e: PsiElement) => e.getNextSibling, (e: PsiElement) => e.getTextRange.getEndOffset)
val prevStartOffsets = getOffsets(HaskellPsiUtil.findQualifiedNameParent(e).flatMap(qe => Option(qe.getPrevSibling)), ListBuffer.empty[Int], (e: PsiElement) => e.getPrevSibling, (e: PsiElement) => e.getTextRange.getStartOffset)
val prevStartOffsets = getOffsets(HaskellPsiUtil.findQualifiedName(e).flatMap(qe => Option(qe.getPrevSibling)), ListBuffer.empty[Int], (e: PsiElement) => e.getPrevSibling, (e: PsiElement) => e.getTextRange.getStartOffset)
val lastEndOffset = nextEndOffsets.lastOption.getOrElse(e.getTextRange.getEndOffset)
(nextEndOffsets.map(eo => new TextRange(startOffset, eo)) ++ prevStartOffsets.map(so => new TextRange(so, lastEndOffset))).asJava
@ -52,7 +52,7 @@ class HaskellExtendWordSelectioner extends ExtendWordSelectionHandler {
element match {
case Some(e) =>
HaskellPsiUtil.findQualifiedNameParent(e) match {
HaskellPsiUtil.findQualifiedName(e) match {
case None => e match {
case e: PsiWhiteSpace => recur(e)
case e: PsiElement if e.getNode.getElementType == HS_COMMA => recur(e)

View File

@ -82,7 +82,7 @@ private[component] object AvailableModuleNamesComponent {
private def findModuleNamesInModule(project: Project, currentModule: Module, modules: Seq[Module], includeTests: Boolean): Iterable[String] = {
for {
vf <- findHaskellFiles(project, currentModule, modules, includeTests)
hf <- HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toOption.flatten
hf <- HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toSeq
mn <- HaskellPsiUtil.findModuleName(hf)
} yield mn
}

View File

@ -17,17 +17,18 @@
package intellij.haskell.external.component
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.external.repl._
import intellij.haskell.util.index.HaskellModuleNameIndex
import intellij.haskell.util.{HaskellProjectUtil, ScalaFutureUtil, StringUtil}
import intellij.haskell.util.{HaskellFileUtil, StringUtil}
import scala.concurrent.{ExecutionContext, Future}
private[component] object BrowseModuleComponent {
private case class Key(project: Project, moduleName: String, psiFile: Option[PsiFile], exported: Boolean)
private case class Key(project: Project, moduleName: String)
type BrowseModuleResult = Iterable[ModuleIdentifier]
private type BrowseModuleInternalResult = Either[NoInfo, Iterable[ModuleIdentifier]]
@ -45,82 +46,36 @@ private[component] object BrowseModuleComponent {
case Right(ids) => Some(ids)
case Left(NoInfoAvailable(_, _)) =>
None
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
Cache.synchronous().invalidate(key)
None
})
}
def findLibraryModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(project, moduleName, None, exported = true)
def findModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(project, moduleName)
matchResult(key, Cache.get(key))
}
def findLibraryModuleIdentifiersSync(project: Project, moduleName: String): BrowseModuleInternalResult = {
val key = Key(project, moduleName, None, exported = true)
def findModuleIdentifiersSync(project: Project, moduleName: String): BrowseModuleInternalResult = {
val key = Key(project, moduleName)
Cache.synchronous().get(key)
}
def loadExportedIdentifiersSync(project: Project, psiFile: PsiFile, moduleName: String): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global
val result = findExportedIdentifiers(psiFile, moduleName)
ScalaFutureUtil.waitForValue(project, result, s"loading exported identifiers in findExportedIdentifiersSync for module $moduleName")
}
def findTopLevelIdentifiers(psiFile: PsiFile, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(psiFile.getProject, moduleName, Some(psiFile), exported = false)
if (LoadComponent.isFileLoaded(psiFile)) {
matchResult(key, Cache.get(key))
} else {
matchResult(key, Cache.getIfPresent(key).getOrElse(Future.successful(Left(ModuleNotLoaded(key.moduleName)))))
}
}
def findExportedIdentifiers(psiFile: PsiFile, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val moduleFiles = HaskellModuleNameIndex.findFileByModuleName(psiFile.getProject, moduleName)
val projectFile = moduleFiles.toOption.exists(_.headOption.exists(HaskellProjectUtil.isSourceFile))
val project = psiFile.getProject
if (projectFile) {
val key = Key(project, moduleName, Some(psiFile), exported = true)
val result = matchResult(key, Cache.get(key))
result.map {
case r@Some(_) => r
case None =>
val result = Cache.synchronous().asMap().find { case (k, _) => k.project == project && k.moduleName == moduleName }.map(_._2).flatMap(_.toOption)
result.foreach(r => Cache.synchronous().put(key, Right(r)))
result
}
} else {
findLibraryModuleIdentifiers(psiFile.getProject, moduleName)
}
}
def findModuleIdentifiersInCache(project: Project): Iterable[ModuleIdentifier] = {
Cache.synchronous().asMap().filter(_._1.project == project).values.flatMap(_.toSeq).flatten
}
def refreshTopLevel(project: Project, moduleName: String, psiFile: PsiFile): Unit = {
val key = Key(project, moduleName, Some(psiFile), exported = false)
Cache.synchronous().refresh(key)
}
def invalidateTopLevel(project: Project, moduleName: String, psiFile: PsiFile): Unit = {
val key = Key(project, moduleName, Some(psiFile), exported = false)
Cache.synchronous().invalidate(key)
}
def invalidateExportedModuleName(project: Project, moduleName: String): Unit = {
def invalidateModuleName(project: Project, moduleName: String): Unit = {
val synchronousCache = Cache.synchronous
val key = synchronousCache.asMap().keys.filter(k => k.moduleName == moduleName && k.exported) // Can be more than one for a module name if file of module can not be found
val key = synchronousCache.asMap().keys.filter(k => k.moduleName == moduleName) // Can be more than one for a module name if file of module can not be found
key.foreach(synchronousCache.invalidate)
}
def refreshExportedModuleNames(project: Project, moduleNames: Seq[String]): Unit = {
def invalidateModuleNames(project: Project, moduleNames: Seq[String]): Unit = {
val synchronousCache = Cache.synchronous
val keys = synchronousCache.asMap().keys.filter(k => moduleNames.contains(k.moduleName))
keys.foreach(synchronousCache.refresh)
keys.foreach(synchronousCache.invalidate)
}
def invalidate(project: Project): Unit = {
@ -133,81 +88,59 @@ private[component] object BrowseModuleComponent {
val project = key.project
val moduleName = key.moduleName
key.psiFile match {
case Some(psiFile) if !key.exported =>
if (LoadComponent.isFileLoaded(psiFile)) {
StackReplsManager.getProjectRepl(psiFile) match {
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 && output.stdoutLines.nonEmpty => Right(output.stdoutLines.takeWhile(l => !l.startsWith("-- imported via")).flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
}
case _ => Left(ReplNotAvailable)
}
} else {
Left(ModuleNotLoaded(key.moduleName))
}
case Some(psiFile) if key.exported =>
val projectRepl = StackReplsManager.getProjectRepl(psiFile)
projectRepl match {
case Some(repl) =>
if (repl.isBusy) {
findInOtherRepl(project, psiFile, moduleName, repl) match {
HaskellModuleNameIndex.findFilesByModuleName2(project, moduleName) match {
case Right(files) => files.headOption match {
case Some((moduleFile, isProjectFile)) =>
if (isProjectFile) {
getCurrentFile(project) match {
case Some(cf) => findInRepl(project, StackReplsManager.getProjectRepl(cf), moduleName, None) match {
case r@Right(_) => r
case _ => Left(ReplIsBusy)
}
} else if (!repl.available) {
findInOtherRepl(project, psiFile, moduleName, repl) match {
case r@Right(_) => r
case _ => Left(ReplNotAvailable)
}
} else {
if (repl.isBrowseModuleLoaded(moduleName)) {
repl.getModuleIdentifiers(moduleName, psiFile) match {
case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
} else {
findInOtherRepl(project, psiFile, moduleName, repl)
case Left(_) =>
val projectRepl = StackReplsManager.getProjectRepl(moduleFile)
findInRepl(project, projectRepl, moduleName, Some(moduleFile))
}
case None =>
val projectRepl = StackReplsManager.getProjectRepl(moduleFile)
findInRepl(project, projectRepl, moduleName, Some(moduleFile))
}
case None => Left(ReplNotAvailable)
}
case None => findLibModuleIdentifiers(project, moduleName)
} else {
findLibModuleIdentifiers(project, moduleName)
}
case None => Left(ModuleNotAvailable(moduleName))
}
case Left(noInfo) => Left(noInfo)
}
}
private def findInOtherRepl(project: Project, psiFile: PsiFile, moduleName: String, repl: ProjectStackRepl) = {
HaskellModuleNameIndex.findFileByModuleName(project, moduleName) match {
case Right(files) => files.headOption match {
case Some(f) => val otherRepl = StackReplsManager.getProjectRepl(f)
if (otherRepl.contains(repl)) {
Left(ReplNotAvailable)
} else {
otherRepl match {
case Some(oRepl) =>
if (oRepl.isBusy) {
Left(ReplIsBusy)
} else if (!oRepl.available) {
Left(ReplNotAvailable)
} else {
oRepl.getModuleIdentifiers(moduleName, f) match {
case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
}
case None => Left(ReplNotAvailable)
}
}
case None => Left(NoInfoAvailable(moduleName, "-"))
private def getCurrentFile(project: Project): Option[PsiFile] = {
if (StackProjectManager.isInitializing(project)) {
None
} else {
FileEditorManager.getInstance(project).getSelectedFiles.headOption match {
case Some(f) => HaskellFileUtil.convertToHaskellFileInReadAction(project, f).toOption
case None => None
}
case Left(noInfo) => Left(noInfo)
}
}
private def findInRepl(project: Project, projectRepl: Option[ProjectStackRepl], moduleName: String, psiFile: Option[PsiFile]): Either[NoInfo, Seq[ModuleIdentifier]] = {
projectRepl match {
case Some(repl) =>
if (repl.isBusy) {
Left(ReplIsBusy)
} else if (!repl.available) {
Left(ReplNotAvailable)
} else {
if (psiFile.isEmpty || repl.isBrowseModuleLoaded(moduleName)) {
repl.getModuleIdentifiers(moduleName, psiFile) match {
case Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName)))
case _ => Left(ReplNotAvailable)
}
} else {
Left(ModuleNotAvailable(moduleName))
}
}
case None => Left(ReplNotAvailable)
}
}

View File

@ -23,7 +23,6 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.util.WaitFor
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.editor.FileModuleIdentifiers
import intellij.haskell.external.repl.StackRepl.StackReplOutput
import intellij.haskell.external.repl.{ProjectStackRepl, StackReplsManager}
import intellij.haskell.navigation.HaskellReference
@ -52,8 +51,7 @@ private[component] object DefinitionLocationComponent {
case Left(noInfo) => Left(noInfo)
}
}
}
)
})
def findDefinitionLocation(psiFile: PsiFile, qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String]): DefinitionLocationResult = {
val key = Key(psiFile, qualifiedNameElement, importQualifier)
@ -64,23 +62,10 @@ private[component] object DefinitionLocationComponent {
Cache.synchronous().asMap().find { case (k, _) => k.psiFile == psiFile && k.qualifiedNameElement == qualifiedNameElement }.flatMap(_._2.toOption)
}
def findReferencesInCache(targetFile: PsiFile): Seq[(PsiFile, HaskellQualifiedNameElement)] = {
def invalidateOtherFiles(currentFile: PsiFile, name: String): Unit = {
val synchronousCache = Cache.synchronous()
synchronousCache.asMap().filter { case (k, v) => v.toOption.exists(l => {
if (!l.namedElement.isValid) {
synchronousCache.invalidate(k)
false
} else {
l.namedElement.getContainingFile.getOriginalFile == targetFile
}
})
}.keys.map(k => (k.psiFile, k.qualifiedNameElement)).toSeq
}
def refresh(elements: Seq[HaskellQualifiedNameElement]): Unit = {
val synchronousCache = Cache.synchronous()
val keys = synchronousCache.asMap().keys.filter(k => elements.contains(k.qualifiedNameElement))
keys.foreach(synchronousCache.refresh)
val keys = synchronousCache.asMap().filter { case (k, v) => k.qualifiedNameElement.getIdentifierElement.getName == name && k.psiFile != currentFile }.keys
keys.foreach(synchronousCache.invalidate)
}
private def checkValidKey(key: Key): Boolean = {
@ -128,105 +113,77 @@ private[component] object DefinitionLocationComponent {
val name = ApplicationUtil.runReadAction(identifierElement.getName)
if (name.headOption.exists(_.isUpper)) {
createDefinitionLocationResult(project, psiFile, key, name, withoutLastColumn = true)
createDefinitionLocationResult(project, key, name, withoutLastColumn = true)
} else {
createDefinitionLocationResult(project, psiFile, key, name, withoutLastColumn = false)
createDefinitionLocationResult(project, key, name, withoutLastColumn = false)
}
}
private def createDefinitionLocationResult(project: Project, psiFile: PsiFile, key: Key, name: String, withoutLastColumn: Boolean): DefinitionLocationResult = {
private def findLocationByImportedIdentifiers(project: Project, key: Key, name: String): Option[Either[NoInfoAvailable, PackageModuleLocation]] = {
val psiFile = key.psiFile
val qName1 = key.qualifiedNameElement.getName
val mids = FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile)
ProgressManager.checkCanceled()
val sourceFile = HaskellProjectUtil.isSourceFile(psiFile)
ProgressManager.checkCanceled()
val moduleName = HaskellPsiUtil.findModuleName(psiFile)
ProgressManager.checkCanceled()
if (sourceFile) {
// Again workaround intero bug
if (key.importQualifier.isDefined || key.qualifiedNameElement.getQualifierName.isDefined) {
val qName1 = key.qualifiedNameElement.getName
val result = FileModuleIdentifiers.getModuleIdentifiers(psiFile) match {
case None => Some(Left(ReadActionTimeout("")))
case Some(mids) =>
ProgressManager.checkCanceled()
val qName = key.importQualifier match {
case None => qName1
case Some(q) => q + "." + qName1
}
for {
mid <- mids.find(_.name == qName)
} yield {
val findResult = HaskellReference.findIdentifiersByModuleAndName(project, mid.moduleName, name)
findResult match {
case Right(nes) if nes.nonEmpty => nes.headOption.map(ne => Right(PackageModuleLocation(mid.moduleName, ne, name, Some(qName)))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
case _ => Left(NoInfoAvailable(name, psiFile.getName))
}
}
}
val qName2 = StringUtil.removeOuterParens(qName1)
val qName = key.importQualifier match {
case None => qName2
case Some(q) => q + "." + qName2
}
for {
mid <- mids.find(_.name == qName)
} yield {
HaskellReference.findIdentifiersByModuleAndName(project, Seq(mid.moduleName), name) match {
case Right(nes) if nes.nonEmpty => nes.headOption.map(ne => Right(PackageModuleLocation(mid.moduleName, ne, name, Some(qName)))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
case _ => Left(NoInfoAvailable(name, psiFile.getName))
}
}
}
ProgressManager.checkCanceled()
private def createDefinitionLocationResult(project: Project, key: Key, name: String, withoutLastColumn: Boolean): DefinitionLocationResult = {
val psiFile = key.psiFile
ProgressManager.checkCanceled()
val libraryFile = HaskellProjectUtil.isLibraryFile(psiFile)
ProgressManager.checkCanceled()
// Again workaround intero bug
if (libraryFile || key.importQualifier.isDefined || key.qualifiedNameElement.getQualifierName.isDefined) {
val locations = findLocationByImportedIdentifiers(project, key, name)
val result2 = result match {
case Some(r@Right(_)) => r
case None => Left(NoInfoAvailable(name, psiFile.getName)) // identifier not imported
case Some(Left(_)) =>
ProgressManager.checkCanceled()
HaskellComponentsManager.findNameInfo(key.qualifiedNameElement, key.importQualifier) match {
case Right(infos) =>
ProgressManager.checkCanceled()
locations match {
case Some(ls) => ls
case None => if (libraryFile) {
ProgressManager.checkCanceled()
HaskellComponentsManager.findNameInfo(key.qualifiedNameElement) match {
case Right(infos) => infos.headOption match {
case Some(info) =>
ProgressManager.checkCanceled()
infos.headOption match {
case Some(nameInfo) =>
val findInfoResult = HaskellReference.findIdentifiersByNameInfo(nameInfo, key.qualifiedNameElement.getIdentifierElement, project)
ProgressManager.checkCanceled()
findInfoResult match {
case Right(nes) =>
nes.headOption match {
case Some(r) => HaskellPsiUtil.findModuleName(r.getContainingFile) match {
case Some(mn) => Right(PackageModuleLocation(mn, r, name, Some(qName1)))
case None => Right(LocalModuleLocation(r.getContainingFile, r, name, Some(qName1)))
}
case None => Left(NoInfoAvailable(name, psiFile.getName))
}
case Left(noInfo) => Left(noInfo)
HaskellReference.findIdentifiersByNameInfo(info, key.qualifiedNameElement.getIdentifierElement, project) match {
case Right(ne) =>
if (ne.isEmpty) {
// TODO Use findIdentifierInFileByName in HaskellReference
HaskellPsiUtil.findHaskellDeclarationElements(psiFile).toSeq.flatMap(_.getIdentifierElements).
find(_.getName == name).
map(e => Right(PackageModuleLocation("-", e, name))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
Left(NoInfoAvailable("", ""))
} else {
ne.headOption.map(ne => Right(PackageModuleLocation("-", ne, name))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
}
case None => Left(NoInfoAvailable(name, psiFile.getName))
case Left(noInfo) => Left(noInfo)
}
case Left(noInfo) => Left(noInfo)
case None => Left(NoInfoAvailable(name, psiFile.getName))
}
case Left(noInfo) => Left(noInfo)
}
} else {
findLocationByImportedIdentifiers(project, key, name).getOrElse(Left(NoInfoAvailable(psiFile.getName, name)))
}
result2 match {
case Right(location) => Right(location)
case l =>
HaskellNotificationGroup.logInfoEvent(project, "In createDefinitionLocationResult no result for " + name)
l
}
} else {
ProgressManager.checkCanceled()
findLocationResultWithRepl(project, psiFile, moduleName, key, name, withoutLastColumn)
}
} else {
ProgressManager.checkCanceled()
HaskellComponentsManager.findNameInfo(key.qualifiedNameElement) match {
case Right(infos) => infos.headOption match {
case Some(info) =>
ProgressManager.checkCanceled()
val ne = HaskellReference.findIdentifiersByNameInfo(info, key.qualifiedNameElement.getIdentifierElement, project)
ne match {
case Right(nee) =>
if (nee.isEmpty) {
HaskellPsiUtil.findHaskellDeclarationElements(psiFile).toSeq.flatMap(_.getIdentifierElements).
find(e => e.getName == name).
map(e => Right(PackageModuleLocation("-", e, name))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
Left(NoInfoAvailable("", ""))
} else {
nee.headOption.map(e => Right(PackageModuleLocation("-", e, name))).getOrElse(Left(NoInfoAvailable(name, psiFile.getName)))
}
case Left(noInfo) => Left(noInfo)
}
case None => Left(NoInfoAvailable(name, psiFile.getName))
}
case Left(noInfo) => Left(noInfo)
}
val moduleName = HaskellPsiUtil.findModuleName(psiFile)
ProgressManager.checkCanceled()
findLocationResultWithRepl(project, psiFile, moduleName, key, name, withoutLastColumn)
}
}
@ -266,7 +223,7 @@ private[component] object DefinitionLocationComponent {
Left(ReplNotAvailable)
} else {
if (ApplicationManager.getApplication.isDispatchThread && !LoadComponent.isModuleLoaded(moduleName, psiFile)) {
Left(ModuleNotLoaded(moduleName.getOrElse(psiFile.getName)))
Left(ModuleNotAvailable(moduleName.getOrElse(psiFile.getName)))
} else {
f(repl) match {
case Some(o) if o.stderrLines.isEmpty && o.stdoutLines.nonEmpty => Right(o)
@ -295,7 +252,7 @@ private[component] object DefinitionLocationComponent {
case (_, _) => Left(NoInfoAvailable(name, psiFile.getName))
}
case PackageModulePattern(_, mn) =>
findFileByModuleName(project, mn) match {
findFilesByModuleName(project, mn) match {
case Right(files) =>
files.headOption.flatMap(HaskellReference.findIdentifierInFileByName(_, name)) match {
case Some(e) => Right(PackageModuleLocation(mn, e, name))
@ -303,11 +260,10 @@ private[component] object DefinitionLocationComponent {
}
case Left(noInfo) => Left(noInfo)
}
case _ => Left(NoInfoAvailable(name, key.psiFile.getName))
case _ => findLocationByImportedIdentifiers(project, key, name).getOrElse(Left(NoInfoAvailable(name, key.psiFile.getName)))
}
}
private[component] def find(key: Key): DefinitionLocationResult = {
val isDispatchThread = ApplicationManager.getApplication.isDispatchThread
val isReadAccessAllowed = ApplicationManager.getApplication.isReadAccessAllowed
@ -329,7 +285,7 @@ private[component] object DefinitionLocationComponent {
} else {
if (isDispatchThread) {
HaskellNotificationGroup.logInfoEvent(psiFile.getProject, s"No info in DefinitionLocationComponent.find for ${qualifiedNameElement.getName} in ${psiFile.getName} because timeout in dispatch thread")
Left(ModuleNotLoaded(psiFile.getName))
Left(ModuleNotAvailable(psiFile.getName))
}
else {
HaskellNotificationGroup.logInfoEvent(psiFile.getProject, s"Timeout in DefinitionLocationComponent.find for ${qualifiedNameElement.getName} in ${psiFile.getName}")
@ -345,7 +301,7 @@ private[component] object DefinitionLocationComponent {
val result = wait(v)
result match {
case Right(_) => result
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReplNotAvailable) =>
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.synchronous().invalidate(key)
result
case _ => result
@ -357,7 +313,7 @@ private[component] object DefinitionLocationComponent {
case Right(_) =>
Cache.synchronous.put(key, result)
result
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotLoaded(_)) =>
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotAvailable(_)) =>
result
case Left(_) =>
Cache.synchronous.put(key, result)

View File

@ -0,0 +1,254 @@
/*
* 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 java.util
import java.util.concurrent.TimeoutException
import com.github.blemale.scaffeine.{LoadingCache, Scaffeine}
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
import intellij.haskell.psi.HaskellPsiUtil.findImportDeclarations
import intellij.haskell.psi.{HaskellImportDeclaration, HaskellImportId, HaskellPsiUtil}
import intellij.haskell.util.{ApplicationUtil, HaskellProjectUtil, ScalaFutureUtil}
import scala.collection.JavaConverters._
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
object FileModuleIdentifiers {
private case class Key(psiFile: PsiFile)
private type Result = Option[ModuleIdentifiers]
private final val Cache: LoadingCache[Key, Result] = Scaffeine().build((k: Key) => findModuleIdentifiers(k))
import scala.concurrent.ExecutionContext.Implicits.global
def invalidate(psiFile: PsiFile): Unit = {
Cache.invalidate(Key(psiFile))
}
def refresh(psiFile: PsiFile): Unit = {
Cache.refresh(Key(psiFile))
}
// Invalidate files which have imported this module
def invalidate(moduleName: String): Unit = {
val keys = Cache.asMap().filter { case (_, v) => v.exists(_.exists(_.exists(_.exists(_.moduleName == moduleName)))) }.keys
Cache.invalidateAll(keys)
}
def invalidateAll(project: Project): Unit = {
Cache.asMap().filter(_._1.psiFile.getProject == project).keys.foreach(Cache.invalidate)
}
def findAvailableModuleIdentifiers(psiFile: PsiFile): Iterable[ModuleIdentifier] = {
if (ApplicationManager.getApplication.isReadAccessAllowed) {
val moduleIdentifiers = Future(getModuleIdentifiers(psiFile))
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, moduleIdentifiers, s"in findAvailableModuleIdentifiers to get all module identifiers for ${psiFile.getName}") match {
case Some(mids) => mids.getOrElse(Iterable())
case _ => Iterable()
}
} else {
getModuleIdentifiers(psiFile).getOrElse(Iterable())
}
}
private def getModuleIdentifiers(psiFile: PsiFile): Option[Iterable[ModuleIdentifier]] = {
val key = Key(psiFile)
Cache.getIfPresent(key) match {
case Some(Some(mids)) =>
if (mids.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(mids.flatten.flatten)
case Some(None) =>
Cache.invalidate(key)
None
case None =>
if (ApplicationManager.getApplication.isReadAccessAllowed) {
val f = Future(Cache.get(key))
ScalaFutureUtil.waitWithCheckCancelled(psiFile.getProject, f, "getModuleIdentifiers", 10.seconds).flatten match {
case Some(mids) =>
if (mids.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(mids.flatten.flatten)
case None =>
Cache.invalidate(key)
None
}
} else {
Cache.get(key) match {
case Some(mids) =>
if (mids.toSeq.contains(None)) {
Cache.invalidate(key)
}
Some(mids.flatten.flatten)
case None =>
Cache.invalidate(key)
None
}
}
}
}
private type ModuleIdentifiers = Iterable[Option[Iterable[ModuleIdentifier]]]
private def findModuleIdentifiers(k: Key): Option[ModuleIdentifiers] = {
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 => FileModuleIdentifiers.isNoImplicitPreludeActive(info, psiFile))
} else {
false
}
val idsF1 = getModuleIdentifiersFromFullImportedModules(noImplicitPrelude, psiFile, importDeclarations)
val idsF2 = getModuleIdentifiersFromHidingIdsImportedModules(psiFile, importDeclarations)
val idsF3 = getModuleIdentifiersFromSpecIdsImportedModules(psiFile, importDeclarations)
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}")
None
}
}
private def getModuleIdentifiersFromFullImportedModules(impliciPrelueActive: Boolean, psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Future[Iterable[Option[Iterable[ModuleIdentifier]]]] = {
val importInfos = getFullImportedModules(impliciPrelueActive, psiFile, importDeclarations)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.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)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.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)
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = HaskellComponentsManager.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(id => if (mi.isOperator) s"(${mi.name})" == id else id == mi.name)))))
}))
}
private sealed trait ImportInfo {
def moduleName: String
def qualified: Boolean
def as: Option[String]
}
private case class ImportFull(moduleName: String, qualified: Boolean, as: Option[String]) extends ImportInfo
private case class ImportWithHiding(moduleName: String, ids: Iterable[String], qualified: Boolean, as: Option[String]) extends ImportInfo
private case class ImportWithIds(moduleName: String, ids: Iterable[String], qualified: Boolean, as: Option[String]) extends ImportInfo
private def isNoImplicitPreludeActive(info: StackComponentInfo, psiFile: PsiFile): Boolean = {
info.isImplicitPreludeActive || ApplicationUtil.runReadAction(HaskellPsiUtil.findLanguageExtensions(psiFile)).exists(p => ApplicationUtil.runReadAction(p.getText).contains("NoImplicitPrelude"))
}
private def getFullImportedModules(implicitPreludeActive: Boolean, psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportFull] = {
val moduleNames = for {
id <- importDeclarations
if Option(id.getImportSpec).isEmpty
mn <- ApplicationUtil.runReadAction(id.getModuleName)
} yield ImportFull(mn, Option(id.getImportQualified).isDefined, Option(id.getImportQualifiedAs).map(qa => ApplicationUtil.runReadAction(qa.getQualifier.getName)))
if (moduleNames.map(_.moduleName).toSeq.contains(HaskellProjectUtil.Prelude) || implicitPreludeActive) {
moduleNames
} else {
Iterable(ImportFull(HaskellProjectUtil.Prelude, qualified = false, None)) ++ moduleNames
}
}
private def getImportedModulesWithHidingIdsSpec(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportWithHiding] = {
for {
importDeclaration <- importDeclarations.filter(i => Option(i.getImportSpec).flatMap(is => Option(is.getImportHidingSpec)).isDefined)
importIdList = importDeclaration.getImportSpec.getImportHidingSpec.getImportIdList
mn <- ApplicationUtil.runReadAction(importDeclaration.getModuleName)
} yield ImportWithHiding(
mn,
findImportIds(importIdList),
Option(importDeclaration.getImportQualified).isDefined,
Option(importDeclaration.getImportQualifiedAs).map(_.getQualifier).map(q => ApplicationUtil.runReadAction(q.getName))
)
}
private def getImportedModulesWithSpecIds(psiFile: PsiFile, importDeclarations: Iterable[HaskellImportDeclaration]): Iterable[ImportWithIds] = {
for {
importDeclaration <- importDeclarations.filter(i => Option(i.getImportSpec).flatMap(is => Option(is.getImportIdsSpec)).isDefined)
importIdList = importDeclaration.getImportSpec.getImportIdsSpec.getImportIdList
mn <- ApplicationUtil.runReadAction(importDeclaration.getModuleName)
} yield ImportWithIds(
mn,
findImportIds(importIdList),
Option(importDeclaration.getImportQualified).isDefined,
Option(importDeclaration.getImportQualifiedAs).map(_.getQualifier).map(q => ApplicationUtil.runReadAction(q.getName))
)
}
private def createQualifiedModuleIdentifiers(importInfo: ImportInfo, moduleIdentifiers: Iterable[ModuleIdentifier]): Iterable[ModuleIdentifier] = {
moduleIdentifiers.flatMap(mi => {
(importInfo.as, importInfo.qualified) match {
case (None, false) => Iterable(mi, mi.copy(name = mi.moduleName + "." + mi.name))
case (None, true) => Iterable(mi.copy(name = mi.moduleName + "." + mi.name))
case (Some(q), false) => Iterable(mi, mi.copy(name = q + "." + mi.name))
case (Some(q), true) => Iterable(mi.copy(name = q + "." + mi.name))
}
})
}
private def findImportIds(importIdList: util.List[HaskellImportId]): Iterable[String] = {
importIdList.asScala.flatMap(importId => Iterable(ApplicationUtil.runReadAction(importId.getCname.getName)) ++ importId.getCnameDotDotList.asScala.flatMap(cndd => Option(cndd.getCname).map(cn => ApplicationUtil.runReadAction(cn.getName))))
}
}

View File

@ -27,7 +27,6 @@ import com.intellij.psi.{PsiElement, PsiFile}
import com.intellij.util.WaitFor
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.cabal.CabalInfo
import intellij.haskell.editor.FileModuleIdentifiers
import intellij.haskell.external.component.DefinitionLocationComponent.DefinitionLocationResult
import intellij.haskell.external.component.NameInfoComponentResult.NameInfoResult
import intellij.haskell.external.component.TypeInfoComponentResult.TypeInfoResult
@ -42,7 +41,7 @@ import scala.concurrent._
object HaskellComponentsManager {
case class StackComponentInfo(module: Module, packageName: String, target: String, stanzaType: StanzaType, sourceDirs: Seq[String], mainIs: Option[String], isImplicitPreludeActive: Boolean, buildDepends: Seq[String], exposedModuleNames: Seq[String] = Seq.empty)
case class StackComponentInfo(module: Module, modulePath: String, packageName: String, target: String, stanzaType: StanzaType, sourceDirs: Seq[String], mainIs: Option[String], isImplicitPreludeActive: Boolean, buildDepends: Seq[String], exposedModuleNames: Seq[String] = Seq.empty)
def findModuleIdentifiersInCache(project: Project)(implicit ec: ExecutionContext): Iterable[ModuleIdentifier] = {
val f = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.callable {
@ -73,29 +72,17 @@ object HaskellComponentsManager {
}
def isReplBusy(psiFile: PsiFile): Boolean = {
LoadComponent.isBusy(psiFile)
LoadComponent.isReplBusy(psiFile)
}
def findLibraryModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
BrowseModuleComponent.findLibraryModuleIdentifiers(project, moduleName)
}
def findExportedModuleIdentifiers(psiFile: PsiFile, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
BrowseModuleComponent.findExportedIdentifiers(psiFile, moduleName)
}
def findTopLevelModuleIdentifiers(psiFile: PsiFile, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
BrowseModuleComponent.findTopLevelIdentifiers(psiFile, moduleName)
def findModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
BrowseModuleComponent.findModuleIdentifiers(project, moduleName)
}
def findDefinitionLocation(psiFile: PsiFile, qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String]): DefinitionLocationResult = {
DefinitionLocationComponent.findDefinitionLocation(psiFile, qualifiedNameElement, importQualifier)
}
def findNameInfo(qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String] = None): NameInfoResult = {
NameInfoComponent.findNameInfo(qualifiedNameElement, importQualifier)
}
def findNameInfo(psiElement: PsiElement): NameInfoResult = {
NameInfoComponent.findNameInfo(psiElement)
}
@ -117,11 +104,11 @@ object HaskellComponentsManager {
}
def findStackComponentInfo(psiFile: PsiFile): Option[StackComponentInfo] = {
HaskellProjectFileInfoComponent.findHaskellProjectFileInfo(psiFile).map(_.stackComponentInfo)
HaskellModuleInfoComponent.findHaskellProjectFileInfo(psiFile)
}
def findStackComponentInfo(project: Project, filePath: String): Option[StackComponentInfo] = {
HaskellProjectFileInfoComponent.findHaskellProjectFileInfo(project, filePath).map(_.stackComponentInfo)
HaskellModuleInfoComponent.findHaskellProjectFileInfo(project, filePath)
}
def getGlobalProjectInfo(project: Project): Option[GlobalProjectInfo] = {
@ -156,15 +143,11 @@ object HaskellComponentsManager {
LoadComponent.load(psiFile, fileChanged, psiElement)
}
def invalidateHaskellFileInfoCache(psiFile: PsiFile): Unit = {
HaskellProjectFileInfoComponent.invalidate(psiFile)
def invalidateFileInfos(psiFile: PsiFile): Unit = {
HaskellModuleInfoComponent.invalidate(psiFile)
}
def refreshDefinitionLocationCache(elements: Seq[HaskellQualifiedNameElement]): Unit = {
DefinitionLocationComponent.refresh(elements)
}
def invalidateDefinitionLocationCache(psiFile: PsiFile): Unit = {
def invalidateLocations(psiFile: PsiFile): Unit = {
DefinitionLocationComponent.invalidate(psiFile)
}
@ -172,16 +155,16 @@ object HaskellComponentsManager {
findStackComponentInfos(project).map(info => (info.module, info.packageName)).distinct
}
def findReferencesInCache(targetFile: PsiFile): Seq[(PsiFile, HaskellQualifiedNameElement)] = {
DefinitionLocationComponent.findReferencesInCache(targetFile)
def invalidateOtherFilesLocations(currentFile: PsiFile, name: String): Unit = {
DefinitionLocationComponent.invalidateOtherFiles(currentFile, name)
}
def findLibraryPackageInfos(project: Project): Seq[PackageInfo] = {
LibraryPackageInfoComponent.libraryPackageInfos(project).toSeq
}
def refreshCacheForModules(project: Project, moduleNames: Seq[String]): Unit = {
BrowseModuleComponent.refreshExportedModuleNames(project, moduleNames)
def invalidateBrowseInfo(project: Project, moduleNames: Seq[String]): Unit = {
BrowseModuleComponent.invalidateModuleNames(project, moduleNames)
}
def findStackComponentInfos(project: Project): Seq[StackComponentInfo] = {
@ -192,7 +175,7 @@ object HaskellComponentsManager {
HaskellNotificationGroup.logInfoEvent(project, "Start to invalidate cache")
GlobalProjectInfoComponent.invalidate(project)
LibraryPackageInfoComponent.invalidate(project)
HaskellProjectFileInfoComponent.invalidate(project)
HaskellModuleInfoComponent.invalidate(project)
BrowseModuleComponent.invalidate(project)
NameInfoComponent.invalidateAll(project)
DefinitionLocationComponent.invalidateAll(project)
@ -260,7 +243,7 @@ object HaskellComponentsManager {
private def preloadLibraryIdentifiers(project: Project): Unit = {
if (!project.isDisposed) {
BrowseModuleComponent.findLibraryModuleIdentifiers(project, HaskellProjectUtil.Prelude)
BrowseModuleComponent.findModuleIdentifiers(project, HaskellProjectUtil.Prelude)
}
if (!project.isDisposed) {
@ -282,33 +265,32 @@ object HaskellComponentsManager {
})
if (!project.isDisposed) {
importedLibraryModuleNames.toSeq.distinct.foreach(mn => {
if (!project.isDisposed) {
if (StackReplsManager.getGlobalRepl(project).exists(_.available)) {
BrowseModuleComponent.findLibraryModuleIdentifiersSync(project, mn)
if (StackReplsManager.getGlobalRepl(project).exists(_.available)) {
importedLibraryModuleNames.toSeq.distinct.foreach(mn => {
if (!project.isDisposed) {
BrowseModuleComponent.findModuleIdentifiersSync(project, mn)
}
}
})
})
}
}
}
}
private def preloadAllLibraryIdentifiers(project: Project): Unit = {
if (!project.isDisposed) {
val componentInfos = findStackComponentInfos(project)
val packageInfos = componentInfos.flatMap(info => findStackComponentGlobalInfo(info).map(_.packageInfos).getOrElse(Seq())).distinct
if (!project.isDisposed) {
packageInfos.flatMap(_.exposedModuleNames).distinct.foreach(mn => {
if (!project.isDisposed) {
if (StackReplsManager.getGlobalRepl(project).exists(_.available)) {
BrowseModuleComponent.findLibraryModuleIdentifiersSync(project, mn)
if (StackReplsManager.getGlobalRepl(project).exists(_.available)) {
packageInfos.flatMap(_.exposedModuleNames).distinct.foreach(mn => {
if (!project.isDisposed) {
BrowseModuleComponent.findModuleIdentifiersSync(project, mn)
// We have to wait for other requests which have more priority because those are on dispatch thread
Thread.sleep(100)
}
}
})
})
}
}
}
}

View File

@ -73,10 +73,10 @@ class HaskellDocumentationProvider extends AbstractDocumentationProvider {
if (definedInSameFile) {
getQuickNavigateInfo(e, oe)
} else {
HaskellPsiUtil.findQualifiedNameParent(oe) match {
HaskellPsiUtil.findQualifiedName(oe) match {
case Some(qone) =>
val presentationText = HaskellPsiUtil.findNamedElement(e).flatMap { ne =>
if (HaskellPsiUtil.findExpressionParent(ne).isDefined || HaskellPsiUtil.findTypeSignatureDeclarationParent(ne).isDefined) {
if (HaskellPsiUtil.findExpression(ne).isDefined || HaskellPsiUtil.findTypeSignatureDeclaration(ne).isDefined) {
None
} else {
Some(DoubleNbsp + "<code>" +

View File

@ -29,37 +29,41 @@ import intellij.haskell.external.repl.StackReplsManager
import intellij.haskell.runconfig.console.HaskellConsoleView
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
private[component] object HaskellProjectFileInfoComponent {
private[component] object HaskellModuleInfoComponent {
private case class Key(project: Project, filePath: String)
private final val Cache: LoadingCache[Key, Option[InternalHaskellProjectFileInfo]] = Scaffeine().build((k: Key) => createFileInfo(k))
private final val Cache: LoadingCache[Key, Option[InternalHaskellModuleInfo]] = Scaffeine().build((k: Key) => createFileInfo(k))
def findHaskellProjectFileInfo(project: Project, filePath: String): Option[HaskellProjectFileInfo] = {
def findHaskellProjectFileInfo(project: Project, filePath: String): Option[StackComponentInfo] = {
val key = Key(project, filePath)
Cache.get(key) match {
case Some(internalInfo) =>
internalInfo.message match {
case Some(m) => HaskellNotificationGroup.warningEvent(project, m)
case None => None
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
}
internalInfo.stackComponentInfo match {
case Some(info) => Some(HaskellProjectFileInfo(info))
case _ => None
}
case _ =>
Cache.invalidate(key)
None
}
}
def findHaskellProjectFileInfo(psiFile: PsiFile): Option[HaskellProjectFileInfo] = {
def findHaskellProjectFileInfo(psiFile: PsiFile): Option[StackComponentInfo] = {
val project = psiFile.getProject
HaskellConsoleView.findConsoleInfo(psiFile) match {
case Some(consoleInfo) =>
val stackComponentInfos = StackReplsManager.getReplsManager(project).map(_.stackComponentInfos)
stackComponentInfos.flatMap(_.find(_.target == consoleInfo.stackTarget)).map(HaskellProjectFileInfo)
stackComponentInfos.flatMap(_.find(_.target == consoleInfo.stackTarget))
case None =>
val filePath = HaskellFileUtil.getAbsolutePath(psiFile)
filePath.flatMap(k => findHaskellProjectFileInfo(project, k))
@ -75,13 +79,13 @@ private[component] object HaskellProjectFileInfoComponent {
HaskellFileUtil.getAbsolutePath(psiFile).foreach(fp => Cache.invalidate(Key(psiFile.getProject, fp)))
}
private def createFileInfo(key: Key): Option[InternalHaskellProjectFileInfo] = {
private def createFileInfo(key: Key): Option[InternalHaskellModuleInfo] = {
val project = key.project
val filePath = key.filePath
StackReplsManager.getReplsManager(project).map(_.stackComponentInfos).map(stackComponentInfos => {
val info = getStackComponentInfo(project, filePath, stackComponentInfos)
InternalHaskellProjectFileInfo(info._1, info._2)
InternalHaskellModuleInfo(info._1, info._2)
})
}
@ -95,12 +99,15 @@ private[component] object HaskellProjectFileInfoComponent {
val (stackComponentInfo, message) = if (sourceDirsByInfo.size > 1) {
val sourceDirByInfo = sourceDirsByInfo.map({ case (info, sds) => (info, sds.maxBy(sd => Paths.get(sd).getNameCount)) })
val mostSpecificSourceDirByInfo = ScalaUtil.maxsBy(sourceDirByInfo)({ case (_, sd) => Paths.get(sd).getNameCount })
val libStackInfo = mostSpecificSourceDirByInfo.find(_._1.stanzaType == LibType)
val message = if (mostSpecificSourceDirByInfo.size > 1) {
Some(s"Ambiguous Stack target for file `$filePath`. It can belong to the source dir of more than one Stack target/Cabal stanza. The first one of `${mostSpecificSourceDirByInfo.map(_._1.target)}` is chosen.")
Some(s"Ambiguous Stack target for file `$filePath`. It can belong to the source dir of more than one Stack target/Cabal stanza: `${mostSpecificSourceDirByInfo.map(_._1.target)}` | `${libStackInfo.map(_._1.target).getOrElse(mostSpecificSourceDirByInfo.map(_._1.target).headOption.getOrElse("-"))}` is chosen.")
} else {
None
}
(mostSpecificSourceDirByInfo.headOption.map(_._1), message)
(libStackInfo.map(_._1).orElse(mostSpecificSourceDirByInfo.headOption.map(_._1)), message)
} else {
(sourceDirsByInfo.headOption.map(_._1), None)
}
@ -117,7 +124,5 @@ private[component] object HaskellProjectFileInfoComponent {
}
}
case class InternalHaskellProjectFileInfo(stackComponentInfo: Option[StackComponentInfo], message: Option[String])
case class HaskellProjectFileInfo(stackComponentInfo: StackComponentInfo)
case class InternalHaskellModuleInfo(stackComponentInfo: Option[StackComponentInfo], message: Option[String])

View File

@ -18,12 +18,9 @@ package intellij.haskell.external.component
import com.intellij.codeInsight.documentation.DocumentationManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
import com.intellij.psi.{PsiElement, PsiFile}
import intellij.haskell.editor.FileModuleIdentifiers
import intellij.haskell.external.component.HaskellComponentsManager.StackComponentInfo
import intellij.haskell.external.execution.{CompilationResult, HaskellCompilationResultHelper}
import intellij.haskell.external.repl.ProjectStackRepl.{Failed, Loaded}
import intellij.haskell.external.repl.ProjectStackRepl.Loaded
import intellij.haskell.external.repl._
import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.ScalaUtil
@ -31,16 +28,11 @@ import intellij.haskell.util.index.HaskellModuleNameIndex
private[component] object LoadComponent {
def isFileLoaded(psiFile: PsiFile): Boolean = {
private def isFileLoaded(psiFile: PsiFile): Boolean = {
val projectRepl = StackReplsManager.getProjectRepl(psiFile)
projectRepl.map(_.isFileLoaded(psiFile)).contains(Loaded)
}
def isFileLoadedFailed(psiFile: PsiFile): Boolean = {
val projectRepl = StackReplsManager.getProjectRepl(psiFile)
projectRepl.map(_.isFileLoaded(psiFile)).contains(Failed)
}
def isModuleLoaded(moduleName: Option[String], psiFile: PsiFile): Boolean = {
isFileLoaded(psiFile) || {
for {
@ -50,12 +42,7 @@ private[component] object LoadComponent {
}.contains(true)
}
def isBusy(project: Project, stackComponentInfo: StackComponentInfo): Boolean = {
val projectRepl = StackReplsManager.getRunningProjectRepl(project, stackComponentInfo)
projectRepl.exists(_.isBusy)
}
def isBusy(psiFile: PsiFile): Boolean = {
def isReplBusy(psiFile: PsiFile): Boolean = {
val projectRepl = StackReplsManager.getRunningProjectRepl(psiFile)
projectRepl.exists(_.isBusy)
}
@ -76,28 +63,26 @@ private[component] object LoadComponent {
projectRepl.load(psiFile, Some(fileChanged)) match {
case Some((loadOutput, loadFailed)) =>
if (fileChanged) {
ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.runnable {
ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.runnable {
TypeInfoComponent.invalidate(psiFile)
DefinitionLocationComponent.invalidate(psiFile)
HaskellModuleNameIndex.invalidateNotFoundEntries(project)
TypeInfoComponent.invalidate(psiFile)
DefinitionLocationComponent.invalidate(psiFile)
HaskellModuleNameIndex.invalidateNotFoundEntries(project)
// Have to refresh because import declarations can be changed
FileModuleIdentifiers.refresh(psiFile)
// Have to refresh because import declarations can be changed
FileModuleIdentifiers.refresh(psiFile)
val moduleName = HaskellPsiUtil.findModuleName(psiFile)
if (!loadFailed) {
NameInfoComponent.invalidate(psiFile)
moduleName.foreach(mn => {
BrowseModuleComponent.invalidateExportedModuleName(project, mn)
FileModuleIdentifiers.invalidate(mn)
})
val moduleName = HaskellPsiUtil.findModuleName(psiFile)
if (!loadFailed) {
NameInfoComponent.invalidate(psiFile)
moduleName.foreach(mn => {
BrowseModuleComponent.invalidateModuleName(project, mn)
FileModuleIdentifiers.invalidate(mn)
})
}
DocumentationManager.getInstance(project).updateToolwindowContext()
})
}
}
DocumentationManager.getInstance(project).updateToolwindowContext()
})
Some(HaskellCompilationResultHelper.createCompilationResult(psiFile, loadOutput.stderrLines, loadFailed))
case _ => None
}

View File

@ -41,7 +41,7 @@ private[component] object NameInfoComponent {
private case class Key(psiFile: PsiFile, name: String)
def findNameInfo(psiElement: PsiElement): NameInfoResult = {
HaskellPsiUtil.findQualifiedNameParent(psiElement) match {
HaskellPsiUtil.findQualifiedName(psiElement) match {
case Some(p) => findNameInfo(p, None)
case None => Left(NoInfoAvailable(ApplicationUtil.runReadAction(psiElement.getText), ApplicationUtil.runReadAction(psiElement.getContainingFile.getName)))
}
@ -109,7 +109,7 @@ private[component] object NameInfoComponent {
case Some(result) =>
result match {
case Right(_) => result
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReplNotAvailable) =>
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReplNotAvailable) =>
Cache.invalidate(key)
result
case _ => result
@ -117,20 +117,16 @@ private[component] object NameInfoComponent {
case None =>
if (isReadAccessAllowed) {
ProgressManager.checkCanceled()
if (isDispatchThread && !LoadComponent.isFileLoaded(psiFile)) {
Left(ModuleNotLoaded(psiFile.getName))
} else {
val result = findNameInfos(key)
result match {
case Right(_) =>
Cache.put(key, result)
result
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotLoaded(_)) =>
result
case Left(_) =>
Cache.put(key, result)
result
}
val result = findNameInfos(key)
result match {
case Right(_) =>
Cache.put(key, result)
result
case Left(ReplIsBusy) | Left(ReadActionTimeout(_)) | Left(IndexNotReady) | Left(ReplNotAvailable) | Left(ModuleNotAvailable(_)) =>
result
case Left(_) =>
Cache.put(key, result)
result
}
} else {
val result = Cache.get(key)
@ -139,7 +135,7 @@ private[component] object NameInfoComponent {
case Left(NoInfoAvailable(_, _)) =>
Cache.put(key, result)
result
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
result
}
}

View File

@ -51,6 +51,16 @@ object ProjectLibraryFileWatcher {
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 {
@ -110,10 +120,11 @@ object ProjectLibraryFileWatcher {
}
})
HaskellComponentsManager.invalidateBrowseInfo(project, libComponentInfos.flatMap(_.exposedModuleNames).toSeq)
dependentFiles.foreach { vf =>
HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toOption.flatten match {
HaskellFileUtil.convertToHaskellFileInReadAction(project, vf).toOption match {
case Some(psiFile) =>
HaskellComponentsManager.refreshCacheForModules(project, libComponentInfos.flatMap(_.exposedModuleNames).toSeq)
HaskellAnnotator.restartDaemonCodeAnalyzerForFile(psiFile)
case None => HaskellNotificationGroup.logInfoEvent(project, s"Could not invalidate cache and restart daemon analyzer for file ${vf.getName}")
}

View File

@ -22,7 +22,7 @@ import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.ProjectComponent
import com.intellij.openapi.progress.{PerformInBackgroundOption, ProgressIndicator, ProgressManager, Task}
import com.intellij.openapi.project.{Project, ProjectUtil}
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.roots.{ModifiableRootModel, ModuleRootModificationUtil}
import com.intellij.openapi.ui.Messages
@ -184,7 +184,7 @@ object StackProjectManager {
})
progressIndicator.setText("Busy with updating project and module settings")
val projectPath = ProjectUtil.guessProjectDir(project).getPath
val projectPath = project.getBasePath
val projectModules = HaskellProjectUtil.findProjectHaskellModules(project)
val packagePaths = StackProjectImportBuilder.getPackagePaths(project)
val packagePathsToAdd = packagePaths.filterNot { relativePath =>
@ -221,17 +221,11 @@ object StackProjectManager {
val replsLoad = ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.runnable {
StackReplsManager.getReplsManager(project).foreach(_.stackComponentInfos.filter(_.stanzaType == LibType).foreach { info =>
progressIndicator.setText("Busy with starting project REPL " + info.packageName)
val repl = StackReplsManager.getProjectRepl(project, info)
if (repl.exists(_.available)) {
repl match {
case Some(r) =>
val moduleNames = info.exposedModuleNames
HaskellNotificationGroup.logInfoEvent(project, s"Loading project modules in REPL ${info.packageName} " + moduleNames.mkString(", "))
r.load(moduleNames)
case None => HaskellNotificationGroup.logWarningEvent(project, s"REPL ${info.packageName} is not started")
}
Thread.sleep(2000) // Have to wait between starting the REPLs otherwise timeouts while starting
StackReplsManager.getProjectRepl(project, info) match {
case Some(r) if r.available => HaskellNotificationGroup.logInfoEvent(project, s"REPL ${info.packageName} is started")
case _ => HaskellNotificationGroup.logWarningEvent(project, s"REPL ${info.packageName} is not started")
}
Thread.sleep(1000) // Have to wait between starting the REPLs otherwise timeouts while starting
})
val projectFiles = ApplicationUtil.scheduleInReadActionWithWriteActionPriority(project,
@ -251,9 +245,9 @@ object StackProjectManager {
projectFilesWithImportedModuleNames match {
case Some(fm) =>
fm.foreach { case (f, moduleNames) =>
moduleNames.foreach(mn => HaskellModuleNameIndex.findFileByModuleName(project, mn))
moduleNames.foreach(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn))
HaskellNotificationGroup.logInfoEvent(project, "Loading module identifiers " + moduleNames.mkString(", "))
moduleNames.foreach(m => BrowseModuleComponent.loadExportedIdentifiersSync(project, f, m))
moduleNames.foreach(m => BrowseModuleComponent.findModuleIdentifiersSync(project, m))
}
case None => HaskellNotificationGroup.logInfoEvent(project, "Could not loaded module identifiers because of timeout ")
}

View File

@ -50,7 +50,7 @@ private[component] object TypeInfoComponent {
val isDispatchThread = ApplicationManager.getApplication.isDispatchThread
(for {
qne <- HaskellPsiUtil.findQualifiedNameParent(element)
qne <- HaskellPsiUtil.findQualifiedName(element)
pf <- getFile
} yield {
Key(pf, qne)
@ -73,7 +73,7 @@ private[component] object TypeInfoComponent {
}
}.getOrElse(Left(NoInfoAvailable(selectionModel.getSelectedText, psiFile.getName)))
} else {
Left(ModuleNotLoaded(moduleName.getOrElse(psiFile.getName)))
Left(ModuleNotAvailable(moduleName.getOrElse(psiFile.getName)))
}
}
@ -133,14 +133,14 @@ private[component] object TypeInfoComponent {
case Right(_) => result
case Left(NoInfoAvailable(_, _)) =>
result
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
synchronousCache.invalidate(key)
result
}
case None =>
val moduleName = HaskellPsiUtil.findModuleName(key.psiFile)
if (isDispatchThread && !LoadComponent.isModuleLoaded(moduleName, key.psiFile)) {
Left(ModuleNotLoaded(moduleName.getOrElse(key.psiFile.getName)))
Left(ModuleNotAvailable(moduleName.getOrElse(key.psiFile.getName)))
} else if (isDispatchThread) {
val result = findTypeInfoResult(key)
result match {
@ -149,7 +149,7 @@ private[component] object TypeInfoComponent {
result
case Left(NoInfoAvailable(_, _)) =>
result
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
synchronousCache.invalidate(key)
result
}
@ -160,7 +160,7 @@ private[component] object TypeInfoComponent {
result
case Left(NoInfoAvailable(_, _)) =>
result
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotLoaded(_)) | Left(ReadActionTimeout(_)) =>
case Left(ReplNotAvailable) | Left(ReplIsBusy) | Left(IndexNotReady) | Left(ModuleNotAvailable(_)) | Left(ReadActionTimeout(_)) =>
synchronousCache.invalidate(key)
result
}

View File

@ -22,8 +22,8 @@ package object component {
override def message: String = "No info because index is not ready"
}
case class ModuleNotLoaded(fileName: String) extends NoInfo {
override def message: String = s"No info because module of file $fileName is not loaded"
case class ModuleNotAvailable(name: String) extends NoInfo {
override def message: String = s"No info because $name is not loaded or found"
}
case class ReadActionTimeout(readActionDescription: String) extends NoInfo {

View File

@ -16,6 +16,8 @@
package intellij.haskell.external.execution
import java.util.concurrent.LinkedBlockingDeque
import com.intellij.compiler.impl._
import com.intellij.compiler.progress.CompilerTask
import com.intellij.execution.ExecutionException
@ -31,7 +33,7 @@ import intellij.haskell.settings.HaskellSettingsState
import intellij.haskell.stackyaml.StackYamlComponent
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil}
import scala.collection.mutable.ListBuffer
import scala.collection.JavaConverters._
import scala.concurrent.SyncVar
object StackCommandLine {
@ -154,10 +156,11 @@ object StackCommandLine {
private class MessageViewProcessAdapter(val compileContext: CompileContext, val progressIndicator: ProgressIndicator) extends ProcessAdapter() {
private final val WhileBuildingText = "-- While building custom"
private final val WhileBuildingText = "-- While building "
private final var whileBuildingTextIsPassed = false
private val previousMessageLines = ListBuffer[String]()
private val previousMessageLines = new LinkedBlockingDeque[String]
@volatile
private var globalError = false
override def onTextAvailable(event: ProcessEvent, outputType: Key[_]) {
@ -175,7 +178,7 @@ object StackCommandLine {
}
def addLastMessage(): Unit = {
if (previousMessageLines.nonEmpty) {
if (!previousMessageLines.isEmpty) {
addMessage()
}
}
@ -188,11 +191,11 @@ object StackCommandLine {
}
// End of sentence which was over multiple lines
if (previousMessageLines.nonEmpty && !text.startsWith(" ")) {
if (!previousMessageLines.isEmpty && !text.startsWith(" ")) {
addMessage()
}
previousMessageLines.append(text)
previousMessageLines.add(text)
} else if (text.startsWith("Warning:")) {
compileContext.addMessage(CompilerMessageCategory.WARNING, text, null, -1, -1)
} else if (text.startsWith("Error:")) {
@ -203,8 +206,9 @@ object StackCommandLine {
}
}
private def addMessage(): Unit = {
val errorMessageLine = previousMessageLines.mkString(" ")
val errorMessageLine = previousMessageLines.iterator().asScala.mkString(" ")
val compilationProblem = HaskellCompilationResultHelper.parseErrorLine(errorMessageLine.replaceAll("\n", " "))
compilationProblem match {
case Some(p@CompilationProblem(filePath, lineNr, columnNr, message)) if p.isWarning =>

View File

@ -19,7 +19,7 @@ package intellij.haskell.external.repl
import com.intellij.openapi.project.Project
import intellij.haskell.external.repl.StackRepl.StackReplOutput
case class GlobalStackRepl(project: Project, replTimeout: Int) extends StackRepl(project, None, Seq("--no-package-hiding"), replTimeout) {
case class GlobalStackRepl(project: Project, replTimeout: Int) extends StackRepl(project, None, Seq("--no-package-hiding", "--no-load"), replTimeout) {
private[this] var loadedModuleName: Option[String] = None

View File

@ -29,7 +29,7 @@ import intellij.haskell.util.{HaskellFileUtil, ScalaFutureUtil}
import scala.collection.JavaConverters._
import scala.concurrent.Future
case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponentInfo, replTimeout: Int) extends StackRepl(project, Some(stackComponentInfo), Seq(), replTimeout: Int) {
case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponentInfo, replTimeout: Int) extends StackRepl(project, Some(stackComponentInfo), Seq("--ghc-options", "-fobject-code"), replTimeout: Int) {
import intellij.haskell.external.repl.ProjectStackRepl._
@ -63,6 +63,9 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
@volatile
private var busy = false
@volatile
private var objectCodeEnabled = true
def isBusy: Boolean = {
busy
}
@ -76,7 +79,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
val filePath = getFilePath(psiFile)
def execute = {
findInfoForCommand(moduleName, psiFile, s":type-at $filePath $startLineNr $startColumnNr $endLineNr $endColumnNr $expression", setBusy = true)
executeModuleLoadedCommand(moduleName, psiFile, s":type-at $filePath $startLineNr $startColumnNr $endLineNr $endColumnNr $expression")
}
if (isReadAccessAllowed) {
@ -90,7 +93,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
val filePath = getFilePath(psiFile)
def execute = {
findInfoForCommand(moduleName, psiFile, s":loc-at $filePath $startLineNr $startColumnNr $endLineNr $endColumnNr $expression", setBusy = true)
executeModuleLoadedCommand(moduleName, psiFile, s":loc-at $filePath $startLineNr $startColumnNr $endLineNr $endColumnNr $expression")
}
if (isReadAccessAllowed) {
@ -102,7 +105,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
def findInfo(psiFile: PsiFile, name: String): Option[StackReplOutput] = {
def execute = {
executeWithLoad(psiFile, s":info $name")
executeWithLoad(psiFile, s":info $name", mustBeByteCode = true)
}
if (isReadAccessAllowed) {
@ -129,37 +132,42 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
private final val FailedModuleLoaded = "Failed, modules loaded: "
private final val FailedModulesLoaded = "Failed, modules loaded: "
private final val OkModulesLoaded = "Ok, modules loaded: "
private def setLoadedModules(o: StackReplOutput): Unit = {
private def setLoadedModules(output: StackReplOutput): Unit = {
loadedDependentModules.clear()
val loadedModuleNames = o.stdoutLines.find(l => l.startsWith(OkModulesLoaded) || l.startsWith(FailedModuleLoaded)).map(findLoadedModuleNames).getOrElse(Array())
val loadedModuleNames = output.stdoutLines.find(l => l.startsWith(OkModulesLoaded) || l.startsWith(FailedModulesLoaded)).map(findLoadedModuleNames).getOrElse(Array())
loadedModuleNames.foreach(mn => loadedDependentModules.put(mn, DependentModuleInfo()))
loadedModuleNames.foreach(mn => everLoadedDependentModules.put(mn, DependentModuleInfo()))
}
def load(moduleNames: Seq[String]): Unit = synchronized {
val moduleNamesString = moduleNames.mkString(" ")
executeWithSettingBusy(s":load $moduleNamesString").foreach(setLoadedModules)
}
def load(psiFile: PsiFile, fileChanged: Option[Boolean]): Option[(StackReplOutput, Boolean)] = synchronized {
val filePath = getFilePath(psiFile)
val reload = if (fileChanged.contains(true)) {
def load(psiFile: PsiFile, fileChanged: Option[Boolean], mustBeByteCode: Boolean = false): Option[(StackReplOutput, Boolean)] = synchronized {
val forceBytecodeLoad = if (mustBeByteCode) objectCodeEnabled else false
val reload = if (fileChanged.contains(true) && !forceBytecodeLoad) {
val loaded = isFileLoaded(psiFile)
loaded == Loaded || loaded == Failed
} else {
HaskellNotificationGroup.logInfoEvent(project, s"No :reload of file ${psiFile.getName} because this file is not changed or module not yet loaded")
false
}
val output = if (reload) {
executeWithSettingBusy(s":reload")
} else {
executeWithSettingBusy(s":load $filePath")
// In case module has to be compiled to byte-code: :set -fbyte-code AND load flag *
val byteCodeFlag = if (forceBytecodeLoad) "*" else ""
if (forceBytecodeLoad) {
objectCodeEnabled = false
executeWithSettingBusy(s":set -fbyte-code")
} else if (!objectCodeEnabled) {
objectCodeEnabled = true
executeWithSettingBusy(s":set -fobject-code")
}
val filePath = getFilePath(psiFile)
executeWithSettingBusy(s":load $byteCodeFlag$filePath")
}
output match {
case Some(o) =>
val loadFailed = isLoadFailed(o)
@ -177,26 +185,22 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
private def findLoadedModuleNames(line: String): Array[String] = {
if (line.startsWith(OkModulesLoaded)) {
line.replace(OkModulesLoaded, "").init.split(",").map(_.trim)
} else if (line.startsWith(FailedModuleLoaded)) {
line.replace(FailedModuleLoaded, "").init.split(",").map(_.trim)
} else if (line.startsWith(FailedModulesLoaded)) {
line.replace(FailedModulesLoaded, "").init.split(",").map(_.trim)
} else {
Array()
}
}
def getModuleIdentifiers(moduleName: String, psiFile: PsiFile): Option[StackReplOutput] = synchronized {
if (isBrowseModuleLoaded(moduleName) || load(psiFile, None).exists(_._2 == false)) {
def getModuleIdentifiers(moduleName: String, psiFile: Option[PsiFile]): Option[StackReplOutput] = synchronized {
if (psiFile.isEmpty || isBrowseModuleLoaded(moduleName) || psiFile.exists(pf => load(pf, None).exists(_._2 == false))) {
executeWithSettingBusy(s":browse! $moduleName")
} else {
HaskellNotificationGroup.logInfoEvent(project, s"Could not get module identifiers for module $moduleName because file ${psiFile.getName} is not loaded")
HaskellNotificationGroup.logInfoEvent(project, s"Could not get module identifiers for module $moduleName because file ${psiFile.map(_.getName).getOrElse("-")} is not loaded")
None
}
}
def getLocalModuleIdentifiers(moduleName: String, psiFile: PsiFile): Option[StackReplOutput] = {
executeWithLoad(psiFile, s":browse! *$moduleName", Some(moduleName))
}
override def restart(forceExit: Boolean): Unit = synchronized {
if (available && !starting) {
exit(forceExit)
@ -204,24 +208,20 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
}
private def findInfoForCommand(moduleName: Option[String], psiFile: PsiFile, command: String, setBusy: Boolean): Option[StackReplOutput] = synchronized {
private def executeModuleLoadedCommand(moduleName: Option[String], psiFile: PsiFile, command: String): Option[StackReplOutput] = synchronized {
if (moduleName.exists(isModuleLoaded)) {
if (setBusy) {
executeWithSettingBusy(command)
} else {
executeWithoutSettingBusy(command)
}
executeWithSettingBusy(command)
} else {
executeWithLoad(psiFile, command)
executeWithLoad(psiFile, command, mustBeByteCode = false)
}
}
private def executeWithLoad(psiFile: PsiFile, command: String, moduleName: Option[String] = None): Option[StackReplOutput] = synchronized {
private def executeWithLoad(psiFile: PsiFile, command: String, moduleName: Option[String] = None, mustBeByteCode: Boolean): Option[StackReplOutput] = synchronized {
loadedFile match {
case Some(info) if info.psiFile == psiFile && !info.loadFailed => executeWithSettingBusy(command)
case Some(info) if info.psiFile == psiFile && info.loadFailed => Some(StackReplOutput())
case Some(info) if info.psiFile == psiFile & !info.loadFailed & (if (mustBeByteCode) !objectCodeEnabled else true) => executeWithSettingBusy(command)
case Some(info) if info.psiFile == psiFile & info.loadFailed => Some(StackReplOutput())
case _ =>
load(psiFile, fileChanged = None)
load(psiFile, fileChanged = None, mustBeByteCode)
loadedFile match {
case None => None
case Some(info) if info.psiFile == psiFile && !info.loadFailed => executeWithSettingBusy(command)
@ -231,18 +231,14 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
private def executeWithSettingBusy(command: String) = {
busy = true
try {
busy = true
executeWithoutSettingBusy(command)
execute(command)
} finally {
busy = false
}
}
private def executeWithoutSettingBusy(command: String) = {
execute(command)
}
private def isLoadFailed(output: StackReplOutput): Boolean = {
output.stdoutLines.lastOption.exists(_.contains("Failed, "))
}

View File

@ -234,12 +234,12 @@ abstract class StackRepl(project: Project, componentInfo: Option[StackComponentI
val replGhciOptionsFilePath = createGhciOptionsFile.getAbsolutePath
val command = (Seq(stackPath, "repl") ++
componentInfo.map(_.target).toSeq ++
Seq("--with-ghc", "intero", "--no-load", "--no-build", "--ghci-options", s"-ghci-script=$replGhciOptionsFilePath", "--silent", "--ghc-options", "-v1") ++ extraOptions).mkString(" ")
Seq("--with-ghc", "intero", "--no-build", "--ghci-options", s"-ghci-script=$replGhciOptionsFilePath", "--silent", "--ghc-options", "-v1") ++ extraOptions).mkString(" ")
logInfo(s"Stack REPL will be started with command: $command")
val processBuilder = Option(EnvironmentUtil.getEnvironmentMap) match {
case None => Process(command, new File(project.getBasePath))
case None => Process(command, new File(componentInfo.map(_.modulePath).getOrElse(project.getBasePath)))
case Some(envMap) => Process(command, new File(project.getBasePath), envMap.asScala.toArray: _*)
}
@ -271,7 +271,6 @@ abstract class StackRepl(project: Project, componentInfo: Option[StackComponentI
if (stanzaType.isDefined) {
execute(":set -fdefer-type-errors", forceExecute = true)
execute(":set -fno-max-valid-substitutions", forceExecute = true) // TODO Check for min GHC version
execute(":set -fobject-code", forceExecute = true)
if (HaskellProjectUtil.setNoDiagnosticsShowCaretFlag(project)) {
execute(s":set ${StackCommandLine.NoDiagnosticsShowCaretFlag}", forceExecute = true)
}

View File

@ -84,10 +84,10 @@ private[external] object StackReplsManager {
private def createStackComponentInfos(project: Project, moduleCabalInfos: Iterable[(Module, CabalInfo)]): Iterable[StackComponentInfo] = {
moduleCabalInfos.flatMap {
case (m: Module, cabalInfo: CabalInfo) => cabalInfo.cabalStanzas.map {
case cs: LibraryCabalStanza => StackComponentInfo(m, cs.packageName, cs.targetName, LibType, cs.sourceDirs, None, cs.isNoImplicitPreludeActive, cs.buildDepends, cs.exposedModuleNames)
case cs: ExecutableCabalStanza => StackComponentInfo(m, cs.packageName, cs.targetName, ExeType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
case cs: TestSuiteCabalStanza => StackComponentInfo(m, cs.packageName, cs.targetName, TestSuiteType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
case cs: BenchmarkCabalStanza => StackComponentInfo(m, cs.packageName, cs.targetName, BenchmarkType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
case cs: LibraryCabalStanza => StackComponentInfo(m, cs.modulePath, cs.packageName, cs.targetName, LibType, cs.sourceDirs, None, cs.isNoImplicitPreludeActive, cs.buildDepends, cs.exposedModuleNames)
case cs: ExecutableCabalStanza => StackComponentInfo(m, cs.modulePath, cs.packageName, cs.targetName, ExeType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
case cs: TestSuiteCabalStanza => StackComponentInfo(m, cs.modulePath, cs.packageName, cs.targetName, TestSuiteType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
case cs: BenchmarkCabalStanza => StackComponentInfo(m, cs.modulePath, cs.packageName, cs.targetName, BenchmarkType, cs.sourceDirs, cs.mainIs, cs.isNoImplicitPreludeActive, cs.buildDepends)
}
}
}

View File

@ -47,6 +47,7 @@
methods("q_con_qualifier1|q_con_qualifier2|q_con_qualifier3|q_con_qualifier4|qualifier")=[getName setName getNameIdentifier getReference getPresentation]
implements("q_con_qualifier1|q_con_qualifier2|q_con_qualifier3|q_con_qualifier4|qualifier")="intellij.haskell.psi.HaskellQualifierElement"
mixin("q_con_qualifier1|q_con_qualifier2|q_con_qualifier3|q_con_qualifier4|qualifier")="intellij.haskell.psi.impl.HaskellQualifierElementImpl"
implements("q_con_qualifier1|q_con_qualifier2|q_con_qualifier3|q_con_qualifier4|qualifier")="intellij.haskell.psi.HaskellNamedElement"
implements("text_literal")=["intellij.haskell.psi.HaskellStringLiteralElement" "com.intellij.psi.PsiLanguageInjectionHost"]
mixin("text_literal")="intellij.haskell.psi.impl.HaskellStringLiteralElementImpl"
@ -170,7 +171,7 @@ q_names ::= q_name (COMMA q_name)*
gtycon ::= QUOTE? q_name | LEFT_PAREN RIGHT_ARROW RIGHT_PAREN | LEFT_PAREN RIGHT_PAREN | QUOTE? LEFT_BRACKET RIGHT_BRACKET | LEFT_PAREN COMMA (COMMA)* RIGHT_PAREN
private constrs ::= constr (onls VERTICAL_BAR onls constr)*
private constr ::= (type_signature | constr1 | constr2 | constr3)
constr ::= (type_signature | constr1 | constr2 | constr3)
constr1 ::= pragma? (onls q_name)? (onls pragma)? onls LEFT_BRACE onl fielddecl? ((onl COMMA)? onl fielddecl)* onl RIGHT_BRACE
constr2 ::= pragma? onls (ttype | q_name | LEFT_PAREN q_name* RIGHT_PAREN | LEFT_BRACKET q_name* RIGHT_BRACKET) (onls pragma onls)? ((onls pragma)? onls ttype (onls pragma)?)* // Base.hs: data () = () and GHC.Types.hs: data [] a = [] | a : [a]
constr3 ::= (onls pragma? onls ttype)+

View File

@ -61,7 +61,7 @@ class HLintQuickfix(startElement: PsiElement, endElement: PsiElement, startLineN
}
}
HaskellPsiUtil.findExpressionParent(commonParent).foreach(e => {
HaskellPsiUtil.findExpression(commonParent).foreach(e => {
val manager = PsiDocumentManager.getInstance(project)
val document = manager.getDocument(psiFile)
manager.doPostponedOperationsAndUnblockDocument(document)

View File

@ -20,9 +20,11 @@ import java.io.File
import com.intellij.ide.util.projectWizard._
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.{ApplicationManager, WriteAction}
import com.intellij.openapi.application.{ApplicationManager, ModalityState, WriteAction}
import com.intellij.openapi.module.{ModifiableModuleModel, Module, ModuleType}
import com.intellij.openapi.project.{Project, ProjectManager, ProjectUtil}
import com.intellij.openapi.progress.impl.CoreProgressManager
import com.intellij.openapi.progress.{EmptyProgressIndicator, ProgressIndicator, Task}
import com.intellij.openapi.project.{Project, ProjectManager}
import com.intellij.openapi.projectRoots.SdkTypeId
import com.intellij.openapi.roots._
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable
@ -75,7 +77,7 @@ class HaskellModuleBuilder extends TemplateModuleBuilder(null, HaskellModuleType
if (isNewProjectWithoutExistingSources) {
val packageRelativePath = StackYamlComponent.getPackagePaths(project).flatMap(_.headOption)
packageRelativePath.flatMap(pp => HaskellModuleBuilder.createCabalInfo(rootModel.getProject, HaskellFileUtil.getAbsolutePath(ProjectUtil.guessProjectDir(project)), pp)) match {
packageRelativePath.flatMap(pp => HaskellModuleBuilder.createCabalInfo(rootModel.getProject, project.getBasePath, pp)) match {
case Some(ci) => cabalInfo = ci
case None =>
Messages.showErrorDialog(s"Could not create Haskell module because could not retrieve or parse Cabal file for package path `$packageRelativePath`", "No Cabal file info")
@ -375,10 +377,18 @@ object HaskellModuleBuilder {
getProjectLibraryTable(project).getLibraries.find(_.getName == library.getName).foreach(library => {
val model = getProjectLibraryTable(project).getModifiableModel
model.removeLibrary(library)
ApplicationManager.getApplication.invokeAndWait(ScalaUtil.runnable(WriteAction.run(() => model.commit())))
runSynchronously(createTask(project, ScalaUtil.runnable(WriteAction.run(() => model.commit()))))
})
}
private def createTask(project: Project, action: => Unit) = {
new Task.Modal(project, "Creating/updating module", true) {
override def run(indicator: ProgressIndicator): Unit = {
action
}
}
}
private def createProjectLibrary(project: Project, libraryDependency: HaskellLibraryDependency, projectLibDirectory: File): Library = {
val projectLibraryTableModel = getProjectLibraryTable(project).getModifiableModel
val (libraryName, sourceRootPath) = (libraryDependency.nameVersion, getPackageDirectory(projectLibDirectory, libraryDependency))
@ -388,8 +398,8 @@ object HaskellModuleBuilder {
libraryModel.addRoot(sourceRootUrl, OrderRootType.CLASSES)
libraryModel.addRoot(sourceRootUrl, OrderRootType.SOURCES)
ApplicationManager.getApplication.invokeAndWait(ScalaUtil.runnable(WriteAction.run(() => libraryModel.commit())))
ApplicationManager.getApplication.invokeAndWait(ScalaUtil.runnable(WriteAction.run(() => projectLibraryTableModel.commit())))
runSynchronously(createTask(project, WriteAction.run(() => libraryModel.commit())))
runSynchronously(createTask(project, WriteAction.run(() => projectLibraryTableModel.commit())))
library
}
@ -399,15 +409,25 @@ object HaskellModuleBuilder {
})
}
private val progressManager = new CoreProgressManager
private def runSynchronously(task: Task): Unit = {
if (ApplicationManager.getApplication.isDispatchThread) {
progressManager.runProcessWithProgressSynchronously(task, null)
}
else ApplicationManager.getApplication.invokeAndWait(() => progressManager.runProcessWithProgressInCurrentThread(task, new EmptyProgressIndicator, ModalityState.defaultModalityState()))
}
trait HaskellDependency {
def name: String
def version: String
def nameVersion: String = s"$name-$version"
def nameVersion = s"$name-$version"
}
case class HaskellLibraryDependency(name: String, version: String) extends HaskellDependency
case class HaskellModuleDependency(name: String, version: String, module: Module) extends HaskellDependency
}

View File

@ -38,5 +38,5 @@ class StackProjectImportProvider(builder: StackProjectImportBuilder) extends Pro
}
}
override def getFileSample: String = "which contains stack.yaml"
override def getFileSample: String = "<b>Haskell</b> project file (stack.yaml)"
}

View File

@ -39,7 +39,7 @@ class GotoByDeclarationContributor extends GotoClassContributor {
override def getItemsByName(name: String, pattern: String, project: Project, includeNonProjectItems: Boolean): Array[NavigationItem] = {
val namedElements = GotoHelper.getNamedElements(name, pattern, project, includeNonProjectItems)
val declarationElements = namedElements.map(ne => (ne, HaskellPsiUtil.findHighestDeclarationElementParent(ne)))
val declarationElements = namedElements.map(ne => (ne, HaskellPsiUtil.findHighestDeclarationElement(ne)))
declarationElements.sortWith(sortByClassDeclarationFirst).flatMap(_._2).toArray
}

View File

@ -30,6 +30,59 @@ import intellij.haskell.util.index.HaskellModuleNameIndex
class HaskellReference(element: HaskellNamedElement, textRange: TextRange) extends PsiPolyVariantReferenceBase[HaskellNamedElement](element, textRange) {
private def findModule(project: Project, modId: HaskellModid) = {
ProgressManager.checkCanceled()
HaskellModuleNameIndex.findFilesByModuleName(project, modId.getName) match {
case Right(files) => files.headOption.map(HaskellFileResolveResult)
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
private def findQualifierDeclaration(project: Project, psiFile: PsiFile, qualifier: HaskellQualifier) = {
ProgressManager.checkCanceled()
val importDeclarations = HaskellPsiUtil.findImportDeclarations(psiFile)
findQualifier(importDeclarations, qualifier) match {
case Some(ne) => Some(HaskellNamedElementResolveResult(ne))
case None => None
ProgressManager.checkCanceled()
findHaskellFile(importDeclarations, qualifier, project) match {
case Right(r) => r.map(HaskellFileResolveResult)
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
}
private def findImportedIdentifierDeclaration(project: Project, psiFile: PsiFile, namedElement: HaskellNamedElement) = {
ProgressManager.checkCanceled()
HaskellPsiUtil.findImportDeclaration(namedElement) match {
case Some(d) =>
val importQualifier = Option(d.getImportQualifiedAs).map(_.getQualifier.getName).orElse(d.getModuleName)
ProgressManager.checkCanceled()
resolveReference(namedElement, psiFile, project, importQualifier) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(_) => None
}
case None => None
}
}
private def isPartOfModId(namedElement: HaskellNamedElement): Boolean = {
Option(namedElement.getParent).map(_.getNode.getElementType).contains(HaskellTypes.HS_MODID)
}
private def isPartOfQualifier(namedElement: HaskellNamedElement): Boolean = {
Option(namedElement.getParent).map(_.getNode.getElementType).contains(HaskellTypes.HS_QUALIFIER)
}
private def isPartOfQualifiedAs(namedElement: HaskellNamedElement): Boolean = {
Option(namedElement.getParent).map(_.getNode.getElementType).contains(HaskellTypes.HS_IMPORT_QUALIFIED_AS)
}
override def multiResolve(incompleteCode: Boolean): Array[ResolveResult] = {
val project = element.getProject
if (StackProjectManager.isInitializing(project)) {
@ -41,69 +94,48 @@ class HaskellReference(element: HaskellNamedElement, textRange: TextRange) exten
} else {
ProgressManager.checkCanceled()
val psiFile = element.getContainingFile.getOriginalFile
val result = element match {
case mi: HaskellModid => HaskellModuleNameIndex.findFileByModuleName(project, mi.getName) match {
case Right(files) => files.headOption.map(HaskellFileResolveResult)
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
case qe: HaskellQualifierElement =>
val importDeclarations = HaskellPsiUtil.findImportDeclarations(psiFile)
findQualifier(importDeclarations, qe) match {
case Some(q) => HaskellPsiUtil.findNamedElement(q).map(HaskellNamedElementResolveResult)
case None => findHaskellFile(importDeclarations, qe, project) match {
case Right(r) => r.map(HaskellFileResolveResult)
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
case q: HaskellQualifier if isPartOfQualifiedAs(q) => Some(HaskellNamedElementResolveResult(q))
case q: HaskellQualifier => findQualifierDeclaration(project, psiFile, q)
case mid: HaskellModid => findModule(project, mid)
case ne: HaskellNamedElement if isPartOfQualifier(ne) | isPartOfQualifiedAs(ne) | isPartOfModId(ne) => None
case ne: HaskellNamedElement =>
HaskellPsiUtil.findImportDeclarationParent(ne) match {
case Some(id) =>
val importQualifier = Option(id.getImportQualifiedAs).map(_.getQualifier.getName).orElse(id.getModuleName)
resolveReference(ne, psiFile, project, importQualifier) match {
ProgressManager.checkCanceled()
findImportedIdentifierDeclaration(project, psiFile, ne).orElse {
HaskellPsiUtil.findTypeSignatureDeclaration(ne) match {
case None => resolveReference(ne, psiFile, project = project, None) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
case None =>
if (HaskellPsiUtil.findQualifierParent(ne).isDefined || HaskellPsiUtil.findModIdElement(element).isDefined) {
// Because they are already handled by HaskellQualifierElement and HaskellModId case
None
} else {
ProgressManager.checkCanceled()
HaskellPsiUtil.findTypeSignatureDeclarationParent(ne) match {
case None => resolveReference(ne, psiFile, project = project, None) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(noInfo) => Some(NoResolveResult(noInfo))
case Some(ts) =>
def find(e: PsiElement): Option[HaskellNamedElement] = {
Option(PsiTreeUtil.findSiblingForward(e, HaskellTypes.HS_TOP_DECLARATION_LINE, null)) match {
case Some(d) if Option(d.getFirstChild).flatMap(c => Option(c.getFirstChild)).exists(_.isInstanceOf[HaskellExpression]) => HaskellPsiUtil.findNamedElements(d).headOption.find(_.getName == ne.getName)
case _ => None
}
case Some(ts) =>
}
def find(e: PsiElement): Option[HaskellNamedElement] = {
Option(PsiTreeUtil.findSiblingForward(e, HaskellTypes.HS_TOP_DECLARATION_LINE, null)) match {
case Some(d) if Option(d.getFirstChild).flatMap(c => Option(c.getFirstChild)).exists(_.isInstanceOf[HaskellExpression]) => HaskellPsiUtil.findNamedElements(d).headOption.find(_.getName == ne.getName)
case _ => None
}
}
ProgressManager.checkCanceled()
ProgressManager.checkCanceled()
// Work around Intero bug.
Option(ts.getParent).flatMap(p => Option(p.getParent)) match {
case Some(p) =>
find(p) match {
case Some(ee) => Some(HaskellNamedElementResolveResult(ee))
case None => resolveReference(ne, psiFile, project, None) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
// Work around Intero bug.
Option(ts.getParent).flatMap(p => Option(p.getParent)) match {
case Some(p) =>
find(p) match {
case Some(ee) => Some(HaskellNamedElementResolveResult(ee))
case None => resolveReference(ne, psiFile, project, None) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
case None => resolveReference(ne, psiFile, project, None) match {
case Right(r) => Some(HaskellNamedElementResolveResult(r))
case Left(noInfo) => Some(NoResolveResult(noInfo))
}
}
}
}
}
case _ => None
}
@ -123,7 +155,7 @@ class HaskellReference(element: HaskellNamedElement, textRange: TextRange) exten
NoInfoAvailable(ApplicationUtil.runReadAction(namedElement.getName), psiFile.getName)
}
HaskellPsiUtil.findQualifiedNameParent(namedElement) match {
HaskellPsiUtil.findQualifiedName(namedElement) match {
case Some(qualifiedNameElement) =>
ProgressManager.checkCanceled()
resolveReferenceByDefinitionLocation(qualifiedNameElement, psiFile, importQualifier)
@ -142,15 +174,14 @@ class HaskellReference(element: HaskellNamedElement, textRange: TextRange) exten
}
private def findQualifier(importDeclarations: Iterable[HaskellImportDeclaration], qualifierElement: HaskellQualifierElement): Option[HaskellNamedElement] = {
importDeclarations.flatMap(id => Option(id.getImportQualifiedAs)).flatMap(iqa => Option(iqa.getQualifier)).find(_.getName == qualifierElement.getName).
orElse(importDeclarations.filter(id => Option(id.getImportQualified).isDefined && Option(id.getImportQualifiedAs).isEmpty).find(mi => Option(mi.getModid).map(_.getName).contains(qualifierElement.getName)).map(_.getModid))
importDeclarations.flatMap(id => Option(id.getImportQualifiedAs)).flatMap(iqa => Option(iqa.getQualifier)).find(_.getName == qualifierElement.getName)
}
private def findHaskellFile(importDeclarations: Iterable[HaskellImportDeclaration], qualifierElement: HaskellQualifierElement, project: Project): Either[NoInfo, Option[PsiFile]] = {
private def findHaskellFile(importDeclarations: Iterable[HaskellImportDeclaration], qualifierElement: HaskellNamedElement, project: Project): Either[NoInfo, Option[PsiFile]] = {
val result = for {
id <- importDeclarations.find(id => id.getModuleName.contains(qualifierElement.getName))
mn <- id.getModuleName
} yield HaskellModuleNameIndex.findFileByModuleName(project, mn)
} yield HaskellModuleNameIndex.findFilesByModuleName(project, mn)
result match {
case Some(Right(files)) => Right(files.headOption)
@ -163,34 +194,55 @@ class HaskellReference(element: HaskellNamedElement, textRange: TextRange) exten
object HaskellReference {
def resolveInstanceReferences(project: Project, namedElement: HaskellNamedElement, nameInfos: Iterable[NameInfoComponentResult.NameInfo]): Seq[HaskellNamedElement] = {
val result = nameInfos.map(ni => findIdentifiersByNameInfo(ni, namedElement, project)).toSeq.distinct
if (result.contains(Left(ReadActionTimeout))) {
val identifiers = nameInfos.map(ni => findIdentifiersByNameInfo(ni, namedElement, project)).toSeq.distinct
if (identifiers.contains(Left(ReadActionTimeout))) {
HaskellEditorUtil.showStatusBarBalloonMessage(project, "Navigating to instance declarations is not available at this moment")
Seq()
} else {
result.flatMap(_.toOption).flatten
identifiers.flatMap(_.toOption).flatten
}
}
def findIdentifiersByLibraryNameInfo(project: Project, libraryNameInfo: LibraryNameInfo, name: String): Either[NoInfo, Seq[HaskellNamedElement]] = {
findIdentifiersByModuleAndName(project, libraryNameInfo.moduleName, name)
findIdentifiersByModuleAndName(project, Seq(libraryNameInfo.moduleName), name)
}
def findIdentifiersByModuleAndName(project: Project, moduleName: String, name: String): Either[NoInfo, Seq[HaskellNamedElement]] = {
def findIdentifiersByModuleAndName(project: Project, moduleNames: Seq[String], name: String): Either[NoInfo, Seq[HaskellNamedElement]] = {
ProgressManager.checkCanceled()
// For know we just take the first module which contains element with same name
for {
moduleNameFiles <- HaskellModuleNameIndex.findFileByModuleName(project, moduleName)
() = ProgressManager.checkCanceled()
ne <- Right(moduleNameFiles.flatMap(findIdentifierInFileByName(_, name)))
} yield ne
moduleNames.map(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn)).find(_.isLeft) match {
case Some(Left(noInfo)) => Left(noInfo)
case _ =>
ProgressManager.checkCanceled()
val identifiers = moduleNames.map(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn)).flatMap(_.toSeq.flatMap(_.flatMap(f => findIdentifierInFileByName(f, name))))
if (identifiers.isEmpty) {
val importedModuleNames = moduleNames.map(mn => HaskellModuleNameIndex.findFilesByModuleName(project, mn)).flatMap(_.toSeq.flatMap(_.flatMap(pf => HaskellPsiUtil.findImportDeclarations(pf).flatMap(_.getModuleName))))
if (importedModuleNames.isEmpty) {
Right(Seq())
} else {
findIdentifiersByModuleAndName(project, importedModuleNames, name)
}
} else {
Right(identifiers)
}
}
}
def findIdentifierInFileByName(psifile: PsiFile, name: String): Option[HaskellNamedElement] = {
import scala.collection.JavaConverters._
def findInDeclarations = {
ProgressManager.checkCanceled()
val topLevelExpressions = HaskellPsiUtil.findTopLevelExpressions(psifile)
ProgressManager.checkCanceled()
val expressionIdentifiers = topLevelExpressions.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement)).find(_.getName == name)
ProgressManager.checkCanceled()
if (expressionIdentifiers.isEmpty) {
val declarationElements = HaskellPsiUtil.findHaskellDeclarationElements(psifile)
ProgressManager.checkCanceled()
@ -200,30 +252,8 @@ object HaskellReference {
ProgressManager.checkCanceled()
declarationIdentifiers.toSeq.sortWith(sortByClassDeclarationFirst).headOption
}
ProgressManager.checkCanceled()
if (HaskellProjectUtil.isLibraryFile(psifile)) {
findInDeclarations
} else if (HaskellProjectUtil.isSourceFile(psifile)) {
ProgressManager.checkCanceled()
val topLevelExpressions = HaskellPsiUtil.findTopLevelExpressions(psifile)
ProgressManager.checkCanceled()
val expressionIdentifiers = topLevelExpressions.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement)).find(_.getName == name)
ProgressManager.checkCanceled()
if (expressionIdentifiers.isEmpty) {
findInDeclarations
} else {
expressionIdentifiers
}
} else {
None
expressionIdentifiers
}
}
@ -240,12 +270,12 @@ object HaskellReference {
orElse {
ProgressManager.checkCanceled()
None
}.orElse(HaskellPsiUtil.findHighestDeclarationElementParent(element).flatMap(_.getIdentifierElements.find(_.getName == name)).
}.orElse(HaskellPsiUtil.findHighestDeclarationElement(element).flatMap(_.getIdentifierElements.find(_.getName == name)).
orElse {
ProgressManager.checkCanceled()
None
}.orElse(HaskellPsiUtil.findQualifiedNameParent(element).map(_.getIdentifierElement)).find(_.getName == name)).orElse {
HaskellPsiUtil.findTypeParent(element).flatMap(_.getQNameList.asScala.map(_.getIdentifierElement).find(_.getName == name))
}.orElse(HaskellPsiUtil.findQualifiedName(element).map(_.getIdentifierElement)).find(_.getName == name)).orElse {
HaskellPsiUtil.findTtype(element).flatMap(_.getQNameList.asScala.map(_.getIdentifierElement).find(_.getName == name))
}
} yield namedElement
@ -255,7 +285,7 @@ object HaskellReference {
}
private def sortByClassDeclarationFirst(namedElement1: HaskellNamedElement, namedElement2: HaskellNamedElement): Boolean = {
(HaskellPsiUtil.findDeclarationElementParent(namedElement1), HaskellPsiUtil.findDeclarationElementParent(namedElement2)) match {
(HaskellPsiUtil.findDeclarationElement(namedElement1), HaskellPsiUtil.findDeclarationElement(namedElement2)) match {
case (Some(_: HaskellClassDeclaration), _) => true
case (_, _) => false
}
@ -270,7 +300,7 @@ object HaskellReference {
val (virtualFile, psiFile) = HaskellProjectUtil.findFile(pni.filePath, project)
ProgressManager.checkCanceled()
(virtualFile, psiFile) match {
case (Some(vf), Right(Some(pf))) => findIdentifierByLocation(project, vf, pf, pni.lineNr, pni.columnNr, name).map(r => Right(Seq(r))).getOrElse(Left(NoInfoAvailable(name, "-")))
case (Some(vf), Right(pf)) => findIdentifierByLocation(project, vf, pf, pni.lineNr, pni.columnNr, name).map(r => Right(Seq(r))).getOrElse(Left(NoInfoAvailable(name, "-")))
case (_, Right(_)) => Left(NoInfoAvailable(name, "-"))
case (_, Left(noInfo)) => Left(noInfo)
}

View File

@ -53,7 +53,7 @@ class HoogleByNameContributor extends ChooseByNameContributor {
val navigationItems = HoogleComponent.runHoogle(project, hooglePattern, count = 25).getOrElse(Seq()).flatMap {
case ModulePattern(moduleName) =>
ProgressManager.checkCanceled()
HaskellModuleNameIndex.findFileByModuleName(project, moduleName) match {
HaskellModuleNameIndex.findFilesByModuleName(project, moduleName) match {
case Right(files) => files
case _ => Seq()
}
@ -69,10 +69,10 @@ class HoogleByNameContributor extends ChooseByNameContributor {
ProgressManager.checkCanceled()
val navigationItemByNameInfo = result.toOption.flatMap(_.headOption) match {
case Some(lni: LibraryNameInfo) => HaskellReference.findIdentifiersByLibraryNameInfo(project, lni, name).toOption.getOrElse(Seq()).
flatMap(HaskellPsiUtil.findDeclarationElementParent).map(d => createLibraryNavigationItem(d, moduleName))
flatMap(HaskellPsiUtil.findDeclarationElement).map(d => createLibraryNavigationItem(d, moduleName))
case Some(pni: ProjectNameInfo) =>
HaskellProjectUtil.findFile(pni.filePath, project) match {
case (Some(virtualFile), Right(Some(psiFile))) => HaskellReference.findIdentifierByLocation(project, virtualFile, psiFile, pni.lineNr, pni.columnNr, name).flatMap(HaskellPsiUtil.findDeclarationElementParent).toSeq
case (Some(virtualFile), Right(psiFile)) => HaskellReference.findIdentifierByLocation(project, virtualFile, psiFile, pni.lineNr, pni.columnNr, name).flatMap(HaskellPsiUtil.findDeclarationElement).toSeq
case (_, _) => Seq()
}
case _ => Seq()
@ -81,11 +81,11 @@ class HoogleByNameContributor extends ChooseByNameContributor {
if (navigationItemByNameInfo.nonEmpty) {
navigationItemByNameInfo
} else {
val identifier = HaskellReference.findIdentifiersByModuleAndName(project, moduleName, name).toOption
if (identifier.isEmpty) {
val identifiers = HaskellReference.findIdentifiersByModuleAndName(project, Seq(moduleName), name).toOption
if (identifiers.isEmpty) {
NotFoundResult(moduleName, declaration)
} else {
identifier.getOrElse(Seq()).flatMap(HaskellPsiUtil.findDeclarationElementParent)
identifiers.getOrElse(Seq()).map(e => HaskellPsiUtil.findDeclarationElement(e).getOrElse(e))
}
}
})
@ -94,7 +94,7 @@ class HoogleByNameContributor extends ChooseByNameContributor {
Seq(NotFoundNavigationItem(d))
}
navigationItems.zipWithIndex.map({ case (item, i) => new NavigationItem {
navigationItems.groupBy(_.getPresentation.getLocationString).flatMap(_._2.headOption).zipWithIndex.map({ case (item, i) => new NavigationItem {
// Hack to display items in same order as given by Hoogle
override def getName: String = {

View File

@ -21,42 +21,6 @@ import com.intellij.psi.PsiElement
object HaskellElementCondition {
final val ImportDeclarationsCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellImportDeclarations => true
case _ => false
}
}
}
final val ImportDeclarationCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellImportDeclaration => true
case _ => false
}
}
}
final val ImportHidingSpecCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellImportHidingSpec => true
case _ => false
}
}
}
final val ImportSpecCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellImportSpec => true
case _ => false
}
}
}
final val QualifiedNameElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
@ -66,15 +30,6 @@ object HaskellElementCondition {
}
}
final val TtypeElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellTtype => true
case _ => false
}
}
}
final val DeclarationElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
@ -84,24 +39,6 @@ object HaskellElementCondition {
}
}
final val DataDeclarationElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellDataDeclaration => true
case _ => false
}
}
}
final val NewTypeDeclarationElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellNewtypeDeclaration => true
case _ => false
}
}
}
final val HighestDeclarationElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
@ -112,33 +49,6 @@ object HaskellElementCondition {
}
}
final val ModuleDeclarationCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellModuleDeclaration => true
case _ => false
}
}
}
final val TypeSignatureCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellTypeSignature => true
case _ => false
}
}
}
final val ExpressionCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellExpression => true
case _ => false
}
}
}
final val NamedElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
@ -147,22 +57,4 @@ object HaskellElementCondition {
}
}
}
final val ModIdElementCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellModid => true
case _ => false
}
}
}
final val FileHeaderCondition = new Condition[PsiElement]() {
override def value(psiElement: PsiElement): Boolean = {
psiElement match {
case _: HaskellFileHeader => true
case _ => false
}
}
}
}

View File

@ -54,7 +54,21 @@ object HaskellPsiUtil {
def findModIdElement(psiElement: PsiElement): Option[HaskellModid] = {
psiElement match {
case e: HaskellModid => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ModIdElementCondition)).map(_.asInstanceOf[HaskellModid])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_MODID)).map(_.getPsi.asInstanceOf[HaskellModid])
}
}
def findDataConstr(psiElement: PsiElement): Option[HaskellConstr] = {
psiElement match {
case e: HaskellConstr => Some(e)
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_CONSTR)).map(_.getPsi.asInstanceOf[HaskellConstr])
}
}
def findDataFieldDecl(psiElement: PsiElement): Option[HaskellFielddecl] = {
psiElement match {
case e: HaskellFielddecl => Some(e)
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_FIELDDECL)).map(_.getPsi.asInstanceOf[HaskellFielddecl])
}
}
@ -72,7 +86,7 @@ object HaskellPsiUtil {
}
def findTopLevelDeclarations(psiFile: PsiFile): Iterable[HaskellDeclarationElement] = {
findHaskellDeclarationElements(psiFile).filterNot(e => e.getNode.getElementType == HS_IMPORT_DECLARATION)
findHaskellDeclarationElements(psiFile).filterNot(e => Seq(HS_IMPORT_DECLARATION, HS_MODULE_DECLARATION).contains(e.getNode.getElementType))
}
def findModuleDeclaration(psiFile: PsiFile): Option[HaskellModuleDeclaration] = {
@ -111,102 +125,63 @@ object HaskellPsiUtil {
}
}
def findQualifierParent(psiElement: PsiElement): Option[HaskellQualifier] = {
psiElement match {
case e: HaskellQualifier => Some(e)
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_QUALIFIER)).map(_.getPsi.asInstanceOf[HaskellQualifier])
}
}
def findQualifiedNameParent(psiElement: PsiElement): Option[HaskellQualifiedNameElement] = {
def findQualifiedName(psiElement: PsiElement): Option[HaskellQualifiedNameElement] = {
psiElement match {
case e: HaskellQualifiedNameElement => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, QualifiedNameElementCondition)).map(_.asInstanceOf[HaskellQualifiedNameElement])
}
}
def findTypeParent(psiElement: PsiElement): Option[HaskellTtype] = {
def findTtype(psiElement: PsiElement): Option[HaskellTtype] = {
psiElement match {
case e: HaskellTtype => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, TtypeElementCondition)).map(_.asInstanceOf[HaskellTtype])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_TTYPE)).map(_.getPsi.asInstanceOf[HaskellTtype])
}
}
def findImportDeclarationsParent(psiElement: PsiElement): Option[HaskellImportDeclarations] = {
def findImportDeclarations(psiElement: PsiElement): Option[HaskellImportDeclarations] = {
psiElement match {
case e: HaskellImportDeclarations => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ImportDeclarationsCondition)).map(_.asInstanceOf[HaskellImportDeclarations])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_IMPORT_DECLARATIONS)).map(_.getPsi.asInstanceOf[HaskellImportDeclarations])
}
}
def findImportDeclarationParent(psiElement: PsiElement): Option[HaskellImportDeclaration] = {
def findImportDeclaration(psiElement: PsiElement): Option[HaskellImportDeclaration] = {
psiElement match {
case e: HaskellImportDeclaration => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ImportDeclarationCondition)).map(_.asInstanceOf[HaskellImportDeclaration])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_IMPORT_DECLARATION)).map(_.getPsi.asInstanceOf[HaskellImportDeclaration])
}
}
def findImportHidingDeclarationParent(psiElement: PsiElement): Option[HaskellImportHidingSpec] = {
psiElement match {
case e: HaskellImportHidingSpec => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ImportHidingSpecCondition)).map(_.asInstanceOf[HaskellImportHidingSpec])
}
}
def findHighestDeclarationElementParent(psiElement: PsiElement): Option[HaskellDeclarationElement] = {
def findHighestDeclarationElement(psiElement: PsiElement): Option[HaskellDeclarationElement] = {
psiElement match {
case e: HaskellDeclarationElement => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, HighestDeclarationElementCondition)).map(_.asInstanceOf[HaskellDeclarationElement])
}
}
def findDeclarationElementParent(psiElement: PsiElement): Option[HaskellDeclarationElement] = {
def findDeclarationElement(psiElement: PsiElement): Option[HaskellDeclarationElement] = {
psiElement match {
case e: HaskellDeclarationElement => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, DeclarationElementCondition)).map(_.asInstanceOf[HaskellDeclarationElement])
}
}
def findDataDeclarationElementParent(psiElement: PsiElement): Option[HaskellDataDeclaration] = {
psiElement match {
case e: HaskellDataDeclaration => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, DataDeclarationElementCondition)).map(_.asInstanceOf[HaskellDataDeclaration])
}
}
def findNewTypeDeclarationElementParent(psiElement: PsiElement): Option[HaskellNewtypeDeclaration] = {
psiElement match {
case e: HaskellNewtypeDeclaration => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, NewTypeDeclarationElementCondition)).map(_.asInstanceOf[HaskellNewtypeDeclaration])
}
}
def findTopLevelTypeSignatures(psiFile: PsiFile): Iterable[HaskellTypeSignature] = {
PsiTreeUtil.findChildrenOfType(psiFile, classOf[HaskellTypeSignature]).asScala.filter(_.getParent.getNode.getElementType == HS_TOP_DECLARATION)
}
def findTopLevelExpressions(psiFile: PsiFile): Iterable[HaskellExpression] = {
PsiTreeUtil.findChildrenOfType(psiFile, classOf[HaskellExpression]).asScala
}
def findModuleDeclarationParent(psiElement: PsiElement): Option[HaskellModuleDeclaration] = {
psiElement match {
case e: HaskellModuleDeclaration => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ModuleDeclarationCondition)).map(_.asInstanceOf[HaskellModuleDeclaration])
}
}
def findTypeSignatureDeclarationParent(psiElement: PsiElement): Option[HaskellTypeSignature] = {
def findTypeSignatureDeclaration(psiElement: PsiElement): Option[HaskellTypeSignature] = {
psiElement match {
case e: HaskellTypeSignature => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, TypeSignatureCondition)).map(_.asInstanceOf[HaskellTypeSignature])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_TYPE_SIGNATURE)).map(_.getPsi.asInstanceOf[HaskellTypeSignature])
}
}
def findExpressionParent(psiElement: PsiElement): Option[HaskellExpression] = {
def findExpression(psiElement: PsiElement): Option[HaskellExpression] = {
psiElement match {
case e: HaskellExpression => Some(e)
case e => Option(PsiTreeUtil.findFirstParent(e, ExpressionCondition)).map(_.asInstanceOf[HaskellExpression])
case e => Option(TreeUtil.findParent(e.getNode, HaskellTypes.HS_EXPRESSION)).map(_.getPsi.asInstanceOf[HaskellExpression])
}
}

View File

@ -275,13 +275,13 @@ object HaskellPsiImplUtil {
def getItemPresentableText(element: PsiElement, shortened: Boolean = true): String = {
HaskellPsiUtil.findNamedElement(element) match {
case Some(namedElement) =>
HaskellPsiUtil.findHighestDeclarationElementParent(element) match {
case Some(de) if de.getIdentifierElements.exists(_ == namedElement) => HaskellPsiUtil.findDeclarationElementParent(namedElement).map(de => getDeclarationInfo(de, shortened)).
orElse(HaskellPsiUtil.findExpressionParent(namedElement).map(e => StringUtil.removeCommentsAndWhiteSpaces(e.getText))).
HaskellPsiUtil.findHighestDeclarationElement(element) match {
case Some(de) if de.getIdentifierElements.exists(_ == namedElement) => HaskellPsiUtil.findDeclarationElement(namedElement).map(de => getDeclarationInfo(de, shortened)).
orElse(HaskellPsiUtil.findExpression(namedElement).map(e => StringUtil.removeCommentsAndWhiteSpaces(e.getText))).
getOrElse(s"${namedElement.getName} `in` ${getDeclarationInfo(de, shortened)}")
case Some(de) => s"${namedElement.getName} `in` ${getDeclarationInfo(de, shortened)}"
case _ if shortened && HaskellPsiUtil.findExpressionParent(namedElement).isDefined => getContainingLineText(namedElement).getOrElse(namedElement.getName).trim
case _ => HaskellPsiUtil.findExpressionParent(namedElement).map(_.getText).getOrElse(namedElement.getName)
case _ if shortened && HaskellPsiUtil.findExpression(namedElement).isDefined => getContainingLineText(namedElement).getOrElse(namedElement.getName).trim
case _ => HaskellPsiUtil.findExpression(namedElement).map(_.getText).getOrElse(namedElement.getName)
}
case _ => element.getText
}
@ -308,7 +308,7 @@ object HaskellPsiImplUtil {
val psiFile = namedElement.getContainingFile.getOriginalFile
for {
doc <- HaskellFileUtil.findDocument(psiFile)
element <- HaskellPsiUtil.findQualifiedNameParent(namedElement)
element <- HaskellPsiUtil.findQualifiedName(namedElement)
start = findNewline(element, e => e.getPrevSibling).getTextOffset
end = findNewline(element, e => e.getNextSibling).getTextOffset
} yield StringUtil.removeCommentsAndWhiteSpaces(doc.getCharsSequence.subSequence(start, end).toString.trim)
@ -332,12 +332,13 @@ object HaskellPsiImplUtil {
}
def getIdentifierElements(dataDeclaration: HaskellDataDeclaration): Seq[HaskellNamedElement] = {
val constrs = dataDeclaration.getConstrList.asScala
dataDeclaration.getSimpletype.getIdentifierElements ++
dataDeclaration.getTypeSignatureList.asScala.flatMap(_.getIdentifierElements) ++
dataDeclaration.getConstr1List.asScala.flatMap(c => Option(c.getQName).map(_.getIdentifierElement).toSeq ++
constrs.flatMap(constr => Option(constr.getConstr1)).flatMap(c => Option(c.getQName).map(_.getIdentifierElement) ++
c.getFielddeclList.asScala.flatMap(_.getQNames.getQNameList.asScala.headOption.map(_.getIdentifierElement))) ++
dataDeclaration.getConstr3List.asScala.flatMap(_.getTtypeList.asScala.headOption.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement))) ++
dataDeclaration.getConstr2List.asScala.flatMap(c => Option(c.getQName).map(_.getIdentifierElement).orElse(c.getTtypeList.asScala.headOption.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement))))
constrs.flatMap(constr => Option(constr.getConstr3)).flatMap(_.getTtypeList.asScala.headOption.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement))) ++
constrs.flatMap(constr => Option(constr.getConstr2)).flatMap(c => Option(c.getQName).map(_.getIdentifierElement).orElse(c.getTtypeList.asScala.headOption.flatMap(_.getQNameList.asScala.headOption.map(_.getIdentifierElement))))
}
def getIdentifierElements(typeDeclaration: HaskellTypeDeclaration): Seq[HaskellNamedElement] = {

View File

@ -42,7 +42,7 @@ class HaskellMoveFileHandler extends MoveFileHandler {
override def updateMovedFile(psiFile: PsiFile): Unit = {
HaskellPsiUtil.invalidateModuleName(psiFile)
HaskellComponentsManager.clearLoadedModule(psiFile)
HaskellComponentsManager.invalidateHaskellFileInfoCache(psiFile)
HaskellComponentsManager.invalidateFileInfos(psiFile)
HaskellAnnotator.restartDaemonCodeAnalyzerForFile(psiFile)
}

View File

@ -18,12 +18,16 @@ package intellij.haskell.refactor
import java.util
import com.intellij.openapi.fileEditor.FileEditorManager
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.HaskellNotificationGroup
import intellij.haskell.external.component.HaskellComponentsManager
import intellij.haskell.external.component.{HaskellComponentsManager, ProjectLibraryFileWatcher}
import intellij.haskell.external.repl.StackRepl.LibType
import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.HaskellProjectUtil
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
@ -33,11 +37,14 @@ class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
val oldName = HaskellPsiUtil.findNamedElement(targetElement).map(_.getName)
oldName match {
case Some(n) =>
val targetFile = targetElement.getContainingFile.getOriginalFile
val usageElements = HaskellComponentsManager.findReferencesInCache(targetFile).filterNot(_._1 == targetFile).filter(_._2.getIdentifierElement.getName == n).map(_._2)
if (usageElements.nonEmpty) {
HaskellComponentsManager.refreshDefinitionLocationCache(usageElements)
}
val project = targetElement.getProject
for {
f <- getCurrentFile(project)
_ = HaskellComponentsManager.invalidateOtherFilesLocations(f, n)
tf <- Option(targetElement.getContainingFile).map(_.getOriginalFile)
tInfo <- if (tf != f) HaskellComponentsManager.findStackComponentInfo(tf) else None
_ = if (tInfo.stanzaType == LibType) ProjectLibraryFileWatcher.addBuild(project, Set(tInfo)) else ()
} yield ()
case None => ()
}
}
@ -61,4 +68,21 @@ class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
}
}
}
override def getPostRenameCallback(element: PsiElement, newName: String, elementListener: RefactoringElementListener): Runnable = {
ScalaUtil.runnable {
val project = element.getProject
HaskellFileUtil.saveAllFiles(project)
getCurrentFile(project).foreach { f =>
val componentInfo = HaskellComponentsManager.findStackComponentInfo(f)
componentInfo.foreach(ci => ProjectLibraryFileWatcher.checkLibraryBuild(project, ci))
}
}
}
private def getCurrentFile(project: Project) = {
FileEditorManager.getInstance(project).getSelectedFiles.headOption.flatMap(f => HaskellFileUtil.convertToHaskellFileDispatchThread(project, f))
}
}

View File

@ -93,7 +93,7 @@ class HaskellConsoleView(val project: Project, val configuration: HaskellConsole
def executeCommand(commandText: String, addToHistory: Boolean = true): Unit = {
commandText.trim() match {
case LoadPattern(moduleName) =>
val psiFile = HaskellModuleNameIndex.findFileByModuleName(project, moduleName).toOption.flatMap(_.headOption)
val psiFile = HaskellModuleNameIndex.findFilesByModuleName(project, moduleName).toOption.flatMap(_.headOption)
psiFile.foreach(hf => HaskellConsoleViewMap.projectFileByConfigName.put(configuration.getName, hf))
case _ => ()
}

View File

@ -48,7 +48,7 @@ public class HaskellSettingsPersistentStateComponent implements PersistentStateC
}
static class HaskellSettingsState {
public Integer replTimeout = 5;
public Integer replTimeout = 30;
public String hlintOptions = "";
public Boolean reformatCodeBeforeCommit = false;
public Boolean optimizeImportsBeforeCommit = false;

View File

@ -34,7 +34,7 @@ import com.intellij.psi.impl.PsiManagerEx
import com.intellij.psi.{PsiDocumentManager, PsiFile, PsiManager}
import intellij.haskell.HaskellFileType
import intellij.haskell.action.SelectionContext
import intellij.haskell.external.component.NoInfo
import intellij.haskell.external.component.{NoInfo, NoInfoAvailable}
object HaskellFileUtil {
@ -150,15 +150,16 @@ object HaskellFileUtil {
}
}
def convertToHaskellFileInReadAction(project: Project, virtualFile: VirtualFile): Either[NoInfo, Option[PsiFile]] = {
def convertToHaskellFileInReadAction(project: Project, virtualFile: VirtualFile): Either[NoInfo, PsiFile] = {
val psiManager = PsiManager.getInstance(project)
val actionMessage = s"Converting ${virtualFile.getName} to psi file"
ApplicationUtil.runReadAction(findCachedPsiFile(psiManager, virtualFile)) match {
case pf@Some(_) => Right(pf)
case Some(pf) => Right(pf)
case None => ApplicationUtil.runInReadActionWithWriteActionPriority(project, findPsiFile(psiManager, virtualFile), readActionDescription = actionMessage) match {
case r@Right(_) => r
case l@Left(_) => l
case Right(Some(pf)) => Right(pf)
case Right(None) => Left(NoInfoAvailable(virtualFile.getName, "-"))
case Left(noInfo) => Left(noInfo)
}
}
}

View File

@ -26,7 +26,7 @@ import com.intellij.openapi.vfs.{LocalFileSystem, VirtualFile}
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.{PsiElement, PsiFile}
import intellij.haskell.HaskellNotificationGroup
import intellij.haskell.external.component.{HaskellComponentsManager, NoInfo}
import intellij.haskell.external.component.{HaskellComponentsManager, NoInfo, NoInfoAvailable}
import intellij.haskell.module.HaskellModuleType
import intellij.haskell.sdk.HaskellSdkType
@ -48,11 +48,11 @@ object HaskellProjectUtil {
findProjectHaskellModules(project).nonEmpty
}
def findFile(filePath: String, project: Project): (Option[VirtualFile], Either[NoInfo, Option[PsiFile]]) = {
def findFile(filePath: String, project: Project): (Option[VirtualFile], Either[NoInfo, PsiFile]) = {
val virtualFile = Option(LocalFileSystem.getInstance().findFileByPath(HaskellFileUtil.makeFilePathAbsolute(filePath, project)))
val psiFile = virtualFile.map(f => HaskellFileUtil.convertToHaskellFileInReadAction(project, f)) match {
case Some(r) => r
case None => Right(None)
case None => Left(NoInfoAvailable(filePath, "-"))
}
(virtualFile, psiFile)
}

View File

@ -50,21 +50,22 @@ object HaskellModuleNameIndex {
private case class Key(project: Project, moduleName: String)
type Result = Either[NoInfo, Seq[PsiFile]]
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 def find(key: Key, timeout: FiniteDuration, reschedule: Boolean): Either[NoInfo, Seq[PsiFile]] = {
findFiles(key.project, key.moduleName, timeout, reschedule) match {
case Right(files) =>
private def find(key: Key, timeout: FiniteDuration, reschedule: Boolean): Either[NoInfo, Seq[(PsiFile, Boolean)]] = {
val project = key.project
findFiles(project, key.moduleName, timeout, reschedule) match {
case Right(virtualFiles) =>
if (ApplicationManager.getApplication.isReadAccessAllowed) {
Right(files.flatMap(vf => HaskellFileUtil.convertToHaskellFileDispatchThread(key.project, vf)))
Right(virtualFiles.flatMap { case (vf, isPf) => HaskellFileUtil.convertToHaskellFileDispatchThread(project, vf).map((_, isPf)).toSeq })
} else {
val result = files.map(vf => HaskellFileUtil.convertToHaskellFileInReadAction(key.project, vf))
if (result.exists(_.isLeft)) {
Left(ReadActionTimeout("Read action timeout while converting virtual file to psi file"))
val psiFiles = virtualFiles.map { case (vf, isPf) => (HaskellFileUtil.convertToHaskellFileInReadAction(project, vf), isPf) }
if (psiFiles.exists(_._1.isLeft)) {
Left(ReadActionTimeout("Read action timeout while converting virtual files to psi files"))
} else {
Right(result.flatMap(_.toOption).flatten)
Right(psiFiles.flatMap { case (pf, isPf) => pf.toSeq.map((_, isPf)) })
}
}
case Left(noInfo) => Left(noInfo)
@ -81,33 +82,26 @@ object HaskellModuleNameIndex {
})
}
def findFilesByModuleName(project: Project, moduleName: String): Either[NoInfo, Seq[PsiFile]] = {
findFilesByModuleName2(project, moduleName).map(_.map(_._1))
}
// IntelliJ tends to send a lot of the same requests from HaskellReference to find a module name.
// This makes the UI unresponsive if the module name can not be found because user is not finished with typing the module name.
// So it seems to be no good solution to do the searching in UI thread because cache can not set before new request comes in.
// This makes the UI unresponsive if the module can not be found while user is still typing the module name.
// So it seems to be not the right solution to do the searching in UI thread because cache can not set before new request comes in.
// So using Cache is solution because Cache.get blocks next request for same key while busy.
def findFileByModuleName(project: Project, moduleName: String): Either[NoInfo, Seq[PsiFile]] = {
def findFilesByModuleName2(project: Project, moduleName: String): Either[NoInfo, Seq[(PsiFile, Boolean)]] = {
val key = Key(project, moduleName)
Cache.getIfPresent(key) match {
case Some(r@Right(_)) => r
case _ =>
// if (ApplicationManager.getApplication.isReadAccessAllowed) {
// find(key, 1.second, reschedule = false) match {
// case r@Right(_) =>
// Cache.put(key, r)
// r
// case Left(noInfo) =>
// Cache.invalidate(key)
// Left(noInfo)
// }
// } else {
Cache.get(key) match {
case r@Right(_) => r
case Left(noInfo) =>
// No invalidate to prevent UI becomes unresponsive after many calls for same module name which does not exists
// 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)
}
// }
}
}
@ -125,7 +119,7 @@ object HaskellModuleNameIndex {
Cache.invalidate(Key(project, moduleName))
}
private def findFiles(project: Project, moduleName: String, timeout: FiniteDuration, reschedule: Boolean): Either[NoInfo, Seq[VirtualFile]] = {
private def findFiles(project: Project, moduleName: String, timeout: FiniteDuration, reschedule: Boolean): Either[NoInfo, Seq[(VirtualFile, Boolean)]] = {
if (moduleName == HaskellProjectUtil.Prelude) {
Right(Seq())
} else {
@ -143,9 +137,8 @@ object HaskellModuleNameIndex {
files match {
case Left(noInfo) => Left(noInfo)
case Right(Some(f)) =>
val firstFile = f.find(f => HaskellProjectUtil.isSourceFile(project, f)).orElse(f.headOption)
Right(firstFile.toSeq ++ firstFile.map(pf => f.filterNot(_ == pf)).getOrElse(Seq()))
case Right(Some(vfs)) =>
Right(vfs.map(f => (f, HaskellProjectUtil.isSourceFile(project, f))).sortBy(!_._2))
case Right(None) => Left(IndexNotReady)
}