Permalink
Browse files

Add AlignSingleLineCaseStatements preference to align the arrows of c…

…onsecutive single-line case statements
  • Loading branch information...
1 parent 6349696 commit 4831dee16c5e3c72fb095a3248ac8a60fde5ca92 @mdr mdr committed Jan 20, 2011
View
@@ -3,6 +3,7 @@
* FIX: Correctly handle use of +/- as type parameters in defs
* Add Maven formatter plugin (contributed by Adam Crain -- https://github.com/jadamcrain)
* FIX: Bug with lexing """ at end of text
+* Add AlignSingleLineCaseStatements preference to align the arrows of consecutive single-line case statements
0.0.7 (18/October/10)
View
@@ -127,6 +127,7 @@ Usage::
Preferences:
[+|-]alignParameters Enable/disable Align parameters on different lines in the same column
+ [+|-]alignSingleLineCaseStatements Enable/disable Align the arrows of consecutive single-line case statements
[+|-]compactStringConcatenation Enable/disable Omit spaces when formatting a '+' operator on String literals
[+|-]doubleIndentClassDeclaration Enable/disable Double indent either a class's parameters or its inheritance list
[+|-]formatXml Enable/disable Format XML literals
@@ -191,6 +192,27 @@ If ``true``, then::
shoeSize: Int,
favoriteColor: java.awt.Color)
+alignSingleLineCaseStatements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``false``
+
+Align the arrows of consecutive single-line case statements. For example, if ``true``, then::
+
+ a match {
+ case b => 1
+ case ccc => 2
+ case dd => 3
+ }
+
+Is reformatted as::
+
+ a match {
+ case b => 1
+ case ccc => 2
+ case dd => 3
+ }
+
compactStringConcatenation
~~~~~~~~~~~~~~~~~~~~~~~~~~
View
@@ -61,6 +61,33 @@ See `ScalaSidekick`_ by Stefan Ettrup:
Run Plugins -> scalaSidekickPlugin -> Format Scala File
+Integration with Maven
+----------------------
+
+There is a Maven plugin to run Scalariform contributed by `Adam
+Crain`_. It is not yet on scala-tools, but you can build it from the
+source.
+
+.. _Adam Crain: https://github.com/jadamcrain
+
+Usage::
+
+ <plugin>
+ <groupId>org.scalariform</groupId>
+ <artifactId>scalariform-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>format</goal>
+ </goals>
+ <configuration>
+ <rewriteArrowSymbols>true</rewriteArrowSymbols>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
Integration with sbt
--------------------
@@ -100,6 +127,7 @@ Usage::
Preferences:
[+|-]alignParameters Enable/disable Align parameters on different lines in the same column
+ [+|-]alignSingleLineCaseStatements Enable/disable Align the arrows of consecutive single-line case statements
[+|-]compactStringConcatenation Enable/disable Omit spaces when formatting a '+' operator on String literals
[+|-]doubleIndentClassDeclaration Enable/disable Double indent either a class's parameters or its inheritance list
[+|-]formatXml Enable/disable Format XML literals
@@ -164,6 +192,27 @@ If ``true``, then::
shoeSize: Int,
favoriteColor: java.awt.Color)
+alignSingleLineCaseStatements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Default: ``false``
+
+Align the arrows of consecutive single-line case statements. For example, if ``true``, then::
+
+ a match {
+ case b => 1
+ case ccc => 2
+ case dd => 3
+ }
+
+Is reformatted as::
+
+ a match {
+ case b => 1
+ case ccc => 2
+ case dd => 3
+ }
+
compactStringConcatenation
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -54,7 +54,7 @@
<dependency>
<groupId>org.scalariform</groupId>
<artifactId>scalariform_2.8.0</artifactId>
- <version>0.0.7</version>
+ <version>0.0.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
@@ -34,6 +34,11 @@
/**
* @parameter default-value=false
*/
+ protected boolean alignSingleLineCaseStatements;
+
+ /**
+ * @parameter default-value=false
+ */
protected boolean compactStringConcatenation;
/**
@@ -76,6 +81,7 @@ public void execute() throws MojoExecutionException {
MojoFormatter.format(baseDir, this.getLog(),
alignParameters,
+ alignSingleLineCaseStatements,
compactStringConcatenation,
doubleIndentClassDeclaration,
formatXml,
@@ -43,6 +43,7 @@ object MojoFormatter {
def format(path: String,
log: Log,
alignParameters: Boolean,
+ alignSingleLineCaseStatements: Boolean,
compactStringConcatenation: Boolean,
doubleIndentClassDeclaration: Boolean,
formatXml: Boolean,
@@ -54,6 +55,7 @@ object MojoFormatter {
val preferences = FormattingPreferences()
.setPreference(AlignParameters, alignParameters)
+ .setPreference(AlignSingleLineCaseStatements, alignSingleLineCaseStatements)
.setPreference(DoubleIndentClassDeclaration, doubleIndentClassDeclaration)
.setPreference(CompactStringConcatenation, compactStringConcatenation)
.setPreference(FormatXml, formatXml)
@@ -0,0 +1,103 @@
+package scalariform.formatter
+
+import scalariform.lexer.Token
+import scalariform.lexer.Tokens._
+import scalariform.parser._
+import scalariform.utils.Utils
+import scalariform.utils.TextEditProcessor
+import scalariform.utils.BooleanLang._
+import scalariform.formatter.preferences._
+import PartialFunction._
+
+trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter with HasHiddenTokenInfo with ScalaFormatter
+
+ def format(caseClauses: CaseClauses)(implicit formatterState: FormatterState): FormatResult = {
+ val clauseGroups = if (formattingPreferences(AlignSingleLineCaseStatements))
+ groupClauses(caseClauses)
+ else
+ caseClauses.caseClauses.map(Right(_))
+ var formatResult: FormatResult = NoFormatResult
+ var first = true
+ for (clauseGroup clauseGroups) {
+ clauseGroup match {
+ case Left(ConsecutiveSingleLineCaseClauses(caseClauses, largestCasePatternLength))
+ for (caseClause@CaseClause(casePattern, statSeq) caseClauses) {
+ if (!first && hiddenPredecessors(casePattern.firstToken).containsNewline)
+ formatResult = formatResult.before(caseClause.firstToken, formatterState.currentIndentLevelInstruction)
+ val arrowInstruction = PlaceAtColumn(formatterState.indentLevel, largestCasePatternLength + 1)
+ formatResult ++= formatCaseClause(caseClause, Some(arrowInstruction))
+ first = false
+ }
+ case Right(caseClause)
+ if (!first && hiddenPredecessors(caseClause.firstToken).containsNewline)
+ formatResult = formatResult.before(caseClause.firstToken, formatterState.currentIndentLevelInstruction)
+ formatResult ++= formatCaseClause(caseClause)
+ }
+ first = false
+ }
+ formatResult
+ }
+
+ private def groupClauses(caseClausesAstNode: CaseClauses): List[Either[ConsecutiveSingleLineCaseClauses, CaseClause]] = {
+ val clausesAreMultiline = containsNewline(caseClausesAstNode) || hiddenPredecessors(caseClausesAstNode.firstToken).containsNewline
+
+ def groupClauses(caseClauses: List[CaseClause], first: Boolean): List[Either[ConsecutiveSingleLineCaseClauses, CaseClause]] =
+ caseClauses match {
+ case Nil Nil
+ case (caseClause@CaseClause(casePattern, statSeq)) :: otherClauses
+ val otherClausesGrouped = groupClauses(otherClauses, first = false)
+
+ val casePatternSource = getSource(casePattern)
+ val casePatternFormatResult = formatCasePattern(casePattern)(FormatterState(indentLevel = 0))
+ val offset = casePattern.firstToken.startIndex
+ val edits = writeTokens(casePatternSource, casePattern.tokens, casePatternFormatResult, offset)
+ val formattedText = TextEditProcessor.runEdits(casePatternSource, edits)
+
+ val newlineBeforeClause = hiddenPredecessors(caseClause.firstToken).containsNewline
+ val clauseBodyIsMultiline = containsNewline(statSeq) || statSeq.firstTokenOption.exists(hiddenPredecessors(_).containsNewline)
+ if (formattedText.contains('\n') || (first && !clausesAreMultiline) || (!first && !newlineBeforeClause) || clauseBodyIsMultiline)
+ Right(caseClause) :: otherClausesGrouped
+ else {
+ val arrowAdjust = (if (formattingPreferences(RewriteArrowSymbols)) 1 else casePattern.arrow.length) + 1
+ val casePatternLength = formattedText.length - arrowAdjust
+ otherClausesGrouped match {
+ case Left(consecutiveSingleLineCaseClauses) :: otherGroups
+ Left(consecutiveSingleLineCaseClauses.prepend(caseClause, casePatternLength)) :: otherGroups
+ case _
+ Left(ConsecutiveSingleLineCaseClauses(caseClause :: Nil, casePatternLength)) :: otherClausesGrouped
+ }
+ }
+ }
+ groupClauses(caseClausesAstNode.caseClauses, first = true)
+ }
+
+ private case class ConsecutiveSingleLineCaseClauses(clauses: List[CaseClause], largestCasePatternLength: Int) {
+ def prepend(clause: CaseClause, length: Int) =
+ ConsecutiveSingleLineCaseClauses(clause :: clauses, scala.math.max(length, largestCasePatternLength))
+ }
+
+ private def formatCasePattern(casePattern: CasePattern, arrowInstructionOpt: Option[PlaceAtColumn] = None)(implicit formatterState: FormatterState): FormatResult = {
+ val CasePattern(caseToken: Token, pattern: Expr, guardOption: Option[Guard], arrow: Token) = casePattern
+ var formatResult: FormatResult = NoFormatResult
+ formatResult ++= format(pattern)
+ for (guard guardOption)
+ formatResult ++= format(guard)
+ arrowInstructionOpt foreach { instruction formatResult = formatResult.before(arrow, instruction) }
+ formatResult
+ }
+
+ private def formatCaseClause(caseClause: CaseClause, arrowInstructionOpt: Option[PlaceAtColumn] = None)(implicit formatterState: FormatterState): FormatResult = {
+ val CaseClause(casePattern: CasePattern, statSeq: StatSeq) = caseClause
+ var formatResult: FormatResult = NoFormatResult
+ formatResult ++= formatCasePattern(casePattern, arrowInstructionOpt)
+ val singleBlockExpr = cond(statSeq.firstStatOpt) { case Some(Expr(List(BlockExpr(_, _, _)))) true } && statSeq.otherStats.isEmpty
+ val indentBlock = statSeq.firstTokenOption.isDefined && hiddenPredecessors(statSeq.firstToken).containsNewline || (containsNewline(statSeq) && !singleBlockExpr)
+ if (indentBlock)
+ formatResult = formatResult.before(statSeq.firstToken, formatterState.nextIndentLevelInstruction)
+
+ val stateForStatSeq = if (singleBlockExpr) formatterState else formatterState.indent
+ formatResult ++= format(statSeq)(stateForStatSeq)
+ formatResult
+ }
+
+}
@@ -4,11 +4,12 @@ import scalariform.lexer.Token
import scalariform.lexer.Tokens._
import scalariform.parser._
import scalariform.utils.Utils
+import scalariform.utils.TextEditProcessor
import scalariform.utils.BooleanLang._
import scalariform.formatter.preferences._
import PartialFunction._
-trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter with HasHiddenTokenInfo with TypeFormatter with TemplateFormatter with ScalaFormatter with XmlFormatter
+trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter with HasHiddenTokenInfo with TypeFormatter with TemplateFormatter with ScalaFormatter with XmlFormatter with CaseClauseFormatter
def format(expr: Expr)(implicit formatterState: FormatterState): FormatResult = format(expr.contents)
@@ -352,7 +353,7 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
formatResult
}
- private def format(guard: Guard)(implicit formatterState: FormatterState): FormatResult = {
+ def format(guard: Guard)(implicit formatterState: FormatterState): FormatResult = {
val Guard(ifToken: Token, expr: Expr) = guard
format(expr)
}
@@ -477,33 +478,7 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
} else
formatResult ++= format(statSeq)(newFormatterState)
}
- formatResult
- }
-
- private def format(caseClauses: CaseClauses)(implicit formatterState: FormatterState): FormatResult = {
- var formatResult: FormatResult = NoFormatResult
- for ((previousOption, caseClause) Utils.pairWithPrevious(caseClauses.caseClauses)) {
- if (previousOption.isDefined && hiddenPredecessors(caseClause.firstToken).containsNewline)
- formatResult = formatResult.before(caseClause.caseToken, formatterState.currentIndentLevelInstruction)
- formatResult ++= format(caseClause)
- }
- formatResult
- }
-
- private def format(caseClause: CaseClause)(implicit formatterState: FormatterState): FormatResult = {
- val CaseClause(caseToken: Token, pattern: Expr, guardOption: Option[Guard], arrow: Token, statSeq: StatSeq) = caseClause
- var formatResult: FormatResult = NoFormatResult
- formatResult ++= format(pattern)
- for (guard guardOption)
- formatResult ++= format(guard)
-
- val singleBlockExpr = cond(statSeq.firstStatOpt) { case Some(Expr(List(BlockExpr(_, _, _)))) true } && statSeq.otherStats.isEmpty
- val indentBlock = statSeq.firstTokenOption.isDefined && hiddenPredecessors(statSeq.firstToken).containsNewline || (containsNewline(statSeq) && !singleBlockExpr)
- if (indentBlock)
- formatResult = formatResult.before(statSeq.firstToken, formatterState.nextIndentLevelInstruction)
- val stateForStatSeq = if (singleBlockExpr) formatterState else formatterState.indent
- formatResult ++= format(statSeq)(stateForStatSeq)
formatResult
}
@@ -40,9 +40,11 @@ object NoFormatResult extends FormatResult(Map(), Map(), Map())
abstract sealed class IntertokenFormatInstruction
-/** Packs the comments together as compactly as possible, eliminating
+/**
+ * Packs the comments together as compactly as possible, eliminating
* as much non-comment whitespace as possible while ensuring that the
- * lexer produces the same tokens.*/
+ * lexer produces the same tokens.
+ */
case object Compact extends IntertokenFormatInstruction
/** Like "Compact", but ensures there is either some comment or a single space. */
@@ -53,3 +55,6 @@ case object CompactPreservingGap extends IntertokenFormatInstruction
/** Ensures that the interttoken region ends with NEWLINE INDENT. */
case class EnsureNewlineAndIndent(indentLevel: Int, relativeTo: Option[Token] = None) extends IntertokenFormatInstruction
+
+/** Places the token at spaces number of spaces after the indent level, padding with spaces if necessary */
+case class PlaceAtColumn(indentLevel: Int, spaces: Int) extends IntertokenFormatInstruction
@@ -20,10 +20,20 @@ trait HasHiddenTokenInfo {
}
-abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatter with AnnotationFormatter with ExprFormatter with HasHiddenTokenInfo with TemplateFormatter with XmlFormatter {
+abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatter with AnnotationFormatter with ExprFormatter with HasHiddenTokenInfo with TemplateFormatter with XmlFormatter with CaseClauseFormatter {
val newlineSequence: String
+ def getSource(astNode: AstNode): String = {
+ val sb = new StringBuilder
+ for (token astNode.tokens) {
+ if (token != astNode.tokens.head)
+ sb.append(hiddenPredecessors(token).text)
+ sb.append(token.text)
+ }
+ sb.toString
+ }
+
def format(compilationUnit: CompilationUnit)(implicit formatterState: FormatterState = FormatterState()): FormatResult = {
val topStats = compilationUnit.topStats
var result = format(topStats)
@@ -42,7 +52,7 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte
private def replaceEdit(token: Token, replacement: String): TextEdit = TextEdit(token.startIndex, token.text.length, replacement)
- def writeTokens(s: String, tokens: List[Token], formatResult: FormatResult): List[TextEdit] = {
+ def writeTokens(s: String, tokens: List[Token], formatResult: FormatResult, offset: Int = 0): List[TextEdit] = {
val FormatResult(predecessorFormatting, inferredNewlineFormatting, xmlRewrites) = formatResult
val builder = new StringBuilder
var tokenIndentMap: Map[Token, Int] = Map()
@@ -82,7 +92,11 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte
}
}
}
- edits.reverse filter { case TextEdit(start, length, replacement) s.substring(start, start + length) != replacement } distinct
+ edits
+ .reverse
+ .flatMap { edit if (edit.position >= offset) Some(edit.shift(-offset)) else None }
+ .filter { case TextEdit(position, length, replacement) s.substring(position, position + length) != replacement }
+ .distinct
}
private def formatComment(comment: HiddenToken, indentLevel: Int) = {
@@ -153,6 +167,10 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte
builder.append(" ")
else
writeIntertokenCompact()
+ case PlaceAtColumn(indentLevel, spaces)
+ writeIntertokenCompact()
+ val indentLength = Spaces(formattingPreferences(IndentSpaces)).length(indentLevel)
+ builder.append(" " * (indentLength + spaces - builder.currentIndent))
case EnsureNewlineAndIndent(indentLevel, relativeTo)
val baseIndentOption = relativeTo flatMap tokenIndentMap.get
if (hiddenTokens.isEmpty) {
Oops, something went wrong.

0 comments on commit 4831dee

Please sign in to comment.