Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flag to skip insertion of spaces within multi imports. (rebase) #127

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.rst
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ You can also configure formatting to be run as a save action (Window -> Preferen

To set preferences, go to Window -> Preferences -> Scala -> Formatter

Integration with IntelliJ
-------------------------

IntelliJ already has a built-in Scala code formatter (C-L).
I use the original settings plus `Wraping and Braces` -> `Align columns in case branches`.
In order to achieve exactly the same formatting use the following options::


def formattingPreferences = {
import scalariform.formatter.preferences._
FormattingPreferences()
.setPreference(AlignParameters, true)
.setPreference(AlignSingleLineCaseStatements, true)
.setPreference(AlignSingleLineCaseStatements.AlignMultiLineCaseStatements, true)
.setPreference(AlignSingleLineCaseStatements.MaxArrowIndent, 120)
.setPreference(AlignSingleLineCaseStatements.GroupByNewLine, true) // IntelliJ compatible
.setPreference(CompactControlReadability, false)
.setPreference(NoSpacesAroundMultiImports, true)
.setPreference(FormatXml, false)
.setPreference(PreserveSpaceBeforeArguments, true)
.setPreference(IndentWithTabs, false)
.setPreference(SpacesWithinPatternBinders, false) // IntelliJ compatible
}


Integration with Emacs/ENSIME
-----------------------------

Expand Down Expand Up @@ -474,6 +499,22 @@ If ``false``,::

case elem@Multi(values@_*) =>

chainedPackageClauses
~~~~~~~~~~~~~~~~~~~~~

Default: ``false``

Example::
```
package com.company.analytics.math.curves.interpolators
```

Will be reformatted to::
```
package com.company.analytics.math
package curves
package interpolators
```

Scala Style Guide
~~~~~~~~~~~~~~~~~
Expand Down
56 changes: 48 additions & 8 deletions scalariform/src/main/scala/scalariform/formatter/CaseClauseFormatter.scala
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package scalariform.formatter

import scalariform.lexer.Token
import scalariform.lexer.{TokenType, Token}
import scalariform.lexer.Tokens._
import scalariform.parser._
import scalariform.utils.Utils
Expand Down Expand Up @@ -81,16 +81,18 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi
val newlineBeforeClause = hiddenPredecessors(caseClause.firstToken).containsNewline ||
previousCaseClauseEndsWithNewline(caseClause, caseClausesAstNode)

// To evaluate whether a clause body is multiline, we ignore a trailing newline:
// To evaluate whether a clause body is multiline, we ignore a trailing newline:
val prunedStatSeq = pruneTrailingNewline(statSeq)
val clauseBodyIsMultiline = containsNewline(pruneTrailingNewline(statSeq)) ||
statSeq.firstTokenOption.exists(hiddenPredecessors(_).containsNewline)

if (formattedCasePattern.contains('\n') || (first && !clausesAreMultiline) || (!first && !newlineBeforeClause) || clauseBodyIsMultiline)
if (first && !clausesAreMultiline || !first && !newlineBeforeClause ||
!formattingPreferences(AlignSingleLineCaseStatements.AlignMultiLineCaseStatements) && clauseBodyIsMultiline)
Right(caseClause) :: otherClausesGrouped
else {
val arrowAdjust = (if (formattingPreferences(RewriteArrowSymbols)) 1 else casePattern.arrow.length) + 1
val casePatternLength = formattedCasePattern.length - arrowAdjust
val casePatternLengthConsideringNewLines = formattedCasePattern.split('\n').last.length
val casePatternLength = casePatternLengthConsideringNewLines - arrowAdjust
otherClausesGrouped match {
case Left(consecutiveSingleLineCaseClauses) :: otherGroups ⇒
Left(consecutiveSingleLineCaseClauses.prepend(caseClause, casePatternLength)) :: otherGroups
Expand All @@ -99,7 +101,26 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi
}
}
}
groupClauses(caseClausesAstNode.caseClauses, first = true)

val caseClauses: List[CaseClause] = caseClausesAstNode.caseClauses

val ranges: List[(Int, Int)] = if (formattingPreferences(AlignSingleLineCaseStatements.GroupByNewLine)) {
val newLinesAt = (caseClauses.zipWithIndex.collect {
case (c, i) if c.tokens.exists(_.isNewlines) => i + 1
})

val newLinesWithBeginAndEnd = (if (newLinesAt.contains(0)) List() else List(0)) ++ newLinesAt ++ List(caseClauses.length)

newLinesWithBeginAndEnd.zip(newLinesWithBeginAndEnd.tail)
} else {
List(0 -> caseClauses.length)
}

