in scala/scala-impl/src/org/jetbrains/plugins/scala/lang/formatting/processors/ScalaSpacingProcessor.scala [161:1508]
private def getSpacingImpl(
left: ScalaBlock,
right: ScalaBlock,
leftIsLineComment: Boolean,
settings: CommonCodeStyleSettings,
scalaSettings: ScalaCodeStyleSettings
): Spacing = {
// if keepLineBreaks is disabled but we have a line comment on a previous line,
// we shouldn't remove the line break, because it can lead to a broken code, for example:
// if (true) // comment
// {
// }
// should NOT transform to
// if (true) // comment {
// }
val keepLineBreaks: Boolean = settings.KEEP_LINE_BREAKS || leftIsLineComment
val keepBlankLinesInCode : Int = settings.KEEP_BLANK_LINES_IN_CODE
val keepBlankLinesInDeclarations: Int = settings.KEEP_BLANK_LINES_IN_DECLARATIONS
val keepBlankLinesBeforeRBrace : Int = settings.KEEP_BLANK_LINES_BEFORE_RBRACE
def getSpacing(minMaxSpaces: Int, minLineFeeds: Int): Spacing =
Spacing.createSpacing(
minMaxSpaces,
minMaxSpaces,
minLineFeeds,
keepLineBreaks,
if (keepLineBreaks) keepBlankLinesInCode else 0
)
def getDependentLFSpacing(minMaxSpaces: Int, range: TextRange): Spacing =
Spacing.createDependentLFSpacing(
minMaxSpaces,
minMaxSpaces,
range,
keepLineBreaks,
if (keepLineBreaks) keepBlankLinesInCode else 0
)
//new formatter spacing
val leftNode = left.getNode
val rightNode = right.getNode
val leftNodeParent = leftNode.getTreeParent
val rightNodeParent = rightNode.getTreeParent
val leftElementType = leftNode.getElementType
val rightElementType = rightNode.getElementType
val leftNodeParentElementType = leftNodeParent.getElementType
val rightNodeParentElementType = rightNodeParent.getElementType
val leftPsi = leftNode.getPsi
val rightPsi = rightNode.getPsi
val leftPsiParent = leftNode.getPsi.getParent
val rightPsiParent = rightNode.getPsi.getParent
// TODO: it's not good for performance to extract entire file text on each reformat
// (it can be reformat of tiny block, e.g. after tiny refactoring)
val fileText = PsiDocumentManager.getInstance(leftPsi.getProject).nullSafe
.map(_.getDocument(leftPsi.getContainingFile))
.map(_.getImmutableCharSequence)
.getOrElse(leftPsi.getContainingFile.charSequence)
val fileTextRange = new TextRange(0, fileText.length())
val leftTextRange = left.getTextRange
val rightTextRange = right.getTextRange
/**
* This is not nodes text! This is blocks text, which can be different from node.
*
* @todo consider not using substring for left and right: for big notes it's not cheep operation
*/
@deprecated("do not access block text directly, this potentially can be a heavyweight operation, use AST nodes")
val (leftBlockString, rightBlockString) =
if (fileTextRange.contains(leftTextRange) && fileTextRange.contains(rightTextRange)) {
(fileText.substring(leftTextRange), fileText.substring(rightTextRange))
} else {
LOG.error("File text: \n%s\n\nDoesn't contains nodes:\n(%s, %s)".format(fileText, leftPsi.getText, rightPsi.getText))
(leftPsi.getText, rightPsi.getText)
}
val spacesMin: Integer = spacesToPreventNewIds(left, right, fileText, fileTextRange)
val WITHOUT_SPACING = getSpacing(spacesMin, 0)
val WITHOUT_SPACING_NO_KEEP = Spacing.createSpacing(spacesMin, spacesMin, 0, leftIsLineComment, 0)
val WITHOUT_SPACING_DEPENDENT = (range: TextRange) => getDependentLFSpacing(spacesMin, range)
val WITH_SPACING = getSpacing(1, 0)
val WITH_SPACING_NO_KEEP = Spacing.createSpacing(1, 1, 0, leftIsLineComment, 0)
val WITH_SPACING_DEPENDENT = (range: TextRange) => getDependentLFSpacing(1, range)
val ON_NEW_LINE = getSpacing(0, 1)
val DOUBLE_LINE = getSpacing(0, 2)
val NO_SPACING_WITH_NEWLINE = Spacing.createSpacing(0, 0, 0, true, 1)
val NO_SPACING = Spacing.createSpacing(spacesMin, spacesMin, 0, false, 0)
val COMMON_SPACING = Spacing.createSpacing(1, 1, 0, keepLineBreaks, 100)
val IMPORT_BETWEEN_SPACING = Spacing.createSpacing(0, 0, 1, true, 100)
def withSpacingIf(condition: Boolean) = if (condition) WITH_SPACING else WITHOUT_SPACING
if (rightPsi.isInstanceOf[PsiComment] && settings.KEEP_FIRST_COLUMN_COMMENT)
return Spacing.createKeepingFirstColumnSpacing(0, Integer.MAX_VALUE, true, settings.KEEP_BLANK_LINES_IN_CODE)
import org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes._
if (leftPsi.isInstanceOf[PsiComment] && rightPsi.isInstanceOf[PsiComment]) {
return ON_NEW_LINE
}
if (leftElementType == ScalaDirectiveElementTypes.SCALA_DIRECTIVE || rightElementType == ScalaDirectiveElementTypes.SCALA_DIRECTIVE) {
return ON_NEW_LINE
}
//Scala 3 quotation/splicing start tokens: '{ or ${
leftElementType match {
case ScalaTokenType.QuoteStart | ScalaTokenType.SpliceStart if rightElementType == tLBRACE =>
return NO_SPACING
case _ =>
}
leftNodeParentElementType match {
case QUOTED_BLOCK | SPLICED_BLOCK_EXPR | SPLICED_PATTERN_EXPR =>
val result = {
//empty quoted block: '{ } should be formatted just as '{}
if (leftElementType == tLBRACE && rightElementType == tRBRACE) NO_SPACING
else COMMON_SPACING
}
return result
case _ =>
}
val elementTypesWithParents = (leftElementType, rightElementType, leftNodeParentElementType, rightNodeParentElementType)
def processElementTypes(pf: PartialFunction[(IElementType, IElementType, IElementType, IElementType), Spacing]): Option[Spacing] =
pf.lift(elementTypesWithParents)
//ScalaDoc
def docCommentOf(node: ASTNode) = node.getPsi.parentsInFile.findByType[ScDocComment]
.getOrElse(throw new RuntimeException("Unable to find parent doc comment"))
def isScalaDocListStart(typ: IElementType): Boolean =
typ match {
case ScalaDocTokenType.DOC_LIST_ITEM_HEAD |
ScalaDocElementTypes.DOC_LIST_ITEM |
ScalaDocElementTypes.DOC_LIST => true
case _ => false
}
val tagSpacing =
if (scalaSettings.SD_PRESERVE_SPACES_IN_TAGS)
Spacing.createSpacing(0, Int.MaxValue, 0, false, 0)
else WITH_SPACING
val scaladocSpacing = elementTypesWithParents match {
case (_, ScalaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS, _, _) =>
NO_SPACING_WITH_NEWLINE
case (_, ScalaDocTokenType.DOC_COMMENT_END, _, _) =>
//val version = docCommentOf(rightNode).version
if (false /*version == 1*/)
NO_SPACING_WITH_NEWLINE
else
WITH_SPACING
case (ScalaDocTokenType.DOC_COMMENT_START, rightType, _, _) =>
if (isScalaDocListStart(rightType)) Spacing.getReadOnlySpacing
//else if (docCommentOf(leftNode).version == 1) NO_SPACING_WITH_NEWLINE
else if (rightType == ScalaDocTokenType.DOC_WHITESPACE/*getText(rightNode, fileText)(0) == ' '*/) WITHOUT_SPACING
else WITH_SPACING
case (x, y, _, _) if !scalaSettings.ENABLE_SCALADOC_FORMATTING &&
ScalaDocElementTypes.AllElementAndTokenTypes.contains(x) &&
ScalaDocElementTypes.AllElementAndTokenTypes.contains(y) =>
Spacing.getReadOnlySpacing
case (ScalaDocTokenType.DOC_COMMENT_LEADING_ASTERISKS, rightType, _, _) =>
rightType match {
case t if isScalaDocListStart(t) => Spacing.getReadOnlySpacing
case ScalaDocTokenType.DOC_INNER_CODE => Spacing.getReadOnlySpacing
case ScalaDocTokenType.DOC_WHITESPACE => WITHOUT_SPACING
case _ => WITH_SPACING
}
case (_, ScalaDocElementTypes.DOC_LIST_ITEM | ScalaDocElementTypes.DOC_LIST, _, _) =>
Spacing.getReadOnlySpacing
case (ScalaDocTokenType.DOC_LIST_ITEM_HEAD, _, _, _) if scalaSettings.SD_ALIGN_LIST_ITEM_CONTENT && !leftPsi.getText.last.isWhitespace =>
WITH_SPACING
case (ScalaDocTokenType.DOC_TAG_NAME, _, _, _) =>
if (nodeTextStartsWith(rightNode, fileText, ' ')) WITH_SPACING
else tagSpacing
case (ScalaDocTokenType.DOC_TAG_VALUE_TOKEN, _, ScalaDocElementTypes.DOC_TAG, _) =>
tagSpacing
case (
ScalaDocTokenType.DOC_COMMENT_DATA,
ScalaDocTokenType.DOC_COMMENT_DATA,
ScalaDocElementTypes.DOC_PARAGRAPH,
ScalaDocElementTypes.DOC_PARAGRAPH
) if leftNodeParentElementType eq rightNodeParentElementType=>
//Handle case when there are two lines of same paragraph without leading asterisk.
//Example:
// /**
// * hello
// world
// */
// class Example
//This branch is primarily needed for range formatting inside scaladoc.
//It can be invoked during some other actions like pasting to scaladoc
return WITH_SPACING
case (_, x, _, _) if ScalaDocTokenType.ALL_SCALADOC_TOKENS.contains(x) =>
Spacing.getReadOnlySpacing
case (x, TokenType.ERROR_ELEMENT, _, _) if ScalaDocTokenType.ALL_SCALADOC_TOKENS.contains(x) =>
WITH_SPACING
case (x, _, _, _) if ScalaDocTokenType.ALL_SCALADOC_TOKENS.contains(x) =>
Spacing.getReadOnlySpacing
case _ =>
null
}
if (scaladocSpacing != null)
return scaladocSpacing
//Xml
processElementTypes {
case (ScalaElementType.XML_START_TAG, ScalaElementType.XML_END_TAG, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else WITHOUT_SPACING
case (ScalaElementType.XML_START_TAG, ScalaXmlTokenTypes.XML_DATA_CHARACTERS, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else WITHOUT_SPACING
case (ScalaXmlTokenTypes.XML_DATA_CHARACTERS, ScalaElementType.XML_END_TAG, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else WITHOUT_SPACING
case (ScalaElementType.XML_START_TAG, _, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else ON_NEW_LINE
case (_, ScalaElementType.XML_END_TAG, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else ON_NEW_LINE
case (ScalaXmlTokenTypes.XML_DATA_CHARACTERS, ScalaXmlTokenTypes.XML_DATA_CHARACTERS, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else WITH_SPACING
case (ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_TOKEN, ScalaXmlTokenTypes.XML_CHAR_ENTITY_REF, _, _) =>
Spacing.getReadOnlySpacing
case (ScalaXmlTokenTypes.XML_CHAR_ENTITY_REF, ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_TOKEN, _, _) =>
Spacing.getReadOnlySpacing
case (ScalaXmlTokenTypes.XML_DATA_CHARACTERS, ScalaXmlTokenTypes.XML_CDATA_END, _, _) =>
Spacing.getReadOnlySpacing
case (ScalaXmlTokenTypes.XML_DATA_CHARACTERS, _, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else ON_NEW_LINE
case (ScalaXmlTokenTypes.XML_CDATA_START, ScalaXmlTokenTypes.XML_DATA_CHARACTERS, _, _) =>
Spacing.getReadOnlySpacing
case (_, ScalaXmlTokenTypes.XML_DATA_CHARACTERS, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else ON_NEW_LINE
case (ScalaElementType.XML_EMPTY_TAG, ScalaElementType.XML_EMPTY_TAG, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else ON_NEW_LINE
case (_, ScalaTokenTypesEx.SCALA_IN_XML_INJECTION_START | ScalaTokenTypesEx.SCALA_IN_XML_INJECTION_END, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_START_TAG_START | ScalaXmlTokenTypes.XML_END_TAG_START |
ScalaXmlTokenTypes.XML_CDATA_START | ScalaXmlTokenTypes.XML_PI_START, _, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (_, ScalaXmlTokenTypes.XML_TAG_END | ScalaXmlTokenTypes.XML_EMPTY_ELEMENT_END |
ScalaXmlTokenTypes.XML_CDATA_END | ScalaXmlTokenTypes.XML_PI_END, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_NAME, ScalaElementType.XML_ATTRIBUTE, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else COMMON_SPACING
case (ScalaXmlTokenTypes.XML_NAME, ScalaXmlTokenTypes.XML_EQ, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_EQ, ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_START_DELIMITER |
ScalaTokenTypesEx.SCALA_IN_XML_INJECTION_START, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_START_DELIMITER, ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_TOKEN, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_TOKEN, ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_END_DELIMITER, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaTokenTypesEx.SCALA_IN_XML_INJECTION_START | ScalaTokenTypesEx.SCALA_IN_XML_INJECTION_END, _, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (_, ScalaXmlTokenTypes.XML_DATA_CHARACTERS | ScalaXmlTokenTypes.XML_COMMENT_END |
ScalaXmlTokenTypes.XML_COMMENT_CHARACTERS, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (ScalaXmlTokenTypes.XML_DATA_CHARACTERS | ScalaXmlTokenTypes.XML_COMMENT_START |
ScalaXmlTokenTypes.XML_COMMENT_CHARACTERS, _, _, _) =>
if (scalaSettings.KEEP_XML_FORMATTING) Spacing.getReadOnlySpacing
else NO_SPACING
case (el1, el2, _, _) if scalaSettings.KEEP_XML_FORMATTING &&
(ScalaXmlTokenTypes.XML_ELEMENTS.contains(el1) || ScalaXmlTokenTypes.XML_ELEMENTS.contains(el2)) => Spacing.getReadOnlySpacing
case (ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_START_DELIMITER, _, _, _) => Spacing.getReadOnlySpacing
case (_, ScalaXmlTokenTypes.XML_ATTRIBUTE_VALUE_END_DELIMITER, _, _) => Spacing.getReadOnlySpacing
} match {
case Some(result) =>
return result
case _ =>
}
// capture sets take precedence above most stuff
rightElementType match {
case ScalaElementType.CAPTURE_SET => return WITHOUT_SPACING
case ScalaTokenType.CaptureOperator => return WITHOUT_SPACING
case ScalaTokenType.ReachCapabilityStar => return WITHOUT_SPACING
case _ =>
}
leftElementType match {
case ScalaTokenType.ReachCapabilityStar => return WITHOUT_SPACING
case _ =>
}
def isParenthesis(psi: PsiElement): Boolean = psi.is[ScParenthesizedElement]
if (leftElementType == tLPARENTHESIS && isParenthesis(leftPsiParent)) {
return if (settings.PARENTHESES_EXPRESSION_LPAREN_WRAP)
if (settings.SPACE_WITHIN_PARENTHESES) WITH_SPACING_DEPENDENT(leftPsiParent.getTextRange)
else WITHOUT_SPACING_DEPENDENT(leftPsiParent.getTextRange)
else if (settings.SPACE_WITHIN_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
//multiline strings
if (scalaSettings.MULTILINE_STRING_OPENING_QUOTES_ON_NEW_LINE && isMultiLineStringCase(rightPsi)) {
return ON_NEW_LINE
}
leftPsi match {
case l: ScStringLiteral if l.isMultiLineString && rightNode == leftNode =>
return spacingForMultilineStringPart(l)
case Parent(l: ScStringLiteral) if l.isMultiLineString =>
return spacingForMultilineStringPart(l)
case _ =>
}
def spacingForMultilineStringPart(l: ScLiteral): Spacing = {
val marginChar = MultilineStringUtil.getMarginChar(leftPsi).toString
if (MultilineStringUtil.looksLikeUsesMargins(l) && leftBlockString != marginChar && rightBlockString == marginChar) {
NO_SPACING_WITH_NEWLINE
} else if (rightBlockString == MultilineStringUtil.MultilineQuotes && scalaSettings.MULTILINE_STRING_ALIGN_DANGLING_CLOSING_QUOTES) {
NO_SPACING_WITH_NEWLINE
} else {
Spacing.getReadOnlySpacing
}
}
//for interpolated strings
if (rightElementType == tINTERPOLATED_STRING_ESCAPE)
return Spacing.getReadOnlySpacing
if (rightElementType == tINTERPOLATED_STRING || rightElementType == tINTERPOLATED_MULTILINE_STRING) {
return if (leftBlockString == MultilineStringUtil.getMarginChar(leftPsi).toString) Spacing.getReadOnlySpacing
else WITHOUT_SPACING
}
if (leftElementType == ScalaElementType.INTERPOLATED_PREFIX_LITERAL_REFERENCE)
return WITHOUT_SPACING
if (leftElementType == ScalaElementType.INTERPOLATED_PREFIX_PATTERN_REFERENCE)
return WITHOUT_SPACING
if (rightElementType == tINTERPOLATED_STRING_END)
return Spacing.getReadOnlySpacing
if (leftElementType == tINTERPOLATED_STRING_INJECTION || rightElementType == tINTERPOLATED_STRING_INJECTION)
return Spacing.getReadOnlySpacing
if (Option(leftNode.getTreeParent.getTreePrev).exists(_.getElementType == tINTERPOLATED_STRING_ID))
return Spacing.getReadOnlySpacing
if (leftElementType == tWRONG_STRING || rightElementType == tWRONG_STRING)
return Spacing.getReadOnlySpacing
@tailrec
def isMultiLineStringCase(psiElem: PsiElement): Boolean = {
psiElem match {
case ml: ScStringLiteral if ml.isMultiLineString =>
val nodeOffset = rightNode.getTextRange.getStartOffset
val magicCondition = rightTextRange.contains(new TextRange(nodeOffset, nodeOffset + 3))
val actuallyMultiline = rightBlockString.contains("\n")
magicCondition && actuallyMultiline
case _: ScInfixExpr | _: ScReferenceExpression | _: ScMethodCall =>
isMultiLineStringCase(psiElem.getFirstChild)
case _ => false
}
}
// detached line comment
if (rightPsi.isInstanceOf[PsiComment] && rightElementType == ScalaTokenTypes.tLINE_COMMENT) {
val result = if (scalaSettings.KEEP_COMMENTS_ON_SAME_LINE)
COMMON_SPACING
else {
val isAloneOnTheLine = PsiTreeUtil.prevLeaf(rightPsi) match {
case ws: PsiWhiteSpace => containsNewLine(fileText, ws.getTextRange)
case _ => false
}
if (isAloneOnTheLine) COMMON_SPACING
else ON_NEW_LINE
}
return result
}
// ';' from right
if (rightElementType == ScalaTokenTypes.tSEMICOLON) {
val result = leftElementType match {
case ScalaTokenTypes.tLBRACE => IMPORT_BETWEEN_SPACING
case _ if leftIsLineComment => WITHOUT_SPACING
case _ => NO_SPACING
}
return result
}
if (rightElementType == tRPARENTHESIS && isParenthesis(rightPsiParent)) {
return if (settings.PARENTHESES_EXPRESSION_RPAREN_WRAP)
if (settings.SPACE_WITHIN_PARENTHESES) WITH_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else WITHOUT_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else if (settings.SPACE_WITHIN_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
if (leftElementType == tIDENTIFIER && rightPsi.isInstanceOf[ScArgumentExprList]) {
if (!nodeTextStartsWith(rightNode, fileText, '{')) {
return if (settings.SPACE_BEFORE_METHOD_CALL_PARENTHESES) WITH_SPACING else WITHOUT_SPACING
}
}
if (leftElementType == tLPARENTHESIS && leftPsiParent.is[ScArgumentExprList, ScPatternArgumentList]) {
val newLineAfterLParen =
scalaSettings.CALL_PARAMETERS_NEW_LINE_AFTER_LPAREN == ScalaCodeStyleSettings.NEW_LINE_ALWAYS ||
scalaSettings.CALL_PARAMETERS_NEW_LINE_AFTER_LPAREN == ScalaCodeStyleSettings.NEW_LINE_FOR_MULTIPLE_ARGUMENTS &&
leftPsiParent.asInstanceOf[ScArguments].getArgsCount > 1
return if (newLineAfterLParen) {
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES && rightElementType != tRPARENTHESIS ||
settings.SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES && rightElementType == tRPARENTHESIS) {
WITH_SPACING_DEPENDENT(leftPsiParent.getTextRange)
} else {
WITHOUT_SPACING_DEPENDENT(leftPsiParent.getTextRange)
}
} else if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES && rightElementType != tRPARENTHESIS ||
settings.SPACE_WITHIN_EMPTY_METHOD_CALL_PARENTHESES && rightElementType == tRPARENTHESIS) {
WITH_SPACING
} else {
WITHOUT_SPACING
}
}
if (rightElementType == tRPARENTHESIS && rightPsiParent.is[ScArgumentExprList, ScPatternArgumentList]) {
return if (settings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE)
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) WITH_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else WITHOUT_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
if (leftElementType == tLPARENTHESIS && leftPsiParent.isInstanceOf[ScParameterClause]) {
return if (settings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE)
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) WITH_SPACING_DEPENDENT(leftPsiParent.getTextRange)
else WITHOUT_SPACING_DEPENDENT(leftPsiParent.getTextRange)
else if (settings.SPACE_WITHIN_METHOD_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
if (rightElementType == tRPARENTHESIS && rightPsiParent.isInstanceOf[ScParameterClause]) {
return if (settings.METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE)
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) WITH_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else WITHOUT_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else if (settings.SPACE_WITHIN_METHOD_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
if (nodeTextStartsWith(rightNode, fileText, '{')) {
//todo: spacing for early definitions
val result =
if (rightPsi.isInstanceOf[ScImportSelectors]) WITHOUT_SPACING
else if (leftPsiParent.isInstanceOf[ScParenthesisedTypeElement]) WITHOUT_SPACING
else if (rightPsi.is[ScExtendsBlock, ScEarlyDefinitions, ScTemplateBody]) {
settings.CLASS_BRACE_STYLE match {
case CommonCodeStyleSettings.END_OF_LINE =>
if (settings.SPACE_BEFORE_CLASS_LBRACE) WITH_SPACING_NO_KEEP
else WITHOUT_SPACING_NO_KEEP
case CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED =>
val extendsBlock = rightPsi match {
case e: ScExtendsBlock => e
case t: ScEarlyDefinitions => t.getParent
case t: ScTemplateBody => t.getParent
}
val startElement = extendsBlock.getParent match {
case b: ScTypeDefinition => b.nameId
case _: ScTemplateDefinition => extendsBlock
case b => b
}
val startOffset = startElement.getTextRange.getStartOffset
val range = new TextRange(startOffset, rightPsi.getTextRange.getStartOffset)
if (settings.SPACE_BEFORE_CLASS_LBRACE) WITH_SPACING_DEPENDENT(range)
else WITHOUT_SPACING_DEPENDENT(range)
case _ =>
ON_NEW_LINE
}
} else rightPsiParent match {
case _: ScBlock | _: ScEarlyDefinitions | _: ScTemplateBody | _: ScalaFile => ON_NEW_LINE
case _: ScPackaging if rightPsi.is[ScBlock] =>
//Example:
//package aaa.bbb.ccc
////(notice bank like here)
//{ some code in block}
//It's quite a dummy code but still...
WITH_SPACING
case _: ScArgumentExprList if rightPsi.isInstanceOf[ScBlock] => WITH_SPACING //don't add/remove newlines for partial function arguments
case parent =>
val (needSpace, braceStyle, startElement) =
parent match {
case fun: ScFunction =>
(settings.SPACE_BEFORE_METHOD_LBRACE, settings.METHOD_BRACE_STYLE, fun.nameId)
case _: ScMethodCall if rightPsi.isInstanceOf[ScArguments] =>
val style = settings.BRACE_STYLE
//Extra check is an optimization not to check `isInScala3File` all the time when default code style is used
//TODO (minor) we ask `isInScala3File` for every block, which is not optimal (it requires tree traversal to parent every time)
// ideally we need to store information `isScala3` somewhere in global context when constructing blocks for entire scala file
// (see other places using isInScala3File in formatter package)
val shouldEnforceBraceAtEndOfLine = style != CommonCodeStyleSettings.END_OF_LINE && left.node.getPsi.isInScala3File
val styleAdjusted = if (shouldEnforceBraceAtEndOfLine) CommonCodeStyleSettings.END_OF_LINE else style
(scalaSettings.SPACE_BEFORE_BRACE_METHOD_CALL, styleAdjusted, parent)
case _ =>
(true, settings.BRACE_STYLE, parent)
}
braceStyle match {
case CommonCodeStyleSettings.END_OF_LINE =>
if (needSpace) WITH_SPACING_NO_KEEP
else WITHOUT_SPACING_NO_KEEP
case CommonCodeStyleSettings.NEXT_LINE_IF_WRAPPED =>
val startOffset = startElement.getTextRange.getStartOffset
val range = new TextRange(startOffset, rightPsi.getTextRange.getStartOffset)
if (needSpace) WITH_SPACING_DEPENDENT(range)
else WITHOUT_SPACING_DEPENDENT(range)
case _ =>
ON_NEW_LINE
}
}
return result
}
//1. No space before braceless template body (with an empty extents list)
// class A:
//2. No spaces before the braceless template body (with a non-empty extents list)
// class A extends B:
// class A extends B with C:
//3. Add a space before "derives" in the extends block with empty parents
//4. Add a space between `:` and type in given-with (given structural instance)
// given intOrd: Ord42[Int] with ...
rightPsi match {
//NOTE: ScExtendsBlock with braces is handled before
case eb: ScExtendsBlock =>
val firstElementType = eb.firstChild.map(_.elementType)
val hasNonEmptyExtendsListOrDerivesClause = firstElementType.exists { et =>
et == ScalaTokenTypes.kEXTENDS ||
et.is[ScDerivesClauseElementType]
}
val isExtendsBlockInGivenInstance = leftElementType == ScalaTokenTypes.tCOLON
val needsSpace = hasNonEmptyExtendsListOrDerivesClause ||
isExtendsBlockInGivenInstance
return if (needsSpace) WITH_SPACING else WITHOUT_SPACING
case tb: ScTemplateBody =>
val needsSpace = tb.isEnclosedByBraces
return if (needsSpace) WITH_SPACING else WITHOUT_SPACING
case _: ScDerivesClause =>
return WITH_SPACING
case _ =>
}
//this is a dirty hack for SCL-9264. It looks bad, but seems to be the only fast way to make this work.
(leftElementType, leftPsi.getPrevSiblingNotWhitespace) match {
case (ScalaTokenTypes.tLBRACE | ScalaTokenTypes.tLPARENTHESIS, forNode: LeafPsiElement) if !left.isLeaf &&
forNode.getElementType == ScalaTokenTypes.kFOR => return COMMON_SPACING
case _ =>
}
if (leftPsi.isInstanceOf[ScStableCodeReference] && !rightPsi.isInstanceOf[ScPackaging]) {
leftPsiParent match {
case p: ScPackaging if p.reference.contains(leftPsi) =>
rightElementType match {
// colon stand for Scala3 braceless package syntax `package p1.p2:\n package p3`
case ScalaTokenTypes.tLBRACE | ScalaTokenTypes.tCOLON =>
case _ =>
return Spacing.createSpacing(0, 0, settings.BLANK_LINES_AFTER_PACKAGE + 1, keepLineBreaks, keepBlankLinesInCode)
}
case _ =>
}
}
if (leftPsi.isInstanceOf[ScPackaging]) {
return Spacing.createSpacing(0, 0, settings.BLANK_LINES_AFTER_PACKAGE + 1, keepLineBreaks, keepBlankLinesInCode)
}
if (rightPsi.isInstanceOf[ScPackaging]) {
val result =
if (leftPsi.isInstanceOf[ScStableCodeReference] || leftElementType == tLBRACE)
Spacing.createSpacing(0, 0, 1, keepLineBreaks, keepBlankLinesInCode)
else if (leftPsi.isInstanceOf[PsiComment] && leftPsi.getPrevSiblingNotWhitespaceComment.isInstanceOf[ScStableCodeReference])
ON_NEW_LINE
else
Spacing.createSpacing(0, 0, settings.BLANK_LINES_BEFORE_PACKAGE + 1, keepLineBreaks, keepBlankLinesInCode)
return result
}
if (leftPsi.isInstanceOf[ScImportStmt] && !rightPsi.isInstanceOf[ScImportStmt]) {
if (leftPsiParent.is[ScEarlyDefinitions, ScTemplateBody, ScalaFile, ScPackaging]) {
return if (rightElementType == ScalaTokenTypes.tLINE_COMMENT) ON_NEW_LINE
else Spacing.createSpacing(0, 0, settings.BLANK_LINES_AFTER_IMPORTS + 1, keepLineBreaks, keepBlankLinesInCode)
}
else return WITHOUT_SPACING
}
if (rightPsi.isInstanceOf[ScImportStmt] && !leftPsi.isInstanceOf[ScImportStmt]) {
val leftIsImport = leftPsi.getPrevSiblingNotWhitespace.isInstanceOf[ScImportStmt]
val leftIsCommentInsideImport = leftPsi.isInstanceOf[PsiComment] && leftPsi.getPrevSiblingNotWhitespaceComment.isInstanceOf[ScImportStmt]
if (!leftIsImport && !leftIsCommentInsideImport) {
if (rightPsiParent.is[ScEarlyDefinitions, ScTemplateBody, ScalaFile, ScPackaging]) {
return Spacing.createSpacing(0, 0, settings.BLANK_LINES_BEFORE_IMPORTS + 1, keepLineBreaks, keepBlankLinesInCode)
}
}
}
if (leftPsi.isInstanceOf[ScImportStmt] || rightPsi.isInstanceOf[ScImportStmt]) {
return Spacing.createSpacing(0, 0, 1, keepLineBreaks, keepBlankLinesInDeclarations)
}
// '}' or 'end'
if (TokenSets.RBRACE_OR_END_STMT.contains(rightElementType)) {
val rightTreeParent = rightNode.getTreeParent
val result = rightTreeParent.getPsi match {
case block@(_: ScEarlyDefinitions |
_: ScTemplateBody |
_: ScPackaging |
_: ScBlockExpr |
_: ScMatch |
_: ScCatchBlock) =>
val checkKeepOneLineLambdas =
scalaSettings.KEEP_ONE_LINE_LAMBDAS_IN_ARG_LIST && leftPsi.isInstanceOf[PsiComment]
val checkOneLineSpaces: Boolean = {
lazy val inMethod = scalaSettings.SPACES_IN_ONE_LINE_BLOCKS &&
rightTreeParent.getTreeParent.nullSafe.exists(_.isInstanceOf[ScFunction])
lazy val inSelfTypeBraces = scalaSettings.SPACE_INSIDE_SELF_TYPE_BRACES &&
leftPsiParent.getFirstChild.getNextSiblingNotWhitespace.isInstanceOf[ScSelfTypeElement]
lazy val inClosure = scalaSettings.SPACE_INSIDE_CLOSURE_BRACES && (leftElementType match {
case ScalaElementType.FUNCTION_EXPR => true
case ScalaElementType.CASE_CLAUSES => block.getParent.is[ScArgumentExprList, ScInfixExpr]
case _ =>
val insideInterpString = rightTreeParent.getTreeParent.nullSafe
.exists(_.getElementType == ScalaElementType.InterpolatedString)
scalaSettings.KEEP_ONE_LINE_LAMBDAS_IN_ARG_LIST &&
(leftPsi.is[ScFunctionExpr, ScCaseClauses] || block.isInstanceOf[ScBlockExpr] && !insideInterpString)
})
val isOneLineEmpty = leftBlockString == "{" || nodeTextContainsNewLine(block.getNode, fileText)
!isOneLineEmpty && (scalaSettings.SPACES_IN_ONE_LINE_BLOCKS || inMethod || inSelfTypeBraces || inClosure)
}
val needsSpace = checkKeepOneLineLambdas || checkOneLineSpaces
val spaces = if (needsSpace) 1 else 0
block match {
case _: ScTemplateBody =>
val isAnonymous = !PsiTreeUtil.getParentOfType(block, classOf[ScTemplateDefinition]).is[ScTypeDefinition]
val skipMinLines =
leftElementType == ScalaTokenTypes.tLBRACE ||
leftElementType == ScalaElementType.SELF_TYPE ||
COMMENTS_TOKEN_SET.contains(leftElementType)
val minLineFeeds =
if (skipMinLines) 0
else if (isAnonymous) 1
else settings.BLANK_LINES_BEFORE_CLASS_END + 1
Spacing.createSpacing(spaces, spaces, minLineFeeds, keepLineBreaks, keepBlankLinesBeforeRBrace)
case _ =>
Spacing.createDependentLFSpacing(spaces, spaces, block.getTextRange, keepLineBreaks, keepBlankLinesBeforeRBrace)
}
case _: ScImportSelectors =>
val refRange = leftNode.getTreeParent.getTextRange
if (scalaSettings.SPACES_IN_IMPORTS) WITH_SPACING_DEPENDENT(refRange)
else WITHOUT_SPACING_DEPENDENT(refRange)
case _ =>
Spacing.createSpacing(0, 0, 0, keepLineBreaks, keepBlankLinesBeforeRBrace)
}
return result
}
// TODO: do we need separate settings for : block syntax
// '{' or ':'
if (leftElementType == ScalaTokenTypes.tLBRACE || leftElementType == tCOLON && leftNodeParentElementType == TEMPLATE_BODY) {
if (!scalaSettings.PLACE_CLOSURE_PARAMETERS_ON_NEW_LINE) {
val b = leftNode.getTreeParent.getPsi
val spaceInsideOneLineBlock = scalaSettings.SPACES_IN_ONE_LINE_BLOCKS && !nodeTextContainsNewLine(b.getNode, fileText)
val spacing = if (scalaSettings.SPACE_INSIDE_CLOSURE_BRACES || spaceInsideOneLineBlock) WITH_SPACING else WITHOUT_SPACING
rightElementType match {
case ScalaElementType.FUNCTION_EXPR => return spacing
case ScalaElementType.CASE_CLAUSES =>
if (b.getParent.isInstanceOf[ScArgumentExprList] || b.getParent.isInstanceOf[ScInfixExpr]) return spacing
case _ =>
}
}
return leftNode.getTreeParent.getPsi match {
case _: ScTemplateBody if rightPsi.isInstanceOf[ScSelfTypeElement] =>
if (scalaSettings.PLACE_SELF_TYPE_ON_NEW_LINE) ON_NEW_LINE
else if (scalaSettings.SPACE_INSIDE_SELF_TYPE_BRACES) WITH_SPACING_NO_KEEP
else WITHOUT_SPACING_NO_KEEP
case b@(_: ScEarlyDefinitions | _: ScTemplateBody) =>
if (settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE && !nodeTextContainsNewLine(b.getNode, fileText)) {
Spacing.createDependentLFSpacing(0, 0, b.getTextRange, keepLineBreaks, keepBlankLinesBeforeRBrace)
} else {
val c = PsiTreeUtil.getParentOfType(b, classOf[ScTemplateDefinition])
val minLineFeeds = if (c.isInstanceOf[ScTypeDefinition]) settings.BLANK_LINES_AFTER_CLASS_HEADER
else settings.BLANK_LINES_AFTER_ANONYMOUS_CLASS_HEADER
Spacing.createSpacing(0, 0, minLineFeeds + 1, keepLineBreaks, keepBlankLinesInDeclarations)
}
case b: ScBlockExpr if b.getParent.isInstanceOf[ScFunction] =>
if (settings.KEEP_SIMPLE_METHODS_IN_ONE_LINE && !nodeTextContainsNewLine(b.getNode, fileText)) {
val spaces = if (scalaSettings.SPACES_IN_ONE_LINE_BLOCKS) 1 else 0
Spacing.createDependentLFSpacing(spaces, spaces, b.getTextRange, keepLineBreaks, keepBlankLinesBeforeRBrace)
} else {
Spacing.createSpacing(0, 0, settings.BLANK_LINES_BEFORE_METHOD_BODY + 1, keepLineBreaks, keepBlankLinesInDeclarations)
}
case b: ScBlockExpr if scalaSettings.KEEP_ONE_LINE_LAMBDAS_IN_ARG_LIST &&
!nodeTextContainsNewLine(b.getNode, fileText) && (rightPsi.isInstanceOf[ScCaseClauses] && b.getParent != null &&
b.getParent.isInstanceOf[ScArgumentExprList] || rightPsi.isInstanceOf[ScFunctionExpr]) =>
Spacing.createDependentLFSpacing(1, 1, b.getTextRange, keepLineBreaks, keepBlankLinesBeforeRBrace)
case b: ScBlockExpr if scalaSettings.SPACE_INSIDE_CLOSURE_BRACES && !nodeTextContainsNewLine(b.getNode, fileText) &&
scalaSettings.KEEP_ONE_LINE_LAMBDAS_IN_ARG_LIST && b.getParent.is[ScArgumentExprList, ScInfixExpr] =>
WITH_SPACING
case block@(_: ScPackaging | _: ScBlockExpr | _: ScMatch | _: ScCatchBlock) =>
val prev = block.getPrevSibling
if (settings.KEEP_SIMPLE_BLOCKS_IN_ONE_LINE || prev != null &&
prev.getNode.getElementType == tINTERPOLATED_STRING_INJECTION) {
val spaces = if (scalaSettings.SPACES_IN_ONE_LINE_BLOCKS) 1 else 0
Spacing.createDependentLFSpacing(spaces, spaces, block.getTextRange, keepLineBreaks, keepBlankLinesBeforeRBrace)
} else {
ON_NEW_LINE
}
case _: ScImportSelectors =>
val refRange = leftNode.getTreeParent.getTextRange
if (scalaSettings.SPACES_IN_IMPORTS) WITH_SPACING_DEPENDENT(refRange)
else WITHOUT_SPACING_DEPENDENT(refRange)
case _ =>
Spacing.createSpacing(0, 0, 0, keepLineBreaks, keepBlankLinesBeforeRBrace)
}
}
if (leftPsi.isInstanceOf[ScSelfTypeElement]) {
val c = PsiTreeUtil.getParentOfType(leftPsi, classOf[ScTemplateDefinition])
val minLineFeeds = if (c.isInstanceOf[ScTypeDefinition]) settings.BLANK_LINES_AFTER_CLASS_HEADER
else settings.BLANK_LINES_AFTER_ANONYMOUS_CLASS_HEADER
return Spacing.createSpacing(0, 0, minLineFeeds + 1, keepLineBreaks, keepBlankLinesInDeclarations)
}
// spacing between members in different scopes (class, trait(interface), local scope)
if (
leftPsi.is[ScTypeDefinition, ScFunction, ScValueOrVariable, ScTypeAlias, ScExpression] &&
!rightPsi.is[PsiComment] ||
rightPsi.is[ScTypeDefinition, ScFunction, ScValueOrVariable, ScTypeAlias]
) {
val cs = settings
val ss = scalaSettings
import BlankLinesContext._
def minBlankLinesAround(psi: PsiElement, context: BlankLinesContext): Int =
(psi, context) match {
case (_: ScFunction, Class) => cs.BLANK_LINES_AROUND_METHOD
case (_: ScFunction, Trait) => cs.BLANK_LINES_AROUND_METHOD_IN_INTERFACE
case (_: ScFunction, LocalScope) => ss.BLANK_LINES_AROUND_METHOD_IN_INNER_SCOPES
case (_: ScEnumCase, Class) => 0 // TODO: add a setting for enum cases
case (_: ScTypeDefinition, Class) => cs.BLANK_LINES_AROUND_CLASS
case (_: ScTypeDefinition, Trait) => cs.BLANK_LINES_AROUND_CLASS
case (_: ScTypeDefinition, LocalScope) => ss.BLANK_LINES_AROUND_CLASS_IN_INNER_SCOPES
case (_: ScTypeDefinition, TopLevel) => cs.BLANK_LINES_AROUND_CLASS
case (_, Class) => cs.BLANK_LINES_AROUND_FIELD
case (_, Trait) => cs.BLANK_LINES_AROUND_FIELD_IN_INTERFACE
case (_, LocalScope) => ss.BLANK_LINES_AROUND_FIELD_IN_INNER_SCOPES
case (_, TopLevel) => ss.BLANK_LINES_AROUND_FIELD_IN_INNER_SCOPES
case _ => 0
}
val context: BlankLinesContext = leftPsiParent match {
case _: ScEarlyDefinitions | _: ScTemplateBody =>
val p = PsiTreeUtil.getParentOfType(leftPsiParent, classOf[ScTemplateDefinition])
if (p.isInstanceOf[ScTrait]) Trait
else Class
case _: ScBlock =>
LocalScope
case _: ScalaFile | _: ScPackaging =>
TopLevel
case _ =>
null
}
if (context != null) {
val leftValue = minBlankLinesAround(leftPsi, context)
val rightValue = minBlankLinesAround(rightPsi, context)
val minBlankLines = math.max(leftValue, rightValue)
return Spacing.createSpacing(0, 0, minBlankLines + 1, keepLineBreaks, keepBlankLinesInDeclarations)
}
}
// ';' from left
// (semicolon before while in do/while statement shouldn't force-place while on a new line)
// kWHILE is handled later
if (leftElementType == ScalaTokenTypes.tSEMICOLON && rightElementType != kWHILE) {
def isInsideForEnumerators: Boolean = {
val rightTreeParentIsFile = rightNode.getTreeParent.getPsi.is[ScalaFile]
val rightPsiParentIsFor = rightPsiParent.getParent.is[ScFor]
!rightTreeParentIsFile && rightPsiParentIsFor
}
val result =
if (isInsideForEnumerators) {
if (settings.SPACE_AFTER_SEMICOLON) WITH_SPACING
else WITHOUT_SPACING
}
// TODO: this behaviour was implemented long ago in 2008 but it doesn't work well
// First format of one-liner `def foo = { 1; 2; }` inserts new line, making it a non-one-liner
// Second format inserts new line after `;` after it
// Looks like it only works in case clause branch: 1 match { case _ => 1; 2; 3 }
else if (rightElementType == ScalaTokenTypes.tSEMICOLON)
NO_SPACING
else if (!containsNewLine(fileText, leftNode.getTreeParent.getTextRange))
WITH_SPACING
else
getSpacing(1, 1) // add spacing even if new line is inserted in order backspace works find when merging lines
return result
}
//special else if treatment
val isElseIf = leftElementType == ScalaTokenTypes.kELSE &&
(rightPsi.isInstanceOf[ScIf] || rightElementType == ScalaTokenTypes.kIF)
if (isElseIf) {
val spacing =
if (settings.SPECIAL_ELSE_IF_TREATMENT) WITH_SPACING_NO_KEEP
else ON_NEW_LINE
return spacing
}
if (rightElementType == ScalaTokenTypes.kELSE && right.lastNode != null) {
var lastNode = left.lastNode
while (lastNode != null && lastNode.getPsi.isInstanceOf[PsiWhiteSpace])
lastNode = lastNode.getTreePrev
val spacing =
if (lastNode == null) WITH_SPACING_DEPENDENT(rightNode.getTreeParent.getTextRange)
else if (settings.ELSE_ON_NEW_LINE) ON_NEW_LINE
else if (COMMENTS_TOKEN_SET.contains(lastNode.getElementType)) ON_NEW_LINE // SCL-16831
else WITH_SPACING
return spacing
}
if (leftElementType == ScalaElementType.MODIFIERS) {
return if (rightPsi.isInstanceOf[ScParameters])
if (scalaSettings.SPACE_AFTER_MODIFIERS_CONSTRUCTOR) WITH_SPACING
else WITHOUT_SPACING
else if (settings.MODIFIER_LIST_WRAP) WITH_SPACING_DEPENDENT(leftNode.getTreeParent.getTextRange)
else WITH_SPACING
}
if (rightPsi.isInstanceOf[ScCatchBlock]) {
return if (settings.CATCH_ON_NEW_LINE) ON_NEW_LINE
else WITH_SPACING
}
if (rightPsi.isInstanceOf[ScFinallyBlock]) {
return if (settings.FINALLY_ON_NEW_LINE) ON_NEW_LINE
else WITH_SPACING
}
if (rightElementType == kWHILE) {
return if (settings.WHILE_ON_NEW_LINE) WITH_SPACING_DEPENDENT(rightPsiParent.getTextRange)
else WITH_SPACING
}
val atProcessor: PartialFunction[ASTNode, Spacing] = {
case node if node.getElementType == tAT && node.getTreeParent != null &&
node.getTreeParent.getElementType == ScalaElementType.NAMING_PATTERN =>
if (scalaSettings.SPACES_AROUND_AT_IN_PATTERNS) WITH_SPACING
else WITHOUT_SPACING
}
atProcessor.lift(rightNode) match {
case Some(spacing) =>
return spacing
case _ =>
}
atProcessor.lift(leftNode) match {
case Some(spacing) =>
return spacing
case _ =>
}
//FIXME: this is a quick hack to stop method signature in scalaDoc from getting disrupted. (#SCL-4280)
// actually the DOC_COMMENT_BAD_CHARACTER elements seem out of place in here
// also method references are now parsed incorrectly, e.g. Class#method() will contain normal reference for `Class`
// and dangling identifiers `#` and `method` and bad characters for '(' and ')'
if (leftNodeParentElementType.isInstanceOf[ScalaDocSyntaxElementType] && (
leftElementType == ScalaDocTokenType.DOC_COMMENT_BAD_CHARACTER |
rightElementType == ScalaDocTokenType.DOC_COMMENT_BAD_CHARACTER |
leftElementType == ScalaTokenTypes.tIDENTIFIER |
rightElementType == ScalaTokenTypes.tIDENTIFIER
))
return Spacing.getReadOnlySpacing
//old formatter spacing
//comments processing
if (leftPsi.isInstanceOf[ScDocComment])
return ON_NEW_LINE
if (rightPsi.isInstanceOf[ScDocComment] && leftElementType == ScalaTokenTypes.tLBRACE)
return ON_NEW_LINE
if (rightPsi.isInstanceOf[ScDocComment])
return DOUBLE_LINE
if (rightPsi.isInstanceOf[PsiComment])
return COMMON_SPACING
//extra check for `left.lastNode == null` is needed for cases when comment is first node in a larger block
//For example (notice that this method call chain uses unusual syntax, when dot is palced on previos line)
//myObject.
// /*comment*/ bar().bar()
if (leftPsi.isInstanceOf[PsiComment] && (left.lastNode == null) )
return COMMON_SPACING
//; : . and , processing
if (rightBlockString.startsWith(".") &&
rightElementType != ScalaTokenType.Float &&
rightElementType != ScalaTokenType.Double &&
!rightPsi.is[ScLiteral, ScDirectiveToken]) {
return WITHOUT_SPACING
}
if (rightBlockString.startsWith(",")) {
return if (settings.SPACE_BEFORE_COMMA) WITH_SPACING
else WITHOUT_SPACING
}
if (rightElementType == ScalaTokenTypes.tCOLON) {
val result = rightPsiParent match {
case tp: ScTypeParam =>
val tpNode = tp.nameId.getNode
val tpNodeNext = tpNode.getTreeNext
val isLeadingContextBound = tpNode.eq(leftNode)
val isLeadingContextBoundHK = tpNodeNext.eq(leftNode) &&
tpNodeNext != null && tpNodeNext.getElementType == ScalaElementType.TYPE_PARAM_CLAUSE
if (isLeadingContextBound) {
withSpacingIf(scalaSettings.SPACE_BEFORE_TYPE_PARAMETER_LEADING_CONTEXT_BOUND_COLON)
} else if (isLeadingContextBoundHK) {
withSpacingIf(scalaSettings.SPACE_BEFORE_TYPE_PARAMETER_LEADING_CONTEXT_BOUND_COLON_HK)
} else {
withSpacingIf(scalaSettings.SPACE_BEFORE_TYPE_PARAMETER_REST_CONTEXT_BOUND_COLONS)
}
case _ =>
if (scalaSettings.SPACE_BEFORE_TYPE_COLON) {
WITH_SPACING
} else {
// For operations like `var Object_!= : Symbol = _`
var left = leftNode
while (left != null && left.getLastChildNode != null) {
left = left.getLastChildNode
}
val colonCanStickToLeftIdentifier = left.getElementType == ScalaTokenTypes.tIDENTIFIER && isIdentifier(getText(left, fileText) + ":")
withSpacingIf(colonCanStickToLeftIdentifier)
}
}
return result
}
if (leftBlockString.endsWith(".")) {
return leftElementType match {
case ScalaElementType.StringLiteral |
ScalaElementType.NullLiteral |
ScalaElementType.LongLiteral |
ScalaElementType.IntegerLiteral |
ScalaElementType.DoubleLiteral |
ScalaElementType.FloatLiteral |
ScalaElementType.BooleanLiteral |
ScalaElementType.SymbolLiteral |
ScalaElementType.CharLiteral => WITH_SPACING
case _ => WITHOUT_SPACING
}
}
if (leftBlockString.endsWith(",")) {
return if (settings.SPACE_AFTER_COMMA) WITH_SPACING
else WITHOUT_SPACING
}
if (leftElementType == ScalaTokenTypes.tCOLON) {
return if (scalaSettings.SPACE_AFTER_TYPE_COLON) WITH_SPACING
else WITHOUT_SPACING
}
//processing left parenthesis (if it's from right) as Java cases
if (rightElementType == ScalaTokenTypes.tLPARENTHESIS) {
leftElementType match {
case ScalaTokenTypes.kIF =>
return if (settings.SPACE_BEFORE_IF_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
case ScalaTokenTypes.kWHILE =>
return if (settings.SPACE_BEFORE_WHILE_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
case ScalaTokenTypes.kFOR =>
return if (settings.SPACE_BEFORE_FOR_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
case _ =>
}
}
if (rightPsi.isInstanceOf[ScParameters]) {
leftPsiParent match {
case _: ScGiven =>
//add space between `given` keyword and parameters in anonymous given:
//`given (using String): String = ???`
val addSpacing = leftElementType == ScalaTokenType.GivenKeyword
return if (addSpacing) WITH_SPACING else WITHOUT_SPACING
case fun: ScFunction =>
val addSpacing = settings.SPACE_BEFORE_METHOD_PARENTHESES ||
(scalaSettings.SPACE_BEFORE_INFIX_LIKE_METHOD_PARENTHESES && ScalaNamesUtil.isOperatorName(fun.name)) ||
(scalaSettings.PRESERVE_SPACE_AFTER_METHOD_DECLARATION_NAME && rightNode.getTreePrev.getPsi.isInstanceOf[PsiWhiteSpace])
return if (addSpacing) WITH_SPACING else WITHOUT_SPACING
case _: ScExtension =>
//We could add some extring setting like we do for ScFunction, but lets not do that unless we will see the demand from users
val result =
if (leftElementType == ScalaTokenType.ExtensionKeyword) WITH_SPACING //extension (param: String)
else WITHOUT_SPACING //extension [TypeParam](param: String)
return result
case _ =>
}
}
if (rightPsi.is[ScArgumentExprList, ScPatternArgumentList] && (
leftNode.getTreeParent.getPsi.is[ScMethodCall, ScConstructorInvocation, ScGenericCall] ||
rightNode.getTreeParent.getPsi.is[ScSelfInvocation] && leftElementType == ScalaTokenTypes.kTHIS
)) {
val result =
if (settings.SPACE_BEFORE_METHOD_CALL_PARENTHESES && !rightBlockString.startsWith("{") &&
(leftNode.getLastChildNode == null || !leftNode.getLastChildNode.getPsi.isInstanceOf[ScArguments]) &&
!leftPsi.isInstanceOf[ScArguments]) WITH_SPACING
else if (scalaSettings.SPACE_BEFORE_BRACE_METHOD_CALL && rightBlockString.startsWith("{")) WITH_SPACING
else WITHOUT_SPACING
return result
}
if (rightNode.getTreeParent.getPsi.isInstanceOf[ScSelfInvocation] &&
leftNode.getTreeParent.getPsi.isInstanceOf[ScSelfInvocation] && leftPsi.isInstanceOf[ScArguments] &&
rightPsi.isInstanceOf[ScArguments]) {
return WITHOUT_SPACING
}
// SCL-2601
if (rightPsi.is[ScUnitExpr, ScTuple, ScParenthesisedExpr] && leftNode.getTreeParent.getPsi.isInstanceOf[ScInfixExpr]) {
val isOperator = leftPsi match {
case ref: ScReferenceExpression => ScalaNamesUtil.isOperatorName(ref.refName)
case _ => false
}
val result =
if (scalaSettings.SPACE_BEFORE_INFIX_METHOD_CALL_PARENTHESES || isOperator) WITH_SPACING
else if (scalaSettings.SPACE_BEFORE_INFIX_OPERATOR_LIKE_METHOD_CALL_PARENTHESES && rightPsi.is[ScParenthesisedExpr]) WITH_SPACING
else WITHOUT_SPACING
return result
}
//processing left parenthesis (if it's from right) only Scala cases
if (rightNode.getPsi.isInstanceOf[ScParameters] &&
leftNode.getTreeParent.getPsi.isInstanceOf[ScPrimaryConstructor]) {
if (settings.SPACE_BEFORE_METHOD_PARENTHESES || (scalaSettings.SPACE_BEFORE_INFIX_LIKE_METHOD_PARENTHESES &&
ScalaNamesUtil.isOperatorName(leftNode.getTreeParent.getPsi.asInstanceOf[ScPrimaryConstructor].name)) ||
(scalaSettings.PRESERVE_SPACE_AFTER_METHOD_DECLARATION_NAME &&
rightNode.getTreePrev.getPsi.isInstanceOf[PsiWhiteSpace]))
return WITH_SPACING
else return WITHOUT_SPACING
}
rightNode.getPsi match {
case _: ScPrimaryConstructor if rightBlockString.startsWith("(") =>
if (settings.SPACE_BEFORE_METHOD_PARENTHESES ||
(scalaSettings.SPACE_BEFORE_INFIX_LIKE_METHOD_PARENTHESES && ScalaNamesUtil.isOperatorName(leftBlockString)) ||
(scalaSettings.PRESERVE_SPACE_AFTER_METHOD_DECLARATION_NAME &&
rightNode.getTreePrev.getPsi.isInstanceOf[PsiWhiteSpace]))
return WITH_SPACING
else return WITHOUT_SPACING
case _: ScPrimaryConstructor =>
return WITH_SPACING
case _ =>
}
if (leftPsi.is[ScParameterClause, ScTypeParamClause] && rightPsi.is[ScParameterClause, ScTypeParamClause]) {
return WITHOUT_SPACING
}
if (rightPsi.isInstanceOf[ScPatternArgumentList] &&
rightNode.getTreeParent.getPsi.isInstanceOf[ScConstructorPattern]) {
return if (settings.SPACE_BEFORE_METHOD_CALL_PARENTHESES) WITH_SPACING
else WITHOUT_SPACING
}
//processing left parenthesis (if it's from left)
if (leftElementType == ScalaTokenTypes.tLPARENTHESIS) {
if (rightElementType == ScalaTokenTypes.tRPARENTHESIS)
return WITHOUT_SPACING
else leftNode.getTreeParent.getPsi match {
case _: ScFor if left.isLeaf =>
if (settings.SPACE_WITHIN_FOR_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScIf =>
if (settings.SPACE_WITHIN_IF_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScWhile | _: ScDo =>
if (settings.SPACE_WITHIN_WHILE_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScParenthesisedExpr =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case x: ScParameterClause if x.getParent.getParent.isInstanceOf[ScFunction] =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case x: ScParameterClause if x.getParent.getParent.isInstanceOf[ScPrimaryConstructor] =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScPatternArgumentList =>
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScArguments =>
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScParenthesisedPattern =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScTuplePattern =>
WITHOUT_SPACING //todo: add setting
case _: ScParenthesisedTypeElement =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScTupleTypeElement =>
WITHOUT_SPACING //todo: add setting
case _: ScTuple =>
WITHOUT_SPACING //todo: add setting
case _: ScFunctionalTypeElement =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _ =>
}
}
//processing right parenthesis (if it's from right)
if (rightElementType == ScalaTokenTypes.tRPARENTHESIS) {
if (leftElementType == ScalaTokenTypes.tLPARENTHESIS)
return WITHOUT_SPACING
rightNode.getTreeParent.getPsi match {
case _: ScFor =>
if (settings.SPACE_WITHIN_FOR_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScIf =>
if (settings.SPACE_WITHIN_IF_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScWhile | _: ScDo =>
if (settings.SPACE_WITHIN_WHILE_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScParenthesisedExpr =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case x: ScParameterClause if x.getParent.getParent.isInstanceOf[ScFunction] =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case x: ScParameterClause if x.getParent.getParent.isInstanceOf[ScPrimaryConstructor] =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScPatternArgumentList =>
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScArguments =>
if (settings.SPACE_WITHIN_METHOD_CALL_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScParenthesisedPattern =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScTuplePattern =>
WITHOUT_SPACING //todo: add setting
case _: ScParenthesisedTypeElement =>
if (settings.SPACE_WITHIN_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _: ScTupleTypeElement =>
WITHOUT_SPACING //todo: add setting
case _: ScTuple =>
WITHOUT_SPACING //todo: add setting
case _: ScFunctionalTypeElement =>
if (settings.SPACE_WITHIN_METHOD_PARENTHESES) return WITH_SPACING
else return WITHOUT_SPACING
case _ =>
}
}
//processing square brackets "[]"
if (leftElementType == ScalaTokenTypes.tLSQBRACKET) {
val result =
if (rightElementType == ScalaTokenTypes.tRSQBRACKET)
WITHOUT_SPACING
else if (settings.SPACE_WITHIN_BRACKETS)
WITH_SPACING
else
WITHOUT_SPACING
return result
}
rightElementType match {
case ScalaTokenTypes.tRSQBRACKET =>
return if (settings.SPACE_WITHIN_BRACKETS) WITH_SPACING else WITHOUT_SPACING
case ScalaElementType.TYPE_PARAM_CLAUSE =>
val result =
//add space after `given` in anonymous given alias with type parameter:
//given [T](using T): String = ??? //alias
//given [T](using Int, Short): Ordering[Byte] with {} //definition
if (leftPsiParent.is[ScGiven] && leftElementType == ScalaTokenType.GivenKeyword) WITH_SPACING
//"extension [T]..."s
else if (leftNodeParentElementType == EXTENSION) WITH_SPACING
else if (scalaSettings.SPACE_BEFORE_TYPE_PARAMETER_IN_DEF_LIST) WITH_SPACING
else WITHOUT_SPACING
return result
case ScalaElementType.TYPE_ARGS =>
return if (settings.SPACE_BEFORE_TYPE_PARAMETER_LIST) WITH_SPACING else WITHOUT_SPACING
case ScalaElementType.TYPE_LAMBDA =>
return COMMON_SPACING
case _ =>
//continue
}
//special for "case <caret> =>" (for SurroundWith)
if (leftElementType == ScalaTokenTypes.kCASE && rightElementType == ScalaTokenTypes.tFUNTYPE) {
return Spacing.createSpacing(2, 2, 0, false, 0)
}
//`case _ => <caret> ...multiline body...`
if (scalaSettings.NEW_LINE_AFTER_CASE_CLAUSE_ARROW_WHEN_MULTILINE_BODY) {
val isArrowBeforeMultilineCaseClauseBody = leftElementType == ScalaTokenTypes.tFUNTYPE &&
leftNodeParentElementType == ScalaElementType.CASE_CLAUSE &&
rightElementType == ScalaElementType.BLOCK &&
nodeTextContainsNewLine(rightNode, fileText)
if (isArrowBeforeMultilineCaseClauseBody) {
return ON_NEW_LINE
}
}
//Case Clauses case
if (leftElementType == ScalaElementType.CASE_CLAUSE && rightElementType == ScalaElementType.CASE_CLAUSE) {
return WITH_SPACING_DEPENDENT(leftNode.getTreeParent.getTreeParent.getTextRange)
}
(leftElementType, rightElementType, leftNodeParentElementType, rightNodeParentElementType) match {
case (ScalaTokenTypes.tFUNTYPE, ScalaElementType.BLOCK, ScalaElementType.FUNCTION_EXPR, _)
if !scalaSettings.PLACE_CLOSURE_PARAMETERS_ON_NEW_LINE =>
if (rightBlockString.startsWith("{")) WITH_SPACING
else if (containsNewLine(fileText, leftNode.getTreeParent.getTextRange)) ON_NEW_LINE
else WITH_SPACING
//type with annotation (val x: String @unchecked)
case (_, ScalaElementType.ANNOTATIONS, ScalaElementType.ANNOT_TYPE, _) =>
WITH_SPACING
//case for package statement
case (ScalaElementType.REFERENCE, ret, _, _) if ret != ScalaElementType.PACKAGING &&
leftNode.getTreePrev != null && leftNode.getTreePrev.getTreePrev != null &&
leftNode.getTreePrev.getTreePrev.getElementType == ScalaTokenTypes.kPACKAGE => DOUBLE_LINE
case (ScalaElementType.REFERENCE, ScalaElementType.PACKAGING, _, _) if leftNode.getTreePrev != null &&
leftNode.getTreePrev.getTreePrev != null &&
leftNode.getTreePrev.getTreePrev.getElementType == ScalaTokenTypes.kPACKAGE => ON_NEW_LINE
//case for covariant or contrvariant type params
case (ScalaTokenTypes.tIDENTIFIER, ScalaTokenTypes.tIDENTIFIER | ScalaTokenTypes.tUNDER, ScalaElementType.TYPE_PARAM, ScalaElementType.TYPE_PARAM) =>
NO_SPACING
//class params
case (ScalaTokenTypes.tIDENTIFIER | ScalaElementType.TYPE_PARAM_CLAUSE, ScalaElementType.PRIMARY_CONSTRUCTOR, _, _)
if rightPsi.asInstanceOf[ScPrimaryConstructor].annotations.isEmpty &&
!rightPsi.asInstanceOf[ScPrimaryConstructor].hasModifier => NO_SPACING
//Type*
case (_, ScalaTokenTypes.tIDENTIFIER, _, ScalaElementType.PARAM_TYPE) if rightBlockString == "*" => NO_SPACING
//Parameters
case (ScalaTokenTypes.tIDENTIFIER, ScalaElementType.PARAM_CLAUSES, _, _) => NO_SPACING
case (_, ScalaElementType.TYPE_ARGS, _, ScalaElementType.TYPE_GENERIC_CALL | ScalaElementType.GENERIC_CALL) =>
NO_SPACING
case (_, ScalaElementType.PATTERN_ARGS, _, ScalaElementType.CONSTRUCTOR_PATTERN) => NO_SPACING
//Annotation
case (ScalaTokenTypes.tAT, _, _, _) if rightPsi.isInstanceOf[ScXmlPattern] => WITH_SPACING
case (ScalaTokenTypes.tAT, _, _, _) => NO_SPACING
case (ScalaTokenTypes.tIDENTIFIER, ScalaTokenTypes.tAT, ScalaElementType.NAMING_PATTERN, _) => NO_SPACING
// Scala named seq-pattern: case Seq(other*) =>
case (ScalaTokenTypes.tIDENTIFIER, ScalaTokenTypes.tIDENTIFIER, ScalaElementType.SEQ_WILDCARD_PATTERN, _) if rightBlockString == "*" => NO_SPACING
case (_, ScalaTokenTypes.tAT, _, _) => NO_SPACING_WITH_NEWLINE
case (ScalaElementType.ANNOTATION, _, _, _) => COMMON_SPACING
//Prefix Identifier
case (ScalaElementType.REFERENCE_EXPRESSION |
ScalaTokenTypes.tIDENTIFIER, _,
ScalaElementType.StringLiteral |
ScalaElementType.NullLiteral |
ScalaElementType.LongLiteral |
ScalaElementType.IntegerLiteral |
ScalaElementType.DoubleLiteral |
ScalaElementType.FloatLiteral |
ScalaElementType.BooleanLiteral |
ScalaElementType.SymbolLiteral |
ScalaElementType.CharLiteral |
ScalaElementType.PREFIX_EXPR, _) => NO_SPACING
//Braces
case (ScalaTokenTypes.tLBRACE, ScalaTokenTypes.tRBRACE, _, _) => NO_SPACING
case (ScalaTokenTypes.tLBRACE, _, ScalaElementType.TEMPLATE_BODY |
ScalaElementType.MATCH_STMT |
ScalaElementType.REFINEMENT |
ScalaElementType.EXISTENTIAL_CLAUSE |
ScCodeBlockElementType.BlockExpression, _) => IMPORT_BETWEEN_SPACING
case (ScalaTokenTypes.tLBRACE, _, _, _) => NO_SPACING_WITH_NEWLINE
case (_, ScalaTokenTypes.tRBRACE, ScalaElementType.TEMPLATE_BODY |
ScalaElementType.MATCH_STMT |
ScalaElementType.REFINEMENT |
ScalaElementType.EXISTENTIAL_CLAUSE |
ScCodeBlockElementType.BlockExpression, _) => IMPORT_BETWEEN_SPACING
case (_, ScalaTokenTypes.tRBRACE, _, _) => NO_SPACING_WITH_NEWLINE
//Imports
case (ImportStatement, ImportStatement, _, _) => IMPORT_BETWEEN_SPACING
case (ImportStatement, _, _: ScStubFileElementType, _) => DOUBLE_LINE
case (ImportStatement, _, ScalaElementType.PACKAGING, _) => DOUBLE_LINE
case (ImportStatement, _, _, _) => IMPORT_BETWEEN_SPACING
//Dot
case (ScalaTokenTypes.tDOT, _, _, _) => NO_SPACING_WITH_NEWLINE
case (_, ScalaTokenTypes.tDOT, _, _) => NO_SPACING
//Comma
case (ScalaTokenTypes.tCOMMA, _, _, _) => COMMON_SPACING
case (_, ScalaTokenTypes.tCOMMA, _, _) => NO_SPACING
//Parenthesises and Brackets
case (ScalaTokenTypes.tLPARENTHESIS | ScalaTokenTypes.tLSQBRACKET, _, _, _) => NO_SPACING_WITH_NEWLINE
case (_, ScalaTokenTypes.tLSQBRACKET, _, _) => NO_SPACING
case (_, ScalaTokenTypes.tLPARENTHESIS, ScalaElementType.CONSTRUCTOR_PATTERN, _) => NO_SPACING
case (ScalaTokenTypes.tRPARENTHESIS | ScalaTokenTypes.tRSQBRACKET, _, _, _) => COMMON_SPACING
case (_, ScalaTokenTypes.tRPARENTHESIS | ScalaTokenTypes.tRSQBRACKET, _, _) => NO_SPACING_WITH_NEWLINE
//Case clauses
case (ScalaElementType.CASE_CLAUSE, _, _, _) =>
IMPORT_BETWEEN_SPACING
case (_, ScalaElementType.CASE_CLAUSE, _, _) =>
// support for Scala3 single `case clause` on the same line with `catch`:
// `try foo() catch case ex: Exception => println(42)`
val isScala3_OnlyCaseClause = rightNode.getTreeNext == null && leftElementType == ScalaTokenTypes.kCATCH
if (isScala3_OnlyCaseClause) COMMON_SPACING
else IMPORT_BETWEEN_SPACING
//#
case (ScalaTokenTypes.tINNER_CLASS, _, _, _) => NO_SPACING
case (ScalaTokenTypes.tUNDER, ScalaTokenTypes.tIDENTIFIER, _, _) =>
leftPsi.getNextSibling match {
case _: PsiWhiteSpace => COMMON_SPACING
case _ => NO_SPACING
}
case (_, ScalaTokenTypes.tINNER_CLASS, _, _) => NO_SPACING
case (ScalaElementType.ANNOTATIONS, ScalaTokenTypes.kDEF, _, _) if scalaSettings.NEWLINE_AFTER_ANNOTATIONS =>
ON_NEW_LINE
//Other cases
case (ScalaTokenTypes.tSTUB, _, _, _) |
(_, ScalaTokenTypes.tSTUB, _, _) => NO_SPACING_WITH_NEWLINE
case _ =>
COMMON_SPACING
}
}