diff --git a/CHANGELOG b/CHANGELOG index 036978b5..2a325eb1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,7 @@ +0.2.5 (27/October/17) + +* Added support for single case pattern without forced newline (fixes #29) + 0.2.4 (25/October/17) * Updated to Scala 2.12.4 diff --git a/README.rst b/README.rst index c526e8e4..467e4c81 100644 --- a/README.rst +++ b/README.rst @@ -52,7 +52,7 @@ Usage within a project Have a use for the scalariform source code directly? You can use it as a build dependency: :: - "org.scalariform" %% "scalariform" % "0.2.4" + "org.scalariform" %% "scalariform" % "0.2.5" Integration with Eclipse ------------------------ @@ -765,6 +765,20 @@ is formatted as: case 1 ⇒ println("odd") } +singleCasePatternOnNewline +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Default: ``true`` + +When ``singleCasePatternOnNewline`` is ``false`` the default behavior of forcing +a single case pattern onto a newline is disabled. This allows for the following formatting style: + +.. code:: scala + + items.map { case (key, value) => + (key, transform(value)) + } + spaceBeforeColon ~~~~~~~~~~~~~~~~ diff --git a/formatterPreferences.properties b/formatterPreferences.properties index 14e08ba7..22eec8b2 100644 --- a/formatterPreferences.properties +++ b/formatterPreferences.properties @@ -21,6 +21,7 @@ danglingCloseParenthesis=Force #placeScaladocAsterisksBeneathSecondAsterisk=false #preserveSpaceBeforeArguments=false #rewriteArrowSymbols=false +#singleCasePatternOnNewline=true #spaceBeforeColon=false #spaceBeforeContextColon=false #spaceInsideBrackets=false diff --git a/pom.xml b/pom.xml index 0d600a86..18d396fe 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.scalariform scalariform.parent - 0.2.4 + 0.2.5 pom diff --git a/scalariform.feature/feature.xml b/scalariform.feature/feature.xml index 10196cf1..35a67875 100644 --- a/scalariform.feature/feature.xml +++ b/scalariform.feature/feature.xml @@ -2,7 +2,7 @@ + version="0.2.5"> Scala Code formatter diff --git a/scalariform.feature/pom.xml b/scalariform.feature/pom.xml index 525884ae..8deb9e33 100644 --- a/scalariform.feature/pom.xml +++ b/scalariform.feature/pom.xml @@ -8,6 +8,6 @@ scalariform.parent org.scalariform - 0.2.4 + 0.2.5 diff --git a/scalariform.update/pom.xml b/scalariform.update/pom.xml index 8918266b..f7b0a74b 100644 --- a/scalariform.update/pom.xml +++ b/scalariform.update/pom.xml @@ -8,6 +8,6 @@ scalariform.parent org.scalariform - 0.2.4 + 0.2.5 diff --git a/scalariform.update/site.xml b/scalariform.update/site.xml index 7d366693..128e251e 100644 --- a/scalariform.update/site.xml +++ b/scalariform.update/site.xml @@ -1,10 +1,10 @@ + url="https://github.com/scala-ide/scalariform/tree/0.2.5/scalariform.update/target/site"> Scalariform Update Site - + diff --git a/scalariform.update/target/site.zip b/scalariform.update/target/site.zip index fc9707f0..da6256a9 100644 Binary files a/scalariform.update/target/site.zip and b/scalariform.update/target/site.zip differ diff --git a/scalariform.update/target/site/artifacts.jar b/scalariform.update/target/site/artifacts.jar index 2260a6a5..3bc24044 100644 Binary files a/scalariform.update/target/site/artifacts.jar and b/scalariform.update/target/site/artifacts.jar differ diff --git a/scalariform.update/target/site/content.jar b/scalariform.update/target/site/content.jar index 27a50674..5d2c0b5a 100644 Binary files a/scalariform.update/target/site/content.jar and b/scalariform.update/target/site/content.jar differ diff --git a/scalariform.update/target/site/features/scalariform.feature_0.2.4.jar b/scalariform.update/target/site/features/scalariform.feature_0.2.4.jar deleted file mode 100644 index a12450a7..00000000 Binary files a/scalariform.update/target/site/features/scalariform.feature_0.2.4.jar and /dev/null differ diff --git a/scalariform.update/target/site/features/scalariform.feature_0.2.5.jar b/scalariform.update/target/site/features/scalariform.feature_0.2.5.jar new file mode 100644 index 00000000..c8ce62f4 Binary files /dev/null and b/scalariform.update/target/site/features/scalariform.feature_0.2.5.jar differ diff --git a/scalariform.update/target/site/plugins/scalariform_0.2.4.jar b/scalariform.update/target/site/plugins/scalariform_0.2.5.jar similarity index 87% rename from scalariform.update/target/site/plugins/scalariform_0.2.4.jar rename to scalariform.update/target/site/plugins/scalariform_0.2.5.jar index bfb9c244..26c473e4 100644 Binary files a/scalariform.update/target/site/plugins/scalariform_0.2.4.jar and b/scalariform.update/target/site/plugins/scalariform_0.2.5.jar differ diff --git a/scalariform.update/target/site/site.xml b/scalariform.update/target/site/site.xml index 7d366693..128e251e 100644 --- a/scalariform.update/target/site/site.xml +++ b/scalariform.update/target/site/site.xml @@ -1,10 +1,10 @@ + url="https://github.com/scala-ide/scalariform/tree/0.2.5/scalariform.update/target/site"> Scalariform Update Site - + diff --git a/scalariform.update/target/site_assembly.zip b/scalariform.update/target/site_assembly.zip index cde75b80..dd1e9f36 100644 Binary files a/scalariform.update/target/site_assembly.zip and b/scalariform.update/target/site_assembly.zip differ diff --git a/scalariform/META-INF/MANIFEST.MF b/scalariform/META-INF/MANIFEST.MF index 8cd0d743..ae9211db 100644 --- a/scalariform/META-INF/MANIFEST.MF +++ b/scalariform/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Scalariform Bundle-SymbolicName: scalariform -Bundle-Version: 0.2.4 +Bundle-Version: 0.2.5 Require-Bundle: org.scala-lang.scala-library, org.scala-lang.modules.scala-xml Bundle-ClassPath: . diff --git a/scalariform/pom.xml b/scalariform/pom.xml index 94534787..71cc1009 100644 --- a/scalariform/pom.xml +++ b/scalariform/pom.xml @@ -8,7 +8,7 @@ scalariform.parent org.scalariform - 0.2.4 + 0.2.5 diff --git a/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala index b0c56fcb..36cf1407 100644 --- a/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala @@ -18,6 +18,7 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi var formatResult: FormatResult = NoFormatResult var isFirstCaseClause = true + val hasSingleCaseClause = clauseGroups.size == 1 // We have to decide whether to indent the hidden tokens before the CASE token (or possibly a preceding // NEWLINE token from a prior case block). @@ -35,7 +36,7 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi def formatSingleCaseClause(caseClause: CaseClause) { handleCaseIndent(caseClause) - formatResult ++= formatCaseClause(caseClause) + formatResult ++= formatCaseClause(caseClause, None, hasSingleCaseClause) isFirstCaseClause = false } @@ -46,7 +47,9 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi for (caseClause @ CaseClause(casePattern, statSeq) ← caseClauses) { handleCaseIndent(caseClause) val arrowInstruction = PlaceAtColumn(formatterState.indentLevel, largestCasePatternLength + 1) - formatResult ++= formatCaseClause(caseClause, Some(arrowInstruction)) + formatResult ++= formatCaseClause( + caseClause, Some(arrowInstruction), hasSingleCaseClause + ) isFirstCaseClause = false } } else { @@ -104,22 +107,55 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi formatResult } - private def formatCaseClause(caseClause: CaseClause, arrowInstructionOpt: Option[PlaceAtColumn] = None)(implicit formatterState: FormatterState): FormatResult = { + private def formatCaseClause( + caseClause: CaseClause, + arrowInstructionOpt: Option[PlaceAtColumn], + hasSingleCaseClause: Boolean + )(implicit formatterState: FormatterState): FormatResult = { + val CaseClause(casePattern, statSeq) = caseClause var formatResult: FormatResult = NoFormatResult formatResult ++= formatCasePattern(casePattern, arrowInstructionOpt) + val hasNewline = caseClause.casePattern.caseToken.associatedWhitespaceAndComments.containsNewline + val singleCaseWithoutNewline = ( + hasSingleCaseClause && !hasNewline && !formattingPreferences(SingleCasePatternOnNewline) + ) val singleExpr = cond(statSeq.firstStatOpt) { case Some(Expr(_)) ⇒ true } && cond(statSeq.otherStats) { case Nil | List((_, None)) ⇒ true } val indentBlock = statSeq.firstTokenOption.isDefined && newlineBefore(statSeq) || containsNewline(statSeq) && !singleExpr - if (indentBlock) - formatResult = formatResult.before(statSeq.firstToken, formatterState.nextIndentLevelInstruction) - val stateForStatSeq = if (singleExpr && !indentBlock) formatterState else formatterState.indent - formatResult ++= format(statSeq)(stateForStatSeq) + def unindent(x: Map[Token, IntertokenFormatInstruction]) = x.map { + case (k, v @ EnsureNewlineAndIndent(indentLevel, relativeTo)) => + k -> EnsureNewlineAndIndent(indentLevel - 1, relativeTo) + case x => x + } + + if (indentBlock) { + val result = formatResult.before(statSeq.firstToken, formatterState.nextIndentLevelInstruction) + formatResult = + if(!singleCaseWithoutNewline) result + else + result.copy( + predecessorFormatting = + unindent(result.predecessorFormatting) + ( // unindent first token in case body + caseClause.casePattern.caseToken -> CompactEnsuringGap // remove `case` leading newline + ) + ) + } + val stateForStatSeq = if (singleExpr && !indentBlock) formatterState else formatterState.indent + formatResult ++= { + val result = format(statSeq)(stateForStatSeq) + if(!singleCaseWithoutNewline) result + else + result.copy( // unindent body tokens + predecessorFormatting = unindent(result.predecessorFormatting), + inferredNewlineFormatting = unindent(result.inferredNewlineFormatting) + ) + } formatResult } diff --git a/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala b/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala index e307171c..7a52bb4d 100755 --- a/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala +++ b/scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala @@ -91,26 +91,6 @@ abstract class ScalaFormatter var suspendFormatting = false var edits: List[TextEdit] = Nil // Stored in reverse - def printableFormattingInstruction(previousTokenOption: Option[Token], token: Token, nextTokenOption: Option[Token]) = { - val maybePredecessorFormatting = predecessorFormatting.get(token) - val isGaplessAssignment = - // avoid `foreach(_.id= ..)` and `foreach(foo= _)` gapless assignment (see MutateTest.scala) - maybePredecessorFormatting exists { - case x @ PlaceAtColumn(_, _, Some(Token(USCORE, _, _, _))) => token.tokenType == EQUALS - case _ => token.tokenType == EQUALS && nextTokenOption.exists(_.tokenType == USCORE) - } - val maybeInstruction = - if (isGaplessAssignment) Some(CompactEnsuringGap) - else - maybePredecessorFormatting.orElse( - previousTokenOption.map(defaultFormattingInstruction(_, token)) - ) - maybeInstruction.getOrElse( - if (token.tokenType == EOF) EnsureNewlineAndIndent(0) /* <-- to allow formatting of files with just a scaladoc comment */ - else Compact - ) - } - for ((previousTokenOption, token, nextTokenOption) ← Utils.withPreviousAndNext(tokens)) { val previousTokenIsPrintable = previousTokenOption exists { !isInferredNewline(_) } if (isInferredNewline(token)) { @@ -130,7 +110,9 @@ abstract class ScalaFormatter basicFormattingInstruction val nextTokenUnindents = nextTokenOption exists { _.tokenType == RBRACE } val includeBufferBeforeNextToken = nextTokenOption exists { nextToken ⇒ - !printableFormattingInstruction(Some(token), nextToken, None).isInstanceOf[EnsureNewlineAndIndent] + !printableFormattingInstruction( + Some(token), nextToken, None, predecessorFormatting + ).isInstanceOf[EnsureNewlineAndIndent] } edits :::= writeHiddenTokens(builder, inferredNewlines(token), formattingInstruction, nextTokenUnindents, includeBufferBeforeNextToken, previousTokenIsPrintable, tokenIndentMap).toList @@ -142,7 +124,10 @@ abstract class ScalaFormatter tokenIndentMap += (token -> builder.currentColumn) builder.append(token.rawText) } else { - val formattingInstruction = printableFormattingInstruction(previousTokenOption, token, nextTokenOption) + val formattingInstruction = + printableFormattingInstruction( + previousTokenOption, token, nextTokenOption, predecessorFormatting + ) val nextTokenUnindents = token.tokenType == RBRACE val includeBufferBeforeNextToken = true // <-- i.e. current token val hiddenTokens = hiddenPredecessors(token) @@ -444,6 +429,31 @@ abstract class ScalaFormatter result } + private def printableFormattingInstruction( + previousTokenOption: Option[Token], + token: Token, + nextTokenOption: Option[Token], + predecessorFormatting: Map[Token, IntertokenFormatInstruction]): IntertokenFormatInstruction = { + + val maybePredecessorFormatting = predecessorFormatting.get(token) + val isGaplessAssignment = + maybePredecessorFormatting match { + // `foreach(_.id= ..)` + case x @ Some(PlaceAtColumn(_, _, Some(Token(USCORE, _, _, _)))) => token.tokenType == EQUALS + // `foreach(foo= _)` + case _ => token.tokenType == EQUALS && nextTokenOption.exists(_.tokenType == USCORE) + } + val maybeInstruction = + if(isGaplessAssignment) Some(CompactEnsuringGap) + else maybePredecessorFormatting.orElse( + previousTokenOption.map(defaultFormattingInstruction(_, token)) + ) + maybeInstruction.getOrElse( + if (token.tokenType == EOF) EnsureNewlineAndIndent(0) /* <-- to allow formatting of files with just a scaladoc comment */ + else Compact + ) + } + private def defaultFormattingInstruction(token1: Token, token2: Token): IntertokenFormatInstruction = { val result = actualDefaultFormattingInstruction(token1, token2) // println("defaultFormattingInstruction(" + token1 + ", " + token2 + ") = " + result) diff --git a/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala b/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala index 1f36591c..271e4f18 100755 --- a/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala +++ b/scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala @@ -93,9 +93,10 @@ object AllPreferences { AlignArguments, AlignParameters, AlignSingleLineCaseStatements, AlignSingleLineCaseStatements.MaxArrowIndent, AllowParamGroupsOnNewlines, CompactControlReadability, CompactStringConcatenation, DanglingCloseParenthesis, DoubleIndentClassDeclaration, DoubleIndentConstructorArguments, DoubleIndentMethodDeclaration, FirstArgumentOnNewline, - FirstParameterOnNewline, FormatXml, IndentLocalDefs, IndentPackageBlocks, IndentSpaces, IndentWithTabs, MultilineScaladocCommentsStartOnFirstLine, - NewlineAtEndOfFile, PlaceScaladocAsterisksBeneathSecondAsterisk, PreserveSpaceBeforeArguments, RewriteArrowSymbols, - SpaceBeforeColon, SpaceBeforeContextColon, SpaceInsideBrackets, SpaceInsideParentheses, SpacesAroundMultiImports, SpacesWithinPatternBinders + FirstParameterOnNewline, FormatXml, IndentLocalDefs, IndentPackageBlocks, IndentSpaces, IndentWithTabs, + MultilineScaladocCommentsStartOnFirstLine, NewlineAtEndOfFile, PlaceScaladocAsterisksBeneathSecondAsterisk, + PreserveSpaceBeforeArguments, RewriteArrowSymbols, SingleCasePatternOnNewline, SpaceBeforeColon, + SpaceBeforeContextColon, SpaceInsideBrackets, SpaceInsideParentheses, SpacesAroundMultiImports, SpacesWithinPatternBinders ) val preferencesByKey: Map[String, PreferenceDescriptor[_]] = @@ -245,6 +246,12 @@ case object RewriteArrowSymbols extends BooleanPreferenceDescriptor { val defaultValue = false } +case object SingleCasePatternOnNewline extends BooleanPreferenceDescriptor { + val key = "singleCasePatternOnNewline" + val description = "Single case in pattern match block on a new line" + val defaultValue = true +} + case object SpaceBeforeColon extends BooleanPreferenceDescriptor { val key = "spaceBeforeColon" val description = "Space before colons" diff --git a/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala b/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala index f59fa5c7..731352dc 100644 --- a/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala +++ b/scalariform/src/test/scala/scalariform/formatter/CaseClausesFormatterTest.scala @@ -84,6 +84,48 @@ class CaseClausesFormatterTest extends AbstractExpressionFormatterTest { | d |}""" + { + implicit val formattingPreferences = FormattingPreferences.setPreference( + SingleCasePatternOnNewline, false + ) + """a match { case a => b; c; + |d }""" ==> + """a match { case a => + | b; c; + | d + |}""" + + """a match { case b => + |val c = d + |case e => + |}""" ==> + """a match { + | case b => + | val c = d + | case e => + |}""" + + """list.foreach { case (key, value) => + |val boo = 1 + |boo + |}""" ==> + """list.foreach { case (key, value) => + | val boo = 1 + | boo + |}""" + + """list.foreach { + |case (key, value) => + |val boo = 1 + |boo + |}""" ==> + """list.foreach { + | case (key, value) => + | val boo = 1 + | boo + |}""" + } + "a match { case b => ; c }" ==> "a match { case b => ; c }" // See issue #60 diff --git a/version.sbt b/version.sbt index a8aa224f..39eecbcb 100644 --- a/version.sbt +++ b/version.sbt @@ -1 +1 @@ -version in ThisBuild := "0.2.5-SNAPSHOT" +version in ThisBuild := "0.2.5"