ranges.foldLeft(List[Either[ConsecutiveSingleLineCaseClauses, CaseClause]]()) {
case (acc, (begin, end)) =>
val slice = caseClauses.slice(begin, end)
acc ++ groupClauses(slice, first = true)
}
}

private case class ConsecutiveSingleLineCaseClauses(clauses: List[CaseClause], largestCasePatternLength: Int, smallestCasePatternLength: Int) {
Expand All @@ -114,6 +135,21 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi
val CasePattern(caseToken: Token, pattern: Expr, guardOption: Option[Guard], arrow: Token) = casePattern
var formatResult: FormatResult = NoFormatResult
formatResult ++= format(pattern)

val beginTokensAfterPipe = List(casePattern.pattern.tokens.head) ++ casePattern.pattern.tokens.zip(casePattern.pattern.tokens.tail).collect {
case (Token(PIPE, _, _, _), b) if b.associatedWhitespaceAndComments.containsNewline => b
}

val alignBeginTokens: Map[Token, IntertokenFormatInstruction] = beginTokensAfterPipe.zip(beginTokensAfterPipe.tail).flatMap {
case (a, b) => if (b.associatedWhitespaceAndComments.containsNewline) {
Map(b -> EnsureNewlineAndIndent(0, Some(a)))
} else {
Map.empty[Token, IntertokenFormatInstruction]
}
}.toMap

formatResult ++= FormatResult(alignBeginTokens, Map(), Map())

for (guard ← guardOption)
formatResult ++= format(guard)
arrowInstructionOpt foreach { instruction ⇒ formatResult = formatResult.before(arrow, instruction) }
Expand Down Expand Up @@ -149,10 +185,14 @@ trait CaseClauseFormatter { self: HasFormattingPreferences with ExprFormatter wi
if separator.isNewline
} yield separator

private def previousCaseClauseTrailingNewlineOpt(caseClause: CaseClause, caseClauses: CaseClauses): Option[Token] =
Utils.pairWithPrevious(caseClauses.caseClauses).collect {
private def previousCaseClauseTrailingNewlineOpt(caseClause: CaseClause, caseClauses: CaseClauses): Option[Token] = {
val previousCaseClauseOpt = Utils.pairWithPrevious(caseClauses.caseClauses).collect {
case (Some(previousClause), `caseClause`) ⇒ previousClause
}.headOption.flatMap(getTrailingNewline)
}.headOption
val hasNewLine = previousCaseClauseOpt.flatMap(getTrailingNewline)
val hasNewLineAtEnd = previousCaseClauseOpt.flatMap(_.statSeq.tokens.lastOption.filter(_.isNewline))
hasNewLine.orElse(hasNewLineAtEnd)
}

private def previousCaseClauseEndsWithNewline(caseClause: CaseClause, caseClauses: CaseClauses): Boolean =
previousCaseClauseTrailingNewlineOpt(caseClause, caseClauses).isDefined
Expand Down
53 changes: 48 additions & 5 deletions scalariform/src/main/scala/scalariform/formatter/ExprFormatter.scala
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -636,15 +636,22 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
(CompactEnsuringGap, indentedState)
formatResult = formatResult.before(statSeq.firstToken, instruction)
formatResult ++= format(params)

val hasNewScopeAfterArrow = {
val arrowIndex = statSeq.tokens.indexWhere(_.tokenType == ARROW)
val tokenAfterArrow = statSeq.tokens.lift(arrowIndex + 1)
tokenAfterArrow.map(_.tokenType == lbrace.tokenType).getOrElse(false)
}
for (firstToken ← subStatSeq.firstTokenOption) {
val instruction =
if (hiddenPredecessors(firstToken).containsNewline || containsNewline(subStatSeq))
if (!hasNewScopeAfterArrow && (hiddenPredecessors(firstToken).containsNewline || containsNewline(subStatSeq)))
statFormatterState(subStatSeq.firstStatOpt)(subStatState).currentIndentLevelInstruction
else
CompactEnsuringGap
formatResult = formatResult.before(firstToken, instruction)
}
formatResult ++= format(subStatSeq)(subStatState)
val subStatStateAfterArrow = if(hasNewScopeAfterArrow) newFormatterState.indent else subStatState
formatResult ++= format(subStatSeq)(subStatStateAfterArrow)
case _ ⇒
val instruction = statSeq.selfReferenceOpt match {
case Some((selfReference, arrow)) if !hiddenPredecessors(selfReference.firstToken).containsNewline ⇒
Expand Down Expand Up @@ -683,7 +690,7 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
}

for (stat ← firstStatOpt)
formatResult ++= format(stat)(firstStatFormatterState)
formatResult ++= format(stat, true)(firstStatFormatterState)

for ((semi, otherStatOption) ← otherStats) {

Expand All @@ -702,23 +709,55 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
CompactEnsuringGap
formatResult = formatResult.before(firstToken, instruction)
}
formatResult ++= format(otherStat)(otherStatFormatterState)
formatResult ++= format(otherStat, false)(otherStatFormatterState)
}

}

