Improve Hoogle navigation

This commit is contained in:
Rik van der Kleij 2019-10-09 21:13:04 +02:00
parent bcbfa2b869
commit eed48c55ed
11 changed files with 131 additions and 125 deletions

View File

@ -467,7 +467,7 @@ class HaskellCompletionContributor extends CompletionContributor {
private def createLookupElement(moduleIdentifier: ModuleIdentifier, addParens: Boolean = false): LookupElementBuilder = {
addWiths(LookupElementBuilder.create(
if (moduleIdentifier.isOperator && addParens)
if (moduleIdentifier.operator && addParens)
s"""(${moduleIdentifier.name})"""
else
moduleIdentifier.name

View File

@ -21,8 +21,9 @@ 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.psi.HaskellPsiUtil
import intellij.haskell.util.index.HaskellModuleNameIndex
import intellij.haskell.util.{HaskellFileUtil, StringUtil}
import intellij.haskell.util.{ApplicationUtil, HaskellFileUtil, HaskellProjectUtil, StringUtil}
import scala.concurrent.{ExecutionContext, Future, blocking}
@ -30,7 +31,6 @@ private[component] object BrowseModuleComponent {
private case class Key(project: Project, moduleName: String)
type BrowseModuleResult = Iterable[ModuleIdentifier]
private type BrowseModuleInternalResult = Either[NoInfo, Iterable[ModuleIdentifier]]
private final val Cache: AsyncLoadingCache[Key, BrowseModuleInternalResult] = Scaffeine().buildAsync((k: Key) => {
@ -41,7 +41,9 @@ private[component] object BrowseModuleComponent {
}
})
private def matchResult(key: Key, result: Future[BrowseModuleInternalResult])(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
def findModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(project, moduleName)
val result = Cache.get(key)
blocking(result.map {
case Right(ids) => Some(ids)
case Left(NoInfoAvailable(_, _)) | Left(NoMatchingExport) =>
@ -52,11 +54,6 @@ private[component] object BrowseModuleComponent {
})
}
def findModuleIdentifiers(project: Project, moduleName: String)(implicit ec: ExecutionContext): Future[Option[Iterable[ModuleIdentifier]]] = {
val key = Key(project, moduleName)
matchResult(key, Cache.get(key))
}
def findModuleIdentifiersSync(project: Project, moduleName: String): BrowseModuleInternalResult = {
val key = Key(project, moduleName)
Cache.synchronous().get(key)
@ -104,11 +101,11 @@ private[component] object BrowseModuleComponent {
findInRepl(project, projectRepl, moduleName, Some(moduleFile))
}
} else {
findLibraryModuleIdentifiers(project, moduleName)
findLibraryModuleIdentifiers(project, Some(moduleFile), moduleName)
}
case None =>
// E.g. module name is Prelude which does not refer to file
findLibraryModuleIdentifiers(project, moduleName)
findLibraryModuleIdentifiers(project, None, moduleName)
}
case Left(noInfo) => Left(noInfo)
}
@ -128,48 +125,60 @@ private[component] object BrowseModuleComponent {
private def findInRepl(project: Project, projectRepl: Option[ProjectStackRepl], moduleName: String, psiFile: Option[PsiFile]): Either[NoInfo, Seq[ModuleIdentifier]] = {
projectRepl match {
case Some(repl) =>
if (!repl.available) {
Left(ReplNotAvailable)
} else {
if (repl.available) {
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 Some(output) if output.stderrLines.isEmpty && output.stdoutLines.nonEmpty => Right(output.stdoutLines.flatMap(l => createProjectModuleIdentifier(project, l, moduleName)))
case _ => Left(ModuleNotAvailable(moduleName))
}
} else {
Left(ReplNotAvailable)
}
case None => Left(ReplNotAvailable)
}
}
private def findLibraryModuleIdentifiers(project: Project, moduleName: String): Either[NoInfo, Seq[ModuleIdentifier]] = {
private def findLibraryModuleIdentifiers(project: Project, moduleFile: Option[PsiFile], moduleName: String): Either[NoInfo, Seq[ModuleIdentifier]] = {
StackReplsManager.getGlobalRepl(project) match {
case Some(repl) =>
if (!repl.available) {
Left(ReplNotAvailable)
} else {
repl.getModuleIdentifiers(moduleName) match {
case Some(o) if o.stderrLines.isEmpty && o.stdoutLines.nonEmpty => Right(o.stdoutLines.flatMap(l => findModuleIdentifiers(project, l, moduleName).toSeq))
if (repl.available) {
(repl.getModuleIdentifiers(moduleName), moduleFile) match {
case (Some(o), _) if o.stderrLines.isEmpty && o.stdoutLines.nonEmpty => Right(o.stdoutLines.flatMap(l => createLibraryModuleIdentifier(project, l, moduleName)))
case (_, Some(f)) => Right(ApplicationUtil.runReadAction(HaskellPsiUtil.findTopLevelDeclarations(f)).map(d => ApplicationUtil.runReadAction(d.getName)).flatMap(d => createLibraryModuleIdentifier(project, d, moduleName)).toSeq)
case _ => Left(ModuleNotAvailable(moduleName))
}
} else {
Left(ReplNotAvailable)
}
case None => Left(ReplNotAvailable)
}
}
// This kind of declarations are returned in case DuplicateRecordFields are enabled
private final val Module$SelPattern =
"""([\w\.\-]+)\.\$sel:([^:]+)(?::[^:]+)?::(.*)""".r
private final val Module$SelPattern = """([\w.\-]+)\.\$sel:([^:]+)(?::[^:]+)?::(.*)""".r
private def findModuleIdentifiers(project: Project, declarationLine: String, moduleName: String): Option[ModuleIdentifier] = {
declarationLine match {
case Module$SelPattern(mn, name, fieldType) => Some(createModuleIdentifier(name, mn, s"$name :: $fieldType"))
case _ => DeclarationLineUtil.findName(declarationLine) map (nd => createModuleIdentifier(nd.name, moduleName, nd.declaration))
}
private def createLibraryModuleIdentifier(project: Project, declarationLine: String, moduleName: String): Option[ModuleIdentifier] = {
DeclarationUtil.getDeclarationInfo(declarationLine, containsQualifiedIds = true).map(declarationInfo => {
val id = declarationInfo.id
if (moduleName == HaskellProjectUtil.Prelude) {
val baseModuleName = declarationInfo.qualifiedId.map(_.split('.').init.mkString("."))
ModuleIdentifier(id, moduleName, declarationInfo.declarationLine, declarationInfo.operator, baseModuleName)
} else {
ModuleIdentifier(id, moduleName, declarationInfo.declarationLine, declarationInfo.operator)
}
})
}
private def createModuleIdentifier(name: String, moduleName: String, declaration: String) = {
ModuleIdentifier(StringUtil.removeOuterParens(name), moduleName, declaration, isOperator = StringUtil.isWithinParens(name))
private def createProjectModuleIdentifier(project: Project, declarationLine: String, moduleName: String): Option[ModuleIdentifier] = {
declarationLine match {
case Module$SelPattern(mn, id, fieldType) => Some(ModuleIdentifier(id, mn, s"$id :: $fieldType", StringUtil.isWithinParens(id)))
case _ => DeclarationUtil.getDeclarationInfo(declarationLine, containsQualifiedIds = false).
map(declarationInfo => ModuleIdentifier(declarationInfo.id, moduleName, declarationInfo.declarationLine, declarationInfo.operator))
}
}
}
// value of name is without (operator) parens
case class ModuleIdentifier(name: String, moduleName: String, declaration: String, isOperator: Boolean)
/**
* @param name is without (operator) parentheses.
* @param preludeBaseModuleName is module name of the Prelude identifier where it's defined in base package.
*/
case class ModuleIdentifier(name: String, moduleName: String, declaration: String, operator: Boolean, preludeBaseModuleName: Option[String] = None)

View File

@ -18,20 +18,23 @@ package intellij.haskell.external.component
import intellij.haskell.util.StringUtil
object DeclarationLineUtil {
object DeclarationUtil {
def findName(declarationLine: String): Option[NameAndShortDeclaration] = {
val declaration = StringUtil.shortenHaskellDeclaration(declarationLine)
def getDeclarationInfo(declarationLine: String, containsQualifiedIds: Boolean): Option[DeclarationInfo] = {
val declaration = StringUtil.removeCommentsAndWhiteSpaces(declarationLine)
val allTokens = declaration.split("""\s+""")
val name = if (allTokens.isEmpty || allTokens(0) == "--") {
(if (allTokens.isEmpty || allTokens(0) == "--") {
None
} else if (Seq("class", "instance").contains(allTokens(0))) {
declaration.split("""where|=\s""").headOption.flatMap { d =>
val tokens = d.trim.split("""=>""")
if (tokens.size == 1) {
Option(allTokens(1))
} else {
val tokens = d.trim.split("=>")
val size = tokens.size
if (size == 1) {
Option(tokens(0))
} else if (size > 1) {
Option(tokens.last.trim.split("""\s+""")(0))
} else {
None
}
}
} else if (allTokens(0) == "type" && allTokens(1) == "role") {
@ -39,17 +42,29 @@ object DeclarationLineUtil {
} else if (Seq("data", "type", "newtype").contains(allTokens(0).trim)) {
Option(allTokens(1))
} else {
val tokens = declaration.split("""::""")
val tokens = declaration.split("::")
if (tokens.size > 1) {
val name = tokens(0).trim
Option(name)
} else {
None
}
}
name.map(n => NameAndShortDeclaration(StringUtil.removeOuterParens(n), declaration))
}).map(name => {
val operator = StringUtil.isWithinParens(name)
val id = if (operator) {
StringUtil.removeOuterParens(name)
} else {
name
}
if (containsQualifiedIds) {
DeclarationInfo(StringUtil.removePackageModuleQualifier(id), Some(id), StringUtil.removePackageModuleQualifier(declaration), operator)
} else {
DeclarationInfo(id, None, declaration, operator)
}
})
}
case class DeclarationInfo(id: String, qualifiedId: Option[String], declarationLine: String, operator: Boolean)
}
case class NameAndShortDeclaration(name: String, declaration: String)

View File

@ -27,8 +27,8 @@ import intellij.haskell.util._
import intellij.haskell.util.index.HaskellModuleNameIndex._
private[component] object DefinitionLocationComponent {
private final val LocAtPattern = """(.+)\:\(([\d]+),([\d]+)\)-\(([\d]+),([\d]+)\)""".r
private final val PackageModulePattern = """([\w\-\d\.]+)(?:\-.*)?\:([\w\.\-]+)""".r
private final val LocAtPattern = """(.+):\(([\d]+),([\d]+)\)-\(([\d]+),([\d]+)\)""".r
private final val PackageModulePattern = """([\w\-\d.]+)(?:-.*)?:([\w.\-]+)""".r
// importQualifier is only set for identifiers in import declarations
private case class Key(psiFile: PsiFile, qualifiedNameElement: HaskellQualifiedNameElement, importQualifier: Option[String])
@ -76,16 +76,16 @@ private[component] object DefinitionLocationComponent {
}
def invalidate(psiFile: PsiFile): Unit = {
val keys = Cache.asMap().flatMap { case (k, v) =>
val keys = Cache.asMap().filter { case (k, v) =>
if (checkValidKey(k)) {
v.toOption match {
case Some(definitionLocation) if checkValidLocation(definitionLocation) && checkValidName(k, definitionLocation) => None
case _ => Some(k)
case Some(definitionLocation) if checkValidLocation(definitionLocation) && checkValidName(k, definitionLocation) => false
case _ => true
}
} else {
Some(k)
true
}
}
}.keys
Cache.invalidateAll(keys)
}
@ -139,7 +139,7 @@ private[component] object DefinitionLocationComponent {
ProgressManager.checkCanceled()
HaskellReference.findIdentifiersByNameInfo(info, key.qualifiedNameElement.getIdentifierElement, project) match {
case Right((mn, ne, pn)) => Right(PackageModuleLocation(findModuleName(ne), ne, name, pn))
case Right((mn, ne, pn)) => Right(PackageModuleLocation(mn.getOrElse("-"), ne, name, pn))
case Left(noInfo) =>
HaskellReference.findIdentifierInFileByName(psiFile, name, prioIdInExpression = true).
map(ne => Right(PackageModuleLocation(findModuleName(ne), ne, name, None))).getOrElse(Left(noInfo))
@ -179,17 +179,15 @@ private[component] object DefinitionLocationComponent {
case Some(q) => q + "." + qNameName
}
val moduleNames = FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).filter(_.name == qName).map(_.moduleName).toSeq
val moduleIdentifiers = FileModuleIdentifiers.findAvailableModuleIdentifiers(psiFile).filter(_.name == qName)
ProgressManager.checkCanceled()
if (moduleNames.contains(HaskellProjectUtil.Prelude)) {
Left(ModuleNotAvailable("Prelude"))
} else {
HaskellReference.findIdentifiersByModulesAndName(project, moduleNames, name) match {
case Right((mn, ne)) => Right(PackageModuleLocation(mn, ne, name, None))
case Left(noInfo) => Left(noInfo)
}
val moduleNames = moduleIdentifiers.map(mi => if (mi.moduleName == HaskellProjectUtil.Prelude) mi.preludeBaseModuleName.getOrElse(HaskellProjectUtil.Prelude) else mi.moduleName).toSeq
HaskellReference.findIdentifiersByModulesAndName(project, moduleNames.filterNot(_ == HaskellProjectUtil.Prelude), name) match {
case Right((mn, ne)) => Right(PackageModuleLocation(mn, ne, name, None))
case Left(noInfo) => Left(noInfo)
}
}

View File

@ -157,7 +157,7 @@ object FileModuleIdentifiers {
blocking {
Future.sequence(importInfos.map(importInfo => {
val allModuleIdentifiers = BrowseModuleComponent.findModuleIdentifiers(psiFile.getProject, importInfo.moduleName)
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(id => if (mi.isOperator) s"(${mi.name})" == id else id == mi.name)))))
allModuleIdentifiers.map(ids => ids.map(is => createQualifiedModuleIdentifiers(importInfo, is.filter(mi => importInfo.ids.exists(_ == mi.name)))))
}))
}
}

View File

@ -197,7 +197,7 @@ object NameInfoComponentResult {
def declaration: String
def shortenedDeclaration: String = StringUtil.shortenHaskellDeclaration(declaration)
def shortenedDeclaration: String = StringUtil.sanitizeDeclaration(declaration)
def escapedDeclaration: String = escapeString(declaration).replaceAll("""\s+""", " ")
}

View File

@ -25,7 +25,7 @@ import com.intellij.psi.stubs.StubIndex
import com.intellij.util.{ArrayUtil, Processor}
import intellij.haskell.HaskellLanguage
import intellij.haskell.psi.stubs.index.HaskellAllNameIndex
import intellij.haskell.psi.{HaskellClassDeclaration, HaskellDeclarationElement, HaskellNamedElement, HaskellPsiUtil}
import intellij.haskell.psi.{HaskellNamedElement, HaskellPsiUtil}
import intellij.haskell.util.HaskellProjectUtil
import scala.collection.mutable.ListBuffer
@ -39,15 +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.findHighestDeclarationElement(ne)))
declarationElements.sortWith(sortByClassDeclarationFirst).flatMap(_._2).toArray
}
private def sortByClassDeclarationFirst(namedAndDeclarationElement1: (HaskellNamedElement, Option[HaskellDeclarationElement]), namedAndDeclarationElement2: (HaskellNamedElement, Option[HaskellDeclarationElement])): Boolean = {
(namedAndDeclarationElement1._2, namedAndDeclarationElement2._2) match {
case (Some(_: HaskellClassDeclaration), _) => true
case (_, _) => false
}
namedElements.flatMap(ne => HaskellPsiUtil.findHighestDeclarationElement(ne)).toArray
}
override def getQualifiedName(item: NavigationItem): String = {

View File

@ -226,7 +226,7 @@ object HaskellReference {
val identifiers = files.flatMap(f => findIdentifierInFileByName(f, name, prioIdInExpression))
if (identifiers.isEmpty) {
val importedModuleNames = files.flatMap(f => FileModuleIdentifiers.findAvailableModuleIdentifiers(f).filter(mid => mid.name == name || mid.name == "_" + name).map(_.moduleName))
val importedModuleNames = files.flatMap(f => FileModuleIdentifiers.findAvailableModuleIdentifiers(f).filter(mid => mid.name == name || mid.name == "_" + name || mid.name == mid.moduleName + "." + name).map(_.moduleName))
if (importedModuleNames.isEmpty) {
Seq()
} else {

View File

@ -26,8 +26,8 @@ import javax.swing.Icon
class HoogleByNameContributor extends ChooseByNameContributor {
private final val DeclarationPattern = """([\w\.\-]+) (.*)""".r
private final val ModulePattern = """module ([\w\.\-]+)""".r
private final val DeclarationPattern = """([\w.\-]+) (.*)""".r
private final val ModulePattern = """module ([\w.\-]+)""".r
private final val PackagePattern = """package (.*)""".r
override def getNames(project: Project, includeNonProjectItems: Boolean): Array[String] = {
@ -51,22 +51,22 @@ class HoogleByNameContributor extends ChooseByNameContributor {
case Right(files) => files
case _ => Seq()
}
case PackagePattern(packageName) =>
case PackagePattern(_) =>
ProgressManager.checkCanceled()
Seq()
case DeclarationPattern(moduleName, declaration) =>
ProgressManager.checkCanceled()
DeclarationLineUtil.findName(declaration) match {
case Some(nameAndShortDeclaration) =>
val identifiers = HaskellReference.findIdentifiersByModulesAndName(project, Seq(moduleName), nameAndShortDeclaration.name, prioIdInExpression = false).toOption.map(_._2).toSeq
DeclarationUtil.getDeclarationInfo(declaration, containsQualifiedIds = false) match {
case Some(declarationInfo) =>
val identifiers = HaskellReference.findIdentifiersByModulesAndName(project, Seq(moduleName), declarationInfo.id, prioIdInExpression = false).toOption.map(_._2).toSeq
if (identifiers.isEmpty) {
Seq()
Seq(NotFoundNavigationItem(declaration, moduleName))
} else {
identifiers.map(e => HaskellPsiUtil.findDeclarationElement(e).getOrElse(e)).map(d => createLibraryNavigationItem(d, moduleName))
}
case None => Seq()
}
case d =>
case _ =>
ProgressManager.checkCanceled()
Seq()
}
@ -109,4 +109,23 @@ class HoogleByNameContributor extends ChooseByNameContributor {
override def canNavigateToSource: Boolean = namedElement.canNavigateToSource
}
}
case class NotFoundNavigationItem(declaration: String, moduleName: String) extends NavigationItem {
override def getName: String = declaration
override def getPresentation: ItemPresentation = new ItemPresentation {
override def getIcon(unused: Boolean): Icon = null
override def getLocationString: String = moduleName
override def getPresentableText: String = getName
}
override def canNavigateToSource: Boolean = false
override def canNavigate: Boolean = false
override def navigate(requestFocus: Boolean): Unit = ()
}
}

View File

@ -22,13 +22,11 @@ import com.intellij.psi.{PsiElement, PsiReference}
import com.intellij.util.ArrayUtil
import icons.HaskellIcons
import intellij.haskell.HaskellFileType
import intellij.haskell.psi.HaskellTypes._
import intellij.haskell.psi._
import intellij.haskell.refactor.HaskellRenameFileProcessor
import intellij.haskell.util.{HaskellFileUtil, StringUtil}
import intellij.haskell.util.StringUtil
import javax.swing._
import scala.annotation.tailrec
import scala.jdk.CollectionConverters._
object HaskellPsiImplUtil {
@ -224,12 +222,12 @@ object HaskellPsiImplUtil {
new HaskellItemPresentation(declarationElement) {
def getPresentableText: String = {
getDeclarationInfo(declarationElement, shortened = true)
getDeclarationText(declarationElement)
}
}
}
def getItemPresentableText(element: PsiElement, shortened: Boolean = true): String = {
def getItemPresentableText(element: PsiElement): String = {
HaskellPsiUtil.findNamedElement(element) match {
case Some(namedElement) =>
HaskellPsiUtil.findHighestDeclarationElement(element) match {
@ -244,39 +242,10 @@ object HaskellPsiImplUtil {
}
}
private def getDeclarationInfo(declarationElement: HaskellDeclarationElement, shortened: Boolean): String = {
val info = declarationElement match {
private def getDeclarationText(declarationElement: HaskellDeclarationElement): String = {
declarationElement match {
case md: HaskellModuleDeclaration => s"module ${md.getModid.getName}"
case de if shortened => StringUtil.shortenHaskellDeclaration(de.getText)
case de => de.getText
}
if (shortened && info.length > 100) {
getFirstLineDeclarationText(declarationElement) + "..."
} else {
info
}
}
private def getFirstLineDeclarationText(declarationElement: HaskellDeclarationElement) = {
StringUtil.removeCommentsAndWhiteSpaces(declarationElement.getNode.getChildren(null).takeWhile(n => n.getElementType != HS_WHERE && n.getElementType != HS_EQUAL).map(_.getText).mkString(" "))
}
private def getContainingLineText(namedElement: PsiElement) = {
val psiFile = namedElement.getContainingFile.getOriginalFile
for {
doc <- HaskellFileUtil.findDocument(psiFile)
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)
}
@tailrec
def findNewline(psiElement: PsiElement, getSibling: PsiElement => PsiElement): PsiElement = {
Option(getSibling(psiElement)) match {
case None => psiElement
case Some(e) if e.getNode.getElementType == HS_NEWLINE => e
case Some(e) => findNewline(e, getSibling)
case de => StringUtil.sanitizeDeclaration(de.getText)
}
}

View File

@ -24,23 +24,27 @@ import scala.collection.mutable.ListBuffer
object StringUtil {
private final val PackageQualifierPattern = """([a-zA-Z\-]+\-[\.0-9]+\:)?([A-Z][\w\\\-]*\.)+"""
private final val PackageQualifierPattern2 = """^([a-zA-Z\-]+\-[\.0-9]+\:)?"""
private final val PackageModuleQualifierPattern = """([a-zA-Z\-]+\-[\.0-9]+\:)?([A-Z][\w\\\-]*\.)+"""
private final val PackageQualifierPattern = """^([a-zA-Z\-]+\-[\.0-9]+\:)?"""
def escapeString(s: String): String = {
XmlStringUtil.escapeString(s, false, false)
}
def removePackageQualifier(s: String): String = {
s.replaceAll(PackageQualifierPattern2, "")
def removePackageModuleQualifier(s: String): String = {
s.replaceAll(PackageModuleQualifierPattern, "")
}
def shortenHaskellDeclaration(declaration: String): String = {
removeCommentsAndWhiteSpaces(declaration.replaceAll(PackageQualifierPattern, ""))
def removePackageQualifier(s: String): String = {
s.replaceAll(PackageQualifierPattern, "")
}
def sanitizeDeclaration(declaration: String): String = {
removeCommentsAndWhiteSpaces(declaration.replaceAll(PackageModuleQualifierPattern, ""))
}
def removeCommentsAndWhiteSpaces(code: String): String = {
code.replaceAll("""\{\-[^\}]+\-\}""", " ").replaceAll("""\-\-.*""", " ").replaceAll("""\s+""", " ")
code.replaceAll("""\{-[^\}]+-\}""", " ").replaceAll("""--.*""", " ").replaceAll("""\s+""", " ")
}
def removeOuterParens(name: String): String = {