Improved renaming.

This commit is contained in:
Rik van der Kleij 2019-06-02 21:22:32 +02:00
parent 44809ba061
commit f787d59f8f
9 changed files with 84 additions and 123 deletions

View File

@ -66,22 +66,12 @@ class HaskellAnnotator extends ExternalAnnotator[PsiFile, CompilationResult] {
}
}
// Workaround in case annotator is triggered but file is not changed.
// In this case the result of REPL load is always empty.
private var prevResult: Option[(PsiFile, CompilationResult)] = None
override def doAnnotate(psiFile: PsiFile): CompilationResult = {
HaskellFileUtil.findVirtualFile(psiFile) match {
case Some(virtualFile) =>
val fileChanged = FileDocumentManager.getInstance().isFileModified(virtualFile)
prevResult match {
case Some((pf, r)) if !fileChanged && psiFile == pf => r
case _ =>
HaskellFileUtil.saveFileInDispatchThread(psiFile.getProject, virtualFile)
val result = HaskellComponentsManager.loadHaskellFile(psiFile).orNull
prevResult = Some((psiFile, result))
result
}
HaskellFileUtil.saveFileInDispatchThread(psiFile.getProject, virtualFile)
HaskellComponentsManager.loadHaskellFile(psiFile, fileChanged).orNull
case None => CompilationResult(Iterable(), Iterable(), failed = false)
}
}
@ -345,18 +335,21 @@ class LanguageExtensionIntentionAction(languageExtension: String) extends Haskel
override def getFamilyName: String = "Add language extension"
override def invoke(project: Project, editor: Editor, file: PsiFile): Unit = {
val languagePragmaElement = HaskellElementFactory.createLanguagePragma(project, s"{-# LANGUAGE $languageExtension #-}\n")
Option(PsiTreeUtil.findChildOfType(file, classOf[HaskellFileHeader])) match {
case Some(fh) =>
val lastPragmaElement = PsiTreeUtil.findChildrenOfType(fh, classOf[HaskellPragma]).asScala.lastOption.orNull
if (lastPragmaElement == null) {
val p = fh.add(languagePragmaElement)
fh.addAfter(HaskellElementFactory.createNewLine(project), p)
} else {
val nl = fh.addAfter(HaskellElementFactory.createNewLine(project), lastPragmaElement)
fh.addAfter(languagePragmaElement, nl)
HaskellElementFactory.createLanguagePragma(project, s"{-# LANGUAGE $languageExtension #-}\n") match {
case Some(languagePragmaElement) =>
Option(PsiTreeUtil.findChildOfType(file, classOf[HaskellFileHeader])) match {
case Some(fh) =>
PsiTreeUtil.findChildrenOfType(fh, classOf[HaskellPragma]).asScala.lastOption match {
case Some(lastPragmaElement) =>
val nl = fh.addAfter(HaskellElementFactory.createNewLine(project), lastPragmaElement)
fh.addAfter(languagePragmaElement, nl)
case None =>
val p = fh.add(languagePragmaElement)
fh.addAfter(HaskellElementFactory.createNewLine(project), p)
}
case None => () // File header should always be there
}
case None => () // File header should always be there
case None => ()
}
}
}
@ -387,11 +380,11 @@ private object IntentionHelper {
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`"))
HaskellElementFactory.createQualifiedNameElement(project, s"`$newName`").foreach(e.replace)
} else if (StringUtil.isWithinParens(e.getText)) {
e.replace(HaskellElementFactory.createQualifiedNameElement(project, s"($newName)"))
HaskellElementFactory.createQualifiedNameElement(project, s"($newName)").foreach(e.replace)
} else {
e.replace(HaskellElementFactory.createQualifiedNameElement(project, newName))
HaskellElementFactory.createQualifiedNameElement(project, newName).foreach(e.replace)
}
case None => ()
}
@ -441,7 +434,7 @@ class NotInScopeIntentionAction(identifier: String, moduleName: String, psiFile:
val commaElement = importIdsSpec.addAfter(HaskellElementFactory.createComma(project), importId)
HaskellElementFactory.createImportId(project, identifier).foreach { ii =>
val iiElement = importIdsSpec.addAfter(ii, commaElement)
importIdsSpec.addBefore(HaskellElementFactory.createWhiteSpace(project), iiElement)
HaskellElementFactory.createWhiteSpace(project).foreach(importIdsSpec.addBefore(_, iiElement))
}
})
case None => createImportDeclaration(importDeclarationElement, ids, project)

View File

@ -70,12 +70,6 @@ private[component] object DefinitionLocationComponent {
Cache.asMap().find { case (k, _) => k.psiFile == psiFile && k.qualifiedNameElement == qualifiedNameElement }.flatMap(_._2.toOption)
}
def invalidateOtherFiles(currentFile: PsiFile, name: String): Unit = {
val synchronousCache = Cache
val keys = synchronousCache.asMap().filter { case (k, v) => k.qualifiedNameElement.getIdentifierElement.getName == name && k.psiFile != currentFile }.keys
keys.foreach(synchronousCache.invalidate)
}
def invalidateAll(project: Project): Unit = {
val synchronousCache = Cache
synchronousCache.asMap().filter(_._1.psiFile.getProject == project).keys.foreach(synchronousCache.invalidate)
@ -109,7 +103,9 @@ private[component] object DefinitionLocationComponent {
}
private def checkValidName(key: Key, definitionLocation: DefinitionLocation): Boolean = {
ApplicationUtil.runReadAction(Option(key.qualifiedNameElement.getIdentifierElement.getName)).contains(ApplicationUtil.runReadAction(definitionLocation.namedElement.getName))
val keyName = ApplicationUtil.runReadAction(Option(key.qualifiedNameElement.getIdentifierElement.getName))
keyName == ApplicationUtil.runReadAction(Option(definitionLocation.namedElement.getName)) &&
keyName.contains(definitionLocation.originalName)
}
private def findDefinitionLocationResult(key: Key): DefinitionLocationResult = {

View File

@ -131,8 +131,8 @@ object HaskellComponentsManager {
StackReplsManager.getReplsManager(project).map(_.moduleCabalInfos.map { case (_, ci) => ci }).getOrElse(Iterable())
}
def loadHaskellFile(psiFile: PsiFile): Option[CompilationResult] = {
LoadComponent.load(psiFile)
def loadHaskellFile(psiFile: PsiFile, fileChanged: Boolean): Option[CompilationResult] = {
LoadComponent.load(psiFile, fileChanged)
}
def invalidateFileInfos(psiFile: PsiFile): Unit = {
@ -147,8 +147,8 @@ object HaskellComponentsManager {
findStackComponentInfos(project).map(info => (info.module, info.packageName)).distinct
}
def invalidateOtherFilesLocations(currentFile: PsiFile, name: String): Unit = {
DefinitionLocationComponent.invalidateOtherFiles(currentFile, name)
def invalidateDefinitionLocations(psiFile: PsiFile): Unit = {
DefinitionLocationComponent.invalidate(psiFile)
}
def findLibraryPackageInfos(project: Project): Seq[PackageInfo] = {

View File

@ -37,7 +37,7 @@ private[component] object LoadComponent {
}.contains(true)
}
def load(psiFile: PsiFile): Option[CompilationResult] = {
def load(psiFile: PsiFile, fileChanged: Boolean): Option[CompilationResult] = {
val project = psiFile.getProject
StackReplsManager.getProjectRepl(psiFile).flatMap(projectRepl => {
@ -51,7 +51,7 @@ private[component] object LoadComponent {
ProjectLibraryFileWatcher.checkLibraryBuild(project, projectRepl.stackComponentInfo)
projectRepl.load(psiFile) match {
projectRepl.load(psiFile, fileChanged) match {
case Some((loadOutput, loadFailed)) =>
ApplicationManager.getApplication.executeOnPooledThread(ScalaUtil.runnable {

View File

@ -34,6 +34,7 @@ import intellij.haskell.external.repl.StackRepl.LibType
import intellij.haskell.external.repl.StackReplsManager
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, HaskellProjectUtil}
import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.collection.concurrent
@ -77,6 +78,7 @@ object ProjectLibraryFileWatcher {
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Building project", false, PerformInBackgroundOption.ALWAYS_BACKGROUND) {
@tailrec
def run(progressIndicator: ProgressIndicator) {
val buildMessage = s"Building project"
HaskellNotificationGroup.logInfoEvent(project, buildMessage)

View File

@ -136,13 +136,13 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
loadedModuleNames.foreach(mn => everLoadedDependentModules.put(mn, DependentModuleInfo()))
}
def load(psiFile: PsiFile, mustBeByteCode: Boolean = false): Option[(StackReplOutput, Boolean)] = synchronized {
def load(psiFile: PsiFile, fileChanged: Boolean, mustBeByteCode: Boolean = false): Option[(StackReplOutput, Boolean)] = synchronized {
val forceBytecodeLoad = if (mustBeByteCode) objectCodeEnabled else false
val reload = if (!forceBytecodeLoad) {
val reload = if (forceBytecodeLoad || !fileChanged) {
false
} else {
val loaded = isFileLoaded(psiFile)
loaded == Loaded || loaded == Failed
} else {
false
}
val output = if (reload) {
@ -186,7 +186,7 @@ case class ProjectStackRepl(project: Project, stackComponentInfo: StackComponent
}
def getModuleIdentifiers(moduleName: String, psiFile: Option[PsiFile]): Option[StackReplOutput] = synchronized {
if (psiFile.isEmpty || isBrowseModuleLoaded(moduleName) || psiFile.exists(pf => load(pf).exists(_._2 == false))) {
if (psiFile.isEmpty || isBrowseModuleLoaded(moduleName) || psiFile.exists(pf => load(pf, fileChanged = false).exists(_._2 == false))) {
execute(s":browse! $moduleName")
} else {
HaskellNotificationGroup.logInfoEvent(project, s"Could not get module identifiers for module $moduleName because file ${psiFile.map(_.getName).getOrElse("-")} is not loaded")

View File

@ -18,60 +18,55 @@ package intellij.haskell.psi
import java.util
import com.intellij.application.options.CodeStyle
import com.intellij.openapi.project.Project
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.impl.source.tree.LeafPsiElement
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.{PsiElement, PsiFileFactory, PsiManager, PsiWhiteSpace}
import com.intellij.psi.{PsiElement, PsiFileFactory, PsiWhiteSpace}
import intellij.haskell.psi.HaskellTypes._
import intellij.haskell.{HaskellFile, HaskellFileType, HaskellLanguage}
import intellij.haskell.{HaskellFile, HaskellLanguage}
import scala.collection.JavaConverters._
object HaskellElementFactory {
def createUnderscore(project: Project): Option[PsiElement] = {
createElementFromText(project, "_", HS_RESERVED_ID)
createElement(project, "_", classOf[HaskellReservedId])
}
def createVarid(project: Project, name: String): Option[HaskellVarid] = {
createElementFromText(project, name, HS_VARID).map(_.asInstanceOf[HaskellVarid]).filter(_.getChildren.length == 0)
createElement(project, name, classOf[HaskellVarid])
}
def createConid(project: Project, name: String): Option[HaskellConid] = {
createElementFromText(project, name, HS_CONID).map(_.asInstanceOf[HaskellConid]).filter(_.getChildren.length == 0)
createElement(project, name, classOf[HaskellConid])
}
def createVarsym(project: Project, name: String): Option[HaskellVarsym] = {
createElementFromText(project, name, HS_VARSYM).map(_.asInstanceOf[HaskellVarsym])
createElement(project, name, classOf[HaskellVarsym])
}
def createConsym(project: Project, name: String): Option[HaskellConsym] = {
createElementFromText(project, name, HS_CONSYM).map(_.asInstanceOf[HaskellConsym]).filter(_.getChildren.length == 0)
createElement(project, name, classOf[HaskellConsym])
}
def createImportId(project: Project, identifier: String): Option[HaskellImportId] = {
createElementFromText(project, surroundWithParensIfSymbol(project, identifier), HS_IMPORT_ID).map(_.asInstanceOf[HaskellImportId])
createElement(project, surroundWithParensIfSymbol(project, identifier), classOf[HaskellImportId])
}
def createQualifiedNameElement(project: Project, name: String): HaskellQualifiedNameElement = {
val haskellFile = createFileFromText(project, name)
PsiTreeUtil.findChildOfType(haskellFile, classOf[HaskellQualifiedNameElement])
def createQualifiedNameElement(project: Project, name: String): Option[HaskellQualifiedNameElement] = {
createElement(project, name, classOf[HaskellQualifiedNameElement])
}
def createBody(project: Project, body: String): Option[HaskellModuleBody] = {
createElementFromText(project, body, HS_MODULE_BODY).map(_.asInstanceOf[HaskellModuleBody])
createElement(project, body, classOf[HaskellModuleBody])
}
def createTopDeclarationLine(project: Project, declaration: String): Option[HaskellTopDeclarationLine] = {
createElementFromText(project, declaration, HS_TOP_DECLARATION_LINE).map(_.asInstanceOf[HaskellTopDeclarationLine])
createElement(project, declaration, classOf[HaskellTopDeclarationLine])
}
def createLanguagePragma(project: Project, languagePragma: String): HaskellPragma = {
val haskellFile = createFileFromText(project, languagePragma)
PsiTreeUtil.findChildOfType(haskellFile, classOf[HaskellPragma])
def createLanguagePragma(project: Project, languagePragma: String): Option[HaskellPragma] = {
createElement(project, languagePragma, classOf[HaskellPragma])
}
def createLeafPsiElements(project: Project, code: String): util.Collection[LeafPsiElement] = {
@ -87,9 +82,8 @@ object HaskellElementFactory {
createLeafPsiElements(project, "add = (1 + 2)").asScala.find(_.getNode.getElementType == HS_RIGHT_PAREN)
}
def createWhiteSpace(project: Project, space: String = " "): PsiWhiteSpace = {
val haskellFile = createFileFromText(project, space)
PsiTreeUtil.findChildOfType(haskellFile, classOf[PsiWhiteSpace])
def createWhiteSpace(project: Project, space: String = " "): Option[PsiWhiteSpace] = {
createElement(project, space, classOf[PsiWhiteSpace])
}
def createComma(project: Project): PsiElement = {
@ -97,34 +91,28 @@ object HaskellElementFactory {
haskellFile.getLastChild
}
def createTab(project: Project): PsiWhiteSpace = {
val tabSize = CodeStyle.getSettings(project).getTabSize(HaskellFileType.Instance)
createWhiteSpace(project, " " * tabSize)
}
def createNewLine(project: Project): PsiElement = {
createFileFromText(project, "\n").getFirstChild
}
def createQualifier(project: Project, qualifier: String): Option[HaskellQualifier] = {
createElementFromText(project, qualifier, HS_QUALIFIER).map(_.asInstanceOf[HaskellQualifier])
createElement(project, "test = " + qualifier + ".bla", classOf[HaskellQualifier])
}
def createQConQualifier(project: Project, qConQualifier: String): Option[HaskellQConQualifier] = {
createElementFromText(project, qConQualifier, HS_Q_CON_QUALIFIER).map(_.asInstanceOf[HaskellQConQualifier])
createElement(project, qConQualifier, classOf[HaskellQConQualifier])
}
def createModid(project: Project, moduleName: String): Option[HaskellModid] = {
val haskellModuleDeclaration = createElementFromText(project, s"module $moduleName where", HS_MODULE_DECLARATION)
haskellModuleDeclaration.map(_.asInstanceOf[HaskellModuleDeclaration]).map(_.getModid)
val haskellModuleDeclaration = createElement(project, s"module $moduleName where", classOf[HaskellModuleDeclaration])
haskellModuleDeclaration.map(_.getModid)
}
def createImportDeclaration(project: Project, moduleName: String, identifier: String): Option[HaskellImportDeclaration] = {
val haskellImportDeclaration = createElementFromText(project, s"import $moduleName (${surroundWithParensIfSymbol(project, identifier)})", HS_IMPORT_DECLARATION)
haskellImportDeclaration.map(_.asInstanceOf[HaskellImportDeclaration])
createElement(project, s"import $moduleName (${surroundWithParensIfSymbol(project, identifier)})", classOf[HaskellImportDeclaration])
}
private def surroundWithParensIfSymbol(project: Project, identifier: String) = {
private def surroundWithParensIfSymbol(project: Project, identifier: String): String = {
if (createVarid(project, identifier).isDefined || createConid(project, identifier).isDefined) {
identifier
} else {
@ -133,15 +121,15 @@ object HaskellElementFactory {
}
def createImportDeclaration(project: Project, importDecl: String): Option[HaskellImportDeclaration] = {
val haskellImportDeclaration = createElementFromText(project, s"$importDecl \n", HS_IMPORT_DECLARATION)
haskellImportDeclaration.map(_.asInstanceOf[HaskellImportDeclaration])
createElement(project, s"$importDecl \n", classOf[HaskellImportDeclaration])
}
private def createElement[C <: PsiElement](project: Project, newName: String, namedElementClass: Class[C]): Option[C] = {
val file = createFileFromText(project, newName)
Option(PsiTreeUtil.findChildOfType(file, namedElementClass))
}
private def createFileFromText(project: Project, text: String): HaskellFile = {
PsiFileFactory.getInstance(project).createFileFromText("a.hs", HaskellLanguage.Instance, text).asInstanceOf[HaskellFile]
}
def createElementFromText(project: Project, text: String, elementType: IElementType): Option[PsiElement] = {
Option(new PsiFileFactoryImpl(PsiManager.getInstance(project)).createElementFromText(text, HaskellLanguage.Instance, elementType, null)).filter(_.isValid)
}
}

View File

@ -101,7 +101,7 @@ object HaskellPsiImplUtil {
def setName(modid: HaskellModid, newName: String): PsiElement = {
if (newName.endsWith("." + HaskellFileType.Instance.getDefaultExtension)) {
val newModid = HaskellElementFactory.createModid(modid.getProject, HaskellRenameFileProcessor.createNewModuleName(modid.getName, newName))
newModid.foreach(mi => modid.getNode.getTreeParent.replaceChild(modid.getNode, mi.getNode))
newModid.foreach(modid.replace)
modid
} else {
modid
@ -118,7 +118,7 @@ object HaskellPsiImplUtil {
def setName(varid: HaskellVarid, newName: String): PsiElement = {
val newVarid = HaskellElementFactory.createVarid(varid.getProject, newName)
newVarid.foreach(vid => varid.getNode.getTreeParent.replaceChild(varid.getNode, vid.getNode))
newVarid.foreach(varid.replace)
varid
}
@ -131,16 +131,9 @@ object HaskellPsiImplUtil {
}
def setName(varsym: HaskellVarsym, newName: String): PsiElement = {
if (varsym.getNode.getChildren(null).length == 1) {
val newVarsym = HaskellElementFactory.createVarsym(varsym.getProject, newName)
newVarsym.foreach(vs => varsym.getNode.getTreeParent.replaceChild(varsym.getNode, vs.getNode))
varsym
} else {
HaskellElementFactory.createVarsym(varsym.getProject, newName).foreach { newVarsym =>
varsym.getNode.replaceAllChildrenToChildrenOf(newVarsym.getNode)
}
varsym
}
val newVarsym = HaskellElementFactory.createVarsym(varsym.getProject, newName)
newVarsym.foreach(varsym.replace)
varsym
}
def getName(conid: HaskellConid): String = {
@ -153,7 +146,7 @@ object HaskellPsiImplUtil {
def setName(conid: HaskellConid, newName: String): PsiElement = {
val newConid = HaskellElementFactory.createConid(conid.getProject, newName)
newConid.foreach(ci => conid.getNode.getTreeParent.replaceChild(conid.getNode, ci.getNode))
newConid.foreach(conid.replace)
conid
}
@ -167,7 +160,7 @@ object HaskellPsiImplUtil {
def setName(consym: HaskellConsym, newName: String): PsiElement = {
val newConsym = HaskellElementFactory.createConsym(consym.getProject, newName)
newConsym.foreach(cs => consym.getNode.getTreeParent.replaceChild(consym.getNode, cs.getNode))
newConsym.foreach(consym.replace)
consym
}
@ -181,7 +174,7 @@ object HaskellPsiImplUtil {
def setName(qualifier: HaskellQualifier, newName: String): PsiElement = {
val newQualifier = HaskellElementFactory.createQualifier(qualifier.getProject, removeFileExtension(newName))
newQualifier.foreach(q => qualifier.getNode.getTreeParent.replaceChild(qualifier.getNode, q.getNode))
newQualifier.foreach(qualifier.replace)
qualifier
}
@ -195,7 +188,7 @@ object HaskellPsiImplUtil {
def setName(qualifier: HaskellQualifierElement, newName: String): PsiElement = {
val newQualifier = HaskellElementFactory.createQConQualifier(qualifier.getProject, newName)
newQualifier.foreach(q => qualifier.getNode.getTreeParent.replaceChild(qualifier.getNode, q.getNode))
newQualifier.foreach(qualifier.replace)
qualifier
}

View File

@ -25,7 +25,6 @@ import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.rename.RenamePsiElementProcessor
import intellij.haskell.external.component.{HaskellComponentsManager, ProjectLibraryFileWatcher}
import intellij.haskell.external.repl.StackRepl.LibType
import intellij.haskell.psi.HaskellPsiUtil
import intellij.haskell.util.{HaskellFileUtil, HaskellProjectUtil, ScalaUtil}
class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
@ -33,19 +32,15 @@ class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
// Target element is element of the definition
// Invalidate cache is necessary because during (inline) renaming the id of psi element is changed
override def prepareRenaming(targetElement: PsiElement, newName: String, allRenames: util.Map[PsiElement, String]): Unit = {
val oldName = HaskellPsiUtil.findNamedElement(targetElement).map(_.getName)
oldName match {
case Some(n) =>
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 => ()
}
val project = targetElement.getProject
for {
cf <- getCurrentFile(project)
() = HaskellComponentsManager.invalidateDefinitionLocations(cf)
tf <- Option(targetElement.getContainingFile).map(_.getOriginalFile)
targetInfo <- HaskellComponentsManager.findStackComponentInfo(tf)
currentInfo <- HaskellComponentsManager.findStackComponentInfo(cf)
} yield if (targetInfo != currentInfo && targetInfo.stanzaType == LibType)
ProjectLibraryFileWatcher.addBuild(project, Set(targetInfo)) else ()
}
override def canProcessElement(psiElement: PsiElement): Boolean = {
@ -63,16 +58,10 @@ class HaskellRenameVariableProcessor extends RenamePsiElementProcessor {
}
}
override def getPostRenameCallback(element: PsiElement, newName: String, elementListener: RefactoringElementListener): Runnable = {
override def getPostRenameCallback(targetElement: PsiElement, newName: String, elementListener: RefactoringElementListener): Runnable = {
ScalaUtil.runnable {
val project = element.getProject
val project = targetElement.getProject
HaskellFileUtil.saveAllFiles(project)
getCurrentFile(project).foreach { f =>
val componentInfo = HaskellComponentsManager.findStackComponentInfo(f)
componentInfo.foreach(ci => ProjectLibraryFileWatcher.checkLibraryBuild(project, ci))
}
}
}