formatResult
}

private def format(stat: Stat)(implicit formatterState: FormatterState): FormatResult =
private def format(stat: Stat, firstStat: Boolean)(implicit formatterState: FormatterState): FormatResult =
stat match {
case expr: Expr ⇒ format(expr)
case fullDefOrDcl: FullDefOrDcl ⇒ format(fullDefOrDcl)
case import_ : ImportClause ⇒ format(import_)
case packageBlock: PackageBlock ⇒ format(packageBlock)
case packageStat: PackageStat ⇒ format(packageStat, firstStat)
case _ ⇒ NoFormatResult // TODO
}

def format(packageStat: PackageStat, firstPackage: Boolean)(implicit formatterState: FormatterState): FormatResult = {
var formatResult: FormatResult = NoFormatResult

if (formattingPreferences(ChainedPackageClauses)) {
val PackageStat(packageToken: Token, name: CallExpr) = packageStat

val packageDepth = if(firstPackage) formattingPreferences(ChainedPackageClauses.PackageDepth) else 1

def dotsForPackageNames(name: CallExpr): List[Token] = {
name.exprDotOpt match {
case Some((List(c@CallExpr(_, id, _, _, _)), dot)) =>
dot :: dotsForPackageNames(c)
case _ =>
Nil
}
}

val dotsBetweenPackageNames = dotsForPackageNames(name)

val lineBreaksAfter = dotsBetweenPackageNames.reverse.drop(packageDepth-1)

for {
token <- lineBreaksAfter
} {
formatResult ++= FormatResult(Map(token -> EnsureNewlineAndIndent(0, Some(packageToken))), Map(), Map())
}
}

formatResult
}

