in scala/scala-impl/src/org/jetbrains/plugins/scala/editor/enterHandler/MultilineStringEnterHandler.scala [68:324]
override def postProcessEnter(file: PsiFile, editor: Editor, dataContext: DataContext): Result = {
if (!file.is[ScalaFile]) return Result.Continue
if (!wasInMultilineString) return Result.Continue
wasInMultilineString = false
val project = file.getProject
val document = editor.getDocument
document.commit(project) // TODO: AVOID COMMITTING DOCUMENTS ON TYPING!
val caretModel = editor.getCaretModel
val offset = caretModel.getOffset
val caretMarker = document.createRangeMarker(offset, offset)
caretMarker.setGreedyToRight(true)
def caretOffset = caretMarker.getEndOffset
val element = file.findElementAt(offset)
if (element == null) return Result.Continue
val literal: ScLiteral = MultilineStringUtil.findParentMLString(element) match {
case Some(v) => v
case _ => return Result.Continue
}
val literalOffset: Int = literal.getTextRange.getStartOffset
val interpolRef: String = MultilineStringUtil.interpolatorPrefix(literal)
val firstMLQuote: String = interpolRef + MultilineQuotes
val firstMLQuoteLength: Int = firstMLQuote.length
val settings = new MultilineStringSettings(project)
import settings._
if (!settings.supportMultilineString || offset - literalOffset < firstMLQuoteLength)
return Result.Continue
def getLineByNumber(number: Int): String = {
val sequence = document.getImmutableCharSequence
val start = document.getLineStartOffset(number)
val end = document.getLineEndOffset(number)
sequence.substring(start, end)
}
def insertNewLine(nlOffset: Int, indent: Int, trimPreviousLine: Boolean,
marginChar: Option[Char] = None): Unit = {
document.insertString(nlOffset, "\n")
forceIndent(nlOffset + 1, indent, marginChar)
if (trimPreviousLine) {
val line = getLineByNumber(document.getLineNumber(nlOffset))
var i = 0
def charToCheck = line.charAt(line.length - 1 - i)
while (i <= line.length - 1 && (charToCheck == ' ' || charToCheck == '\t')) {
i += 1
}
document.deleteString(nlOffset - i, nlOffset)
}
}
def forceIndent(offset: Int, indent: Int, marginChar: Option[Char]): Unit = {
val lineNumber = document.getLineNumber(offset)
val lineStart = document.getLineStartOffset(lineNumber)
val line = getLineByNumber(lineNumber)
val wsPrefix = line.takeWhile(c => c == ' ' || c == '\t')
document.replaceString(lineStart, lineStart + wsPrefix.length, getSmartSpaces(indent) + marginChar.getOrElse(""))
}
inWriteAction {
val currentLineNumber = document.getLineNumber(offset)
val prevLineNumber = currentLineNumber - 1
val nextLineNumber = currentLineNumber + 1
assert(prevLineNumber >= 0)
val prevLine = getLineByNumber(prevLineNumber)
val currentLine = getLineByNumber(prevLineNumber + 1)
val nextLine = if (document.getLineCount > nextLineNumber) getLineByNumber(prevLineNumber + 2) else ""
def prevLinePrefixAfterDelimiter(offsetInLine: Int): Int =
StringUtils.substring(prevLine, offsetInLine).segmentLength(c => c == ' ' || c == '\t')
val literalText = literal.getText
val lines = literalText.split("\n")
val marginChar: Char = MultilineStringUtil.getMarginChar(element)
val marginCharOpt: Option[Char] = {
if (settings.insertMargin && (
lines.length > 3 ||
MultilineStringUtil.hasMarginChars(element, marginChar.toString) ||
MultilineStringUtil.needAddByType(literal))) {
Some(marginChar)
} else {
None
}
}
lazy val insertedBracketsOnSingleLine = lines.length == 3 && {
def betweenBrackets = lines(0).endsWith('(') && lines(2).trim.startsWith(')')
def betweenBraces = lines(0).endsWith('{') && lines(2).trim.startsWith('}')
def caretIsBetweenBrackets = currentLineNumber == document.getLineNumber(literal.getTextRange.getStartOffset) + 1
(betweenBrackets || betweenBraces) && caretIsBetweenBrackets
}
def handleEnterInsideMultilineExpanded(): Unit = {
if (settings.insertMargin && MultilineStringUtil.needAddByType(literal)) {
MultilineStringUtil.insertStripMargin(document, literal, marginChar)
}
val needNewLineBeforeLiteral = quotesOnNewLine && !literal.startsFromNewLine(false)
if (needNewLineBeforeLiteral) {
insertNewLine(literalOffset, 0, trimPreviousLine = true)
}
val manager = CodeStyleManager.getInstance(project)
val newLinesAdded = insertedBracketsOnSingleLine.toInt + needNewLineBeforeLiteral.toInt
manager.adjustLineIndent(document, document.getLineStartOffset(currentLineNumber))
val firstLineIndent: Int = {
val lineIdx = prevLineNumber + needNewLineBeforeLiteral.toInt
val lineOffset = document.getLineStartOffset(lineIdx)
val indentStr = manager.getLineIndent(document, lineOffset)
calcIndentSize(indentStr)
}
val quotesIndent = firstLineIndent + interpolRef.length
forceIndent(caretOffset, quotesIndent + marginIndent, marginCharOpt)
if (insertedBracketsOnSingleLine) {
forceIndent(caretOffset + 1, quotesIndent, marginCharOpt)
}
document.commit(project) // TODO: AVOID COMMITTING DOCUMENTS ON TYPING!
if (settings.insertMargin) {
for {
lineIdx <- nextLineNumber to currentLineNumber + newLinesAdded
if lineIdx < document.getLineCount
} manager.adjustLineIndent(document, document.getLineStartOffset(lineIdx))
}
val closingQuotesOnNewLine =
settings.closingQuotesOnNewLine && literalText.substring(offset - literalOffset) == MultilineQuotes
if (closingQuotesOnNewLine) {
caretMarker.setGreedyToRight(false)
insertNewLine(caretOffset, quotesIndent, trimPreviousLine = false, marginCharOpt)
caretMarker.setGreedyToRight(true)
if (marginCharOpt.isDefined) {
manager.adjustLineIndent(document, document.getLineStartOffset(currentLineNumber + newLinesAdded + 1))
}
}
}
def handleEnterInsideMultiline(): Unit = {
val prevLineOffset = document.getLineStartOffset(prevLineNumber)
val currentLineOffset = document.getLineStartOffset(currentLineNumber)
val prevLineTrimmed = prevLine.trim
val isPrevLineFirst = prevLineTrimmed.startsWith(firstMLQuote)
val wsPrefixLength: Int = prevLine.segmentLength(c => c == ' ' || c == '\t')
val quotesOptLength = if (isPrevLineFirst) firstMLQuoteLength else 0
val prevLineStriped: String = {
val idx = wsPrefixLength + quotesOptLength
prevLine.substring(idx)
}
def handleEnterWithMargin(): Unit = {
val currentLineHasMarginChar = currentLine.trim.startsWith(marginChar)
if (currentLineHasMarginChar) return
val inBraces = prevLine.endsWith('{') && nextLine.trim.startsWith('}') || prevLine.endsWith('(') && nextLine.trim.startsWith(')')
val prefix: String = {
if (inBraces)
getPrefix(prevLine) + getSmartSpaces(quotesOptLength)
else if (prevLineStriped.trim.startsWith(marginChar))
getPrefix(prevLine) + getSmartSpaces(quotesOptLength)
else if (nextLine.trim.startsWith(marginChar))
getPrefix(nextLine)
else
getPrefix(currentLine)
}
val indentSizeAfterMargin: Int = {
val offsetToContent =
if (isPrevLineFirst) firstMLQuoteLength + prevLineStriped.startsWith(marginChar).toInt
else 1
prevLinePrefixAfterDelimiter(wsPrefixLength + offsetToContent)
}
forceIndent(caretOffset, getSmartLength(prefix), marginCharOpt)
document.insertString(caretOffset, getSpaces(indentSizeAfterMargin))
if (inBraces) {
val nextLineOffset = document.getLineStartOffset(prevLineNumber + 2)
forceIndent(nextLineOffset, 0, None)
document.insertString(nextLineOffset, marginChar.toString + getSpaces(indentSizeAfterMargin))
forceIndent(nextLineOffset, getSmartLength(prefix), None)
}
}
def handleEnterWithoutMargin(): Unit = {
val isCurrentLineEmpty = StringUtils.isBlank(currentLine)
val isPrevLineEmpty = prevLine.trim.isEmpty
if (prevLineOffset < literalOffset) {
val beforeQuotes = prevLinePrefixAfterDelimiter(0)
val elementStart = prevLine.indexOf(firstMLQuote) + firstMLQuoteLength
val prevLineWsPrefixAfterQuotes = prevLinePrefixAfterDelimiter(elementStart)
val spacesToInsert =
if (isPrevLineFirst) {
beforeQuotes + firstMLQuoteLength + prevLineWsPrefixAfterQuotes
} else {
val shiftLeft = if (isCurrentLineEmpty) 0 else wsPrefixLength
elementStart - shiftLeft + prevLineWsPrefixAfterQuotes
}
forceIndent(currentLineOffset, getSmartLength(getSmartSpaces(spacesToInsert)), None)
}
else if (isCurrentLineEmpty && !isPrevLineEmpty) {
forceIndent(caretOffset, wsPrefixLength, None)
}
else if (isPrevLineEmpty) {
forceIndent(caretOffset, prevLine.length, None)
}
else if (isPrevLineFirst) {
val wsAfterQuotes = prevLinePrefixAfterDelimiter(wsPrefixLength + firstMLQuoteLength) + firstMLQuoteLength
forceIndent(caretOffset, wsAfterQuotes, None)
}
}
val literalAlreadyHasLineMargin: Boolean = {
// first line can contain quotes, so check stripped content
def prevLineHasMargin = prevLineStriped.startsWith(marginChar)
def otherLinesHaveMargin = lines.exists(_.trim.startsWith(marginChar))
prevLineHasMargin || otherLinesHaveMargin
}
if (literalAlreadyHasLineMargin && settings.insertMargin) {
handleEnterWithMargin()
} else {
handleEnterWithoutMargin()
}
}
val wasSingleLine = lines.length <= 2 || insertedBracketsOnSingleLine
if (wasSingleLine) {
handleEnterInsideMultilineExpanded()
} else {
handleEnterInsideMultiline()
}
document.insertString(caretOffset, whiteSpaceAfterCaret)
caretModel.moveToOffset(caretOffset)
caretMarker.dispose()
}
Result.Stop
}