<commit_before>/*
 * Copyright 2015 <NAME>
 *
 * 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 com.powertuple.intellij.haskell.external

import java.util.concurrent.{Callable, Executors, TimeUnit}

import com.google.common.cache.{CacheBuilder, CacheLoader}
import com.google.common.util.concurrent.{ListenableFuture, ListenableFutureTask, UncheckedExecutionException}
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.{PsiElement, PsiFile}
import com.powertuple.intellij.haskell.HaskellNotificationGroup
import com.powertuple.intellij.haskell.psi._
import com.powertuple.intellij.haskell.util.{FileUtil, HaskellElementCondition, LineColumnPosition}

import scala.util.{Failure, Success, Try}

object GhcModiTypeInfo {
  private final val GhcModiTypeInfoPattern = """([\d]+) ([\d]+) ([\d]+) ([\d]+) "(.+)"""".r

  private final val NoInfoIndicator = "Cannot show info"

  private final val Executor = Executors.newCachedThreadPool()

  private case class ElementTypeInfo(filePath: String, lineNr: Int, columnNr: Int, project: Project)

  private final val TypeInfoCache = CacheBuilder.newBuilder()
      .refreshAfterWrite(10, TimeUnit.SECONDS)
      .build(
        new CacheLoader[ElementTypeInfo, GhcModiOutput]() {
          private def findTypeInfoFor(elementTypeInfo: ElementTypeInfo): GhcModiOutput = {
            val cmd = s"type ${elementTypeInfo.filePath} ${elementTypeInfo.lineNr} ${elementTypeInfo.columnNr}"
            GhcModiManager.getGhcModi(elementTypeInfo.project).execute(cmd)
          }

          override def load(elementTypeInfo: ElementTypeInfo): GhcModiOutput = {
            findTypeInfoFor(elementTypeInfo)
          }

          override def reload(elementTypeInfo: ElementTypeInfo, oldInfo: GhcModiOutput): ListenableFuture[GhcModiOutput] = {
            val task = ListenableFutureTask.create(new Callable[GhcModiOutput]() {
              def call() = {
                val newInfo = findTypeInfoFor(elementTypeInfo)
                if (newInfo.outputLines.isEmpty) {
                  oldInfo
                } else {
                  newInfo
                }
              }
            })
            Executor.execute(task)
            task
          }
        }
      )

  def findTypeInfoFor(psiFile: PsiFile, psiElement: PsiElement): Option[TypeInfo] = {
    if (FileUtil.isLibraryFile(psiFile)) {
      return None
    }

    val textOffset = psiElement match {
      case e: HaskellQVarConOpElement => e.getTextOffset
      case e => Option(PsiTreeUtil.findFirstParent(e, HaskellElementCondition.QVarConOpElementCondition)).map(_.getTextOffset).getOrElse(e.getTextOffset)
    }

    val startPositionExpression = LineColumnPosition.fromOffset(psiFile, textOffset) match {
      case Some(lcp) => Some(lcp)
      case None => HaskellNotificationGroup.notifyError(s"Could not find start position for ${psiElement.getText}"); None
    }

    for {
      spe <- startPositionExpression
      typeInfos <- findGhcModiTypeInfos(psiFile, spe)
      typeInfo <- typeInfos.find(ty => ty.startLine == spe.lineNr && ty.startColumn == spe.columnNr)
    } yield typeInfo
  }

  def findTypeInfoForSelection(psiFile: PsiFile, selectionModel: SelectionModel): Option[TypeInfo] = {
    val selectionStart = LineColumnPosition.fromOffset(psiFile, selectionModel.getSelectionStart) match {
      case Some(lcp) => Some(lcp)
      case None => HaskellNotificationGroup.notifyError(s"Could not find start of selection"); None
    }

    val selectionEnd = LineColumnPosition.fromOffset(psiFile, selectionModel.getSelectionEnd) match {
      case Some(lcp) => Some(lcp)
      case None => HaskellNotificationGroup.notifyError(s"Could not find end of selection"); None
    }

    for {
      ss <- selectionStart
      se <- selectionEnd
      typeInfos <- findGhcModiTypeInfos(psiFile, ss)
      typeInfo <- typeInfos.find(ty => ty.startLine == ss.lineNr && ty.startColumn == ss.columnNr && ty.endLine == se.lineNr && ty.endColumn == se.columnNr)
    } yield typeInfo
  }

  private def findGhcModiTypeInfos(psiFile: PsiFile, startPositionExpression: LineColumnPosition): Option[Iterable[TypeInfo]] = {
    val filePath = FileUtil.getFilePath(psiFile)
    val ghcModiOutput = try {
      val key = ElementTypeInfo(filePath, startPositionExpression.lineNr, startPositionExpression.columnNr, psiFile.getProject)
      val output = TypeInfoCache.get(key)
      if (output.outputLines.isEmpty) {
        TypeInfoCache.refresh(key)
        TypeInfoCache.get(key)
      } else {
        output
      }
    }
    catch {
      case _: UncheckedExecutionException => GhcModiOutput()
      case _: ProcessCanceledException => GhcModiOutput()
    }
    ghcModiOutputToTypeInfo(ghcModiOutput.outputLines) match {
      case Success(typeInfos) => Some(typeInfos)
      case Failure(error) => HaskellNotificationGroup.notifyError(s"Could not determine type. Error: $error.getMessage"); None
    }
  }

  private[external] def ghcModiOutputToTypeInfo(ghcModiOutput: Iterable[String]): Try[Iterable[TypeInfo]] = Try {
    for (outputLine <- ghcModiOutput) yield {
      outputLine match {
        case GhcModiTypeInfoPattern(startLn, startCol, endLine, endCol, typeSignature) =>
          TypeInfo(startLn.toInt, startCol.toInt, endLine.toInt, endCol.toInt, shortenTypeSignature(typeSignature))
      }
    }
  }

  private def shortenTypeSignature(typeSignature: String) = {
    if (typeSignature.length > 80) {
      typeSignature.split("->").mkString("->\n")
    } else {
      typeSignature
    }
  }
}

case class TypeInfo(startLine: Int, startColumn: Int, endLine: Int, endColumn: Int, typeSignature: String)
<commit_msg>Removed error messages.<commit_after>/*
 * Copyright 2015 <NAME>
 *
 * 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 com.powertuple.intellij.haskell.external

import java.util.concurrent.{Callable, Executors, TimeUnit}

import com.google.common.cache.{CacheBuilder, CacheLoader}
import com.google.common.util.concurrent.{ListenableFuture, ListenableFutureTask, UncheckedExecutionException}
import com.intellij.openapi.editor.SelectionModel
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.{PsiElement, PsiFile}
import com.powertuple.intellij.haskell.HaskellNotificationGroup
import com.powertuple.intellij.haskell.psi._
import com.powertuple.intellij.haskell.util.{FileUtil, HaskellElementCondition, LineColumnPosition}

import scala.util.{Failure, Success, Try}

object GhcModiTypeInfo {
  private final val GhcModiTypeInfoPattern = """([\d]+) ([\d]+) ([\d]+) ([\d]+) "(.+)"""".r

  private final val NoInfoIndicator = "Cannot show info"

  private final val Executor = Executors.newCachedThreadPool()

  private case class ElementTypeInfo(filePath: String, lineNr: Int, columnNr: Int, project: Project)

  private final val TypeInfoCache = CacheBuilder.newBuilder()
      .refreshAfterWrite(10, TimeUnit.SECONDS)
      .build(
        new CacheLoader[ElementTypeInfo, GhcModiOutput]() {
          private def findTypeInfoFor(elementTypeInfo: ElementTypeInfo): GhcModiOutput = {
            val cmd = s"type ${elementTypeInfo.filePath} ${elementTypeInfo.lineNr} ${elementTypeInfo.columnNr}"
            GhcModiManager.getGhcModi(elementTypeInfo.project).execute(cmd)
          }

          override def load(elementTypeInfo: ElementTypeInfo): GhcModiOutput = {
            findTypeInfoFor(elementTypeInfo)
          }

          override def reload(elementTypeInfo: ElementTypeInfo, oldInfo: GhcModiOutput): ListenableFuture[GhcModiOutput] = {
            val task = ListenableFutureTask.create(new Callable[GhcModiOutput]() {
              def call() = {
                val newInfo = findTypeInfoFor(elementTypeInfo)
                if (newInfo.outputLines.isEmpty) {
                  oldInfo
                } else {
                  newInfo
                }
              }
            })
            Executor.execute(task)
            task
          }
        }
      )

  def findTypeInfoFor(psiFile: PsiFile, psiElement: PsiElement): Option[TypeInfo] = {
    if (FileUtil.isLibraryFile(psiFile)) {
      return None
    }

    val textOffset = psiElement match {
      case e: HaskellQVarConOpElement => e.getTextOffset
      case e => Option(PsiTreeUtil.findFirstParent(e, HaskellElementCondition.QVarConOpElementCondition)).map(_.getTextOffset).getOrElse(e.getTextOffset)
    }

    for {
      spe <- LineColumnPosition.fromOffset(psiFile, textOffset)
      typeInfos <- findGhcModiTypeInfos(psiFile, spe)
      typeInfo <- typeInfos.find(ty => ty.startLine == spe.lineNr && ty.startColumn == spe.columnNr)
    } yield typeInfo
  }

  def findTypeInfoForSelection(psiFile: PsiFile, selectionModel: SelectionModel): Option[TypeInfo] = {
    for {
      ss <- LineColumnPosition.fromOffset(psiFile, selectionModel.getSelectionStart)
      se <- LineColumnPosition.fromOffset(psiFile, selectionModel.getSelectionEnd)
      typeInfos <- findGhcModiTypeInfos(psiFile, ss)
      typeInfo <- typeInfos.find(ty => ty.startLine == ss.lineNr && ty.startColumn == ss.columnNr && ty.endLine == se.lineNr && ty.endColumn == se.columnNr)
    } yield typeInfo
  }

  private def findGhcModiTypeInfos(psiFile: PsiFile, startPositionExpression: LineColumnPosition): Option[Iterable[TypeInfo]] = {
    val filePath = FileUtil.getFilePath(psiFile)
    val ghcModiOutput = try {
      val key = ElementTypeInfo(filePath, startPositionExpression.lineNr, startPositionExpression.columnNr, psiFile.getProject)
      val output = TypeInfoCache.get(key)
      if (output.outputLines.isEmpty) {
        TypeInfoCache.refresh(key)
        TypeInfoCache.get(key)
      } else {
        output
      }
    }
    catch {
      case _: UncheckedExecutionException => GhcModiOutput()
      case _: ProcessCanceledException => GhcModiOutput()
    }
    ghcModiOutputToTypeInfo(ghcModiOutput.outputLines) match {
      case Success(typeInfos) => Some(typeInfos)
      case Failure(error) => HaskellNotificationGroup.notifyError(s"Could not determine type. Error: $error.getMessage"); None
    }
  }

  private[external] def ghcModiOutputToTypeInfo(ghcModiOutput: Iterable[String]): Try[Iterable[TypeInfo]] = Try {
    for (outputLine <- ghcModiOutput) yield {
      outputLine match {
        case GhcModiTypeInfoPattern(startLn, startCol, endLine, endCol, typeSignature) =>
          TypeInfo(startLn.toInt, startCol.toInt, endLine.toInt, endCol.toInt, shortenTypeSignature(typeSignature))
      }
    }
  }

  private def shortenTypeSignature(typeSignature: String) = {
    if (typeSignature.length > 80) {
      typeSignature.split("->").mkString("->\n")
    } else {
      typeSignature
    }
  }
}

case class TypeInfo(startLine: Int, startColumn: Int, endLine: Int, endColumn: Int, typeSignature: String)

{'fraction_non_alphanumeric': 0.09951476272719796, 'fraction_numerical': 0.0019738465334320256, 'mean_word_length': 3.5697106350995864, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1565138', 'n_tokens_mistral': 3616, 'n_tokens_neox': 3556, 'n_words': 783}