def format(packageBlock: PackageBlock)(implicit formatterState: FormatterState): FormatResult = {
val PackageBlock(packageToken: Token, name: CallExpr, newlineOpt: Option[Token], lbrace: Token, topStats: StatSeq, rbrace: Token) = packageBlock

Expand Down Expand Up @@ -909,9 +948,13 @@ trait ExprFormatter { self: HasFormattingPreferences with AnnotationFormatter wi
val newFormatterState = formatterState.copy(inSingleLineBlock = singleLineBlock)

if (singleLineBlock) {
if (formattingPreferences(NoSpacesAroundMultiImports))
formatResult = formatResult.before(firstImportSelector.firstToken, Compact)
formatResult ++= format(firstImportSelector)
for ((comma, otherImportSelector) ← otherImportSelectors)
formatResult ++= format(otherImportSelector)
if (formattingPreferences(NoSpacesAroundMultiImports))
formatResult = formatResult.before(rbrace, Compact)
} else {
formatResult = formatResult.before(firstImportSelector.firstToken, formatterState.nextIndentLevelInstruction)
formatResult ++= format(firstImportSelector)
Expand Down
9 changes: 7 additions & 2 deletions scalariform/src/main/scala/scalariform/formatter/ScalaFormatter.scala
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte
val replacement = builder.substring(startPos)
positionHintOption match {
case Some(positionHint) if hiddenTokens.isEmpty ⇒
Some(TextEdit(positionHint, length = 0, replacement = replacement))
instruction match {
case EnsureNewlineAndIndent(indentLevel, Some(Token(PACKAGE, _, _, _))) =>
Some(TextEdit(positionHint, length = 1, replacement = replacement + "package "))
case _ =>
Some(TextEdit(positionHint, length = 0, replacement = replacement))
}
case _ ⇒
for {
firstToken ← hiddenTokens.firstTokenOption
Expand Down Expand Up @@ -348,7 +353,7 @@ abstract class ScalaFormatter extends HasFormattingPreferences with TypeFormatte
return CompactEnsuringGap
if (type1 == MINUS && (type2 == INTEGER_LITERAL || type2 == FLOATING_POINT_LITERAL))
return Compact
if (Set(IMPLICIT, VAL, VAR, PRIVATE, PROTECTED, OVERRIDE).contains(type2) && type1 == LPAREN)
if (Set(IMPLICIT, VAL, VAR, PRIVATE, PROTECTED, OVERRIDE, FINAL).contains(type2) && type1 == LPAREN)
return Compact
if ((type1 == PROTECTED || type1 == PRIVATE) && type2 == LBRACKET)
return Compact
Expand Down
37 changes: 34 additions & 3 deletions scalariform/src/main/scala/scalariform/formatter/preferences/PreferenceDescriptor.scala
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ trait IntegerPreferenceDescriptor extends PreferenceDescriptor[Int] {
object AllPreferences {
val preferences: List[PreferenceDescriptor[_]] = List(RewriteArrowSymbols, IndentSpaces, SpaceBeforeColon, CompactStringConcatenation,
PreserveSpaceBeforeArguments, AlignParameters, DoubleIndentClassDeclaration, FormatXml, IndentPackageBlocks,
AlignSingleLineCaseStatements, AlignSingleLineCaseStatements.MaxArrowIndent, IndentLocalDefs, PreserveDanglingCloseParenthesis,
AlignSingleLineCaseStatements, AlignSingleLineCaseStatements.MaxArrowIndent, AlignSingleLineCaseStatements.AlignMultiLineCaseStatements, IndentLocalDefs, PreserveDanglingCloseParenthesis,
SpaceInsideParentheses, SpaceInsideBrackets, SpacesWithinPatternBinders, MultilineScaladocCommentsStartOnFirstLine, IndentWithTabs,
CompactControlReadability, PlaceScaladocAsterisksBeneathSecondAsterisk)
CompactControlReadability, PlaceScaladocAsterisksBeneathSecondAsterisk, NoSpacesAroundMultiImports, ChainedPackageClauses, ChainedPackageClauses.PackageDepth)

val preferencesByKey: Map[String, PreferenceDescriptor[_]] = {
var map: Map[String, PreferenceDescriptor[_]] = Map()
Expand Down Expand Up @@ -142,6 +142,18 @@ case object AlignSingleLineCaseStatements extends BooleanPreferenceDescriptor {
val defaultValue = 40
}

case object AlignMultiLineCaseStatements extends BooleanPreferenceDescriptor {
val key = "alignSingleLineCaseStatements.alignMultiLineCaseStatements"
val description = "Align the arrows of consecutive multi-line case statements"
val defaultValue = false
}

case object GroupByNewLine extends BooleanPreferenceDescriptor {
val key = "alignSingleLineCaseStatements.groupByNewLine"
val description = "Treat blocks that are separated by newlines independently"
val defaultValue = false
}

}

case object IndentLocalDefs extends BooleanPreferenceDescriptor {
Expand Down Expand Up @@ -196,4 +208,23 @@ case object PlaceScaladocAsterisksBeneathSecondAsterisk extends BooleanPreferenc
val key = "placeScaladocAsterisksBeneathSecondAsterisk"
val description = "Place Scaladoc asterisks beneath the second asterisk in the opening '/**', as opposed to the first"
val defaultValue = false
}
}

case object NoSpacesAroundMultiImports extends BooleanPreferenceDescriptor {
val key = "noSpacesAroundMultiImports"
val description = "Don't place spaces around multi imports (Java-style)"
val defaultValue = false
}

case object ChainedPackageClauses extends BooleanPreferenceDescriptor {
val key = "chainedPackageClauses"
val description = "Break up chained package clauses"
val defaultValue = false

case object PackageDepth extends IntegerPreferenceDescriptor {
val key = "chainedPackageClauses.packageDepth"
val description = "Depth of package nesting"
val preferenceType = IntegerPreference(1, 100)
val defaultValue = 4
}
}
2 changes: 2 additions & 0 deletions scalariform/src/main/scala/scalariform/lexer/Token.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ case class Token(tokenType: TokenType, text: String, offset: Int, rawText: Strin

def isNewline = tokenType.isNewline

def isNewlines = tokenType.isNewlines

@deprecated(message = "Use text instead" /*, since = "0.1.2"*/ )
def getText = text

Expand Down
4 changes: 3 additions & 1 deletion scalariform/src/main/scala/scalariform/lexer/TokenType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package scalariform.lexer

case class TokenType(name: String, isXml: Boolean = false) {

def isNewline = this == Tokens.NEWLINE || this == Tokens.NEWLINES
def isNewline = this == Tokens.NEWLINE || isNewlines

def isNewlines = this == Tokens.NEWLINES

def isKeyword = Tokens.KEYWORDS contains this

Expand Down