override def getFamilyName: String = ScalaBundle.message()

in scala/scala-impl/src/org/jetbrains/plugins/scala/codeInsight/intention/expression/ConvertUnderscoreToParameterIntention.scala [35:250]


  override def getFamilyName: String = ScalaBundle.message("family.name.convert.underscore.section.to.parameter")

  override def getText: String = getFamilyName

  override def isAvailable(project: Project, editor: Editor, element: PsiElement): Boolean =
    findExpression(element, editor).isDefined

  override def invoke(project: Project, editor: Editor, element: PsiElement): Unit = {
    implicit val ctx: ProjectContext = project

    val expr = findExpression(element, editor).get
    if (expr == null || !expr.isValid) return

    val buf = new mutable.StringBuilder
    val underscores = ScUnderScoreSectionUtil.underscores(expr)
    val parentStartOffset =
      if (ApplicationManager.getApplication.isUnitTestMode) underscores.head.getTextRange.getStartOffset
      else expr.getTextRange.getStartOffset
    val parentEndOffset =
      if (ApplicationManager.getApplication.isUnitTestMode) {
        if (underscores.nonEmpty) underscores.last.getTextRange.getEndOffset
        else underscores.head.getTextRange.getEndOffset
      } else expr.getTextRange.getEndOffset

    val underscoreToParam: mutable.HashMap[ScUnderscoreSection, ScReferenceExpression] =
      new mutable.HashMap[ScUnderscoreSection, ScReferenceExpression]
    val offsets: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int]
    val usedNames: mutable.HashSet[String] = new mutable.HashSet[String]
    val macros: mutable.HashSet[String] = new mutable.HashSet[String]
    var needComma = false
    var needBraces = false

    for (m <- Macro.EP_NAME.getExtensionList.asScala) {
      macros.add(m.getName)
    }

    for (u <- underscores) {
      if (needComma) buf.append(",")
      if (underscores.size > 1) needComma = true

      val names = NameSuggester.suggestNames(u,
        new ScalaVariableValidator(u, false, expr.getContext, expr.getContext) {
          override def validateName(name: String): String = {
            var res = super.validateName(name)
            var index = 1

            if (usedNames.contains(res)) {
              val indexStr = res.replaceAll(name, "")
              if (indexStr != "") index = Integer.valueOf(indexStr)

              while (usedNames.contains(name + index)) {
                index = index + 1
              }
            } else {
              return res
            }
            res = name + index
            res
          }
        })

      val un = names.toList match {
        case head :: _ if !macros.contains(head) => head
        case _ :: head :: _ => head
        case _ => "value"
      }

      usedNames.add(un)
      buf.append(un)

      u.getParent match {
        case typedExpr: ScTypedExpression =>
          needBraces = true
          buf.append(": ").append(typedExpr.`type`().get.canonicalText)
        case _ =>
      }

      val newParam = createReferenceExpressionFromText(un)
      underscoreToParam.put(u, newParam)
    }

    IntentionPreviewUtils.write { () =>
      for (u <- underscores) {
        val param = underscoreToParam.get(u)
        val replaced = u.replace(param.get).asInstanceOf[ScReferenceExpression]
        underscoreToParam.put(u, replaced)
        offsets.put(param.get.refName, replaced.getTextRange.getStartOffset)
      }
    }

    if (underscores.size > 1 || needBraces) buf.insert(0, "(").append(")")
    val arrow = ScalaPsiUtil.functionArrow
    buf.append(s" $arrow ")
    val diff = buf.length

    val newExpressionText = usedNames.foldLeft(expr.getText) { (text, un) =>
      val regex = raw"\(\s*${un}\s*\:\s*\S+\s*\)" // looks for `(un: ...)`
      Pattern.compile(regex).matcher(text).replaceFirst { mr: MatchResult =>
        val start = mr.start()
        if (start > 0 && text(start - 1).isLetterOrDigit) s"($un)" else un
      }
    }
    buf.append(newExpressionText)

    val newExpr = createExpressionFromText(buf.toString(), expr)
    val newParentEndOffset =
      if (ApplicationManager.getApplication.isUnitTestMode) {
        parentEndOffset
      } else {
        parentEndOffset + newExpr.getText.length - expr.getText.length + 1
      }

    IntentionPreviewUtils.write { () =>
      val document = editor.getDocument

      expr.replace(newExpr)
      PsiDocumentManager.getInstance(project).commitDocument(document)

      if (IntentionPreviewUtils.isIntentionPreviewActive) return

      val file = PsiDocumentManager.getInstance(project).getPsiFile(document)
      val parent = PsiTreeUtil.findCommonParent(file.findElementAt(parentStartOffset), file.findElementAt(newParentEndOffset))

      val builder: TemplateBuilderImpl = TemplateBuilderFactory.getInstance().
        createTemplateBuilder(parent).asInstanceOf[TemplateBuilderImpl]
      val params = new mutable.HashMap[Int, String]()
      val depends = new mutable.HashMap[Int, String]()

      var index: Int = 1
      parent match {
        case f: ScFunctionExpr =>
          for (parameter <- f.parameters) {
            val lookupExpr = new MyLookupExpression(parameter.name, null, parameter, f, false, null)
            builder.replaceElement(parameter.nameId, parameter.name, lookupExpr, true)

            val dependantParam = file.findElementAt(offsets(parameter.name) + diff)
            builder.replaceElement(dependantParam, parameter.name + "_1", parameter.name, false)

            params.put(index, parameter.name)
            depends.put(index, parameter.name + "_1")
            index = index + 1
          }

        case _ =>
      }

      CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(parent)

      editor.getCaretModel.moveToOffset(parent.getTextRange.getStartOffset)
      val template = builder.buildInlineTemplate()
      val myHighlighters = new ArrayBuffer[RangeHighlighter]
      val rangesToHighlight: mutable.HashMap[TextRange, TextAttributesKey] = new mutable.HashMap

      TemplateManager.getInstance(project).startTemplate(editor, template, new TemplateEditingAdapter {
        override def waitingForInput(template: Template): Unit = {
          markCurrentVariables(1)
        }

        override def currentVariableChanged(templateState: TemplateState, template: Template,
                                            oldIndex: Int, newIndex: Int): Unit = {
          if (oldIndex >= 0) clearHighlighters()
          if (newIndex > 0) markCurrentVariables(newIndex + 1)
        }

        override def templateCancelled(template: Template): Unit = {
          clearHighlighters()
        }

        override def templateFinished(template: Template, brokenOff: Boolean): Unit = {
          clearHighlighters()
        }

        private def addHighlights(ranges: mutable.HashMap[TextRange, TextAttributesKey], editor: Editor,
                                  highlighters: ArrayBuffer[RangeHighlighter], highlightManager: HighlightManager): Unit = {
          for ((range, attributes) <- ranges) {
            highlightManager.addOccurrenceHighlight(
              editor, range.getStartOffset, range.getEndOffset,
              attributes, 0, highlighters.asJava)
          }
          for (highlighter <- highlighters) {
            highlighter.setGreedyToLeft(true)
            highlighter.setGreedyToRight(true)
          }
        }

        private def markCurrentVariables(index: Int): Unit = {
          val templateState: TemplateState = TemplateManagerImpl.getTemplateState(editor)
          var i: Int = 0

          while (i < templateState.getSegmentsCount) {
            val segmentOffset: TextRange = templateState.getSegmentRange(i)
            val name: String = template.getSegmentName(i)
            var attributes: TextAttributesKey = null
            if (name == params(index)) {
              attributes = EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES
            }
            else if (name == depends(index)) {
              attributes = EditorColors.SEARCH_RESULT_ATTRIBUTES
            }
            if (attributes != null) rangesToHighlight.put(segmentOffset, attributes)
            i += 1
          }
          addHighlights(rangesToHighlight, editor, myHighlighters, HighlightManager.getInstance(project))
        }

        private def clearHighlighters(): Unit = {
          val highlightManager = HighlightManager.getInstance(project)
          myHighlighters.foreach { a => highlightManager.removeSegmentHighlighter(editor, a) }
          rangesToHighlight.clear()
          myHighlighters.clear()
        }
      })

      PsiDocumentManager.getInstance(project).commitDocument(document)
    }
  }