diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index 33ff92966..24789a3c2 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -6,15 +6,11 @@ package sbt import java.io.File import java.net.URI import Def.{ ScopedKey, Setting } -import Project._ import Types.Endo import compiler.Eval import SessionSettings._ -import scala.collection.immutable.SortedMap - - /** * Represents (potentially) transient settings added into a build via commands/user. * @@ -85,16 +81,18 @@ object SessionSettings { type SessionSetting = (Setting[_], List[String]) type SessionMap = Map[ProjectRef, Seq[SessionSetting]] - /** This will re-evaluate all Setting[_]'s on this session against the current build state and - * return the new build state. - */ + /** + * This will re-evaluate all Setting[_]'s on this session against the current build state and + * return the new build state. + */ def reapply(session: SessionSettings, s: State): State = BuiltinCommands.reapply(session, Project.structure(s), s) - /** This will clear any user-added session settings for a given build state and return the new build state. - * - * Note: Does not clear `rawAppend` settings - */ + /** + * This will clear any user-added session settings for a given build state and return the new build state. + * + * Note: Does not clear `rawAppend` settings + */ def clearSettings(s: State): State = withSettings(s)(session => reapply(session.copy(append = session.append - session.current), s)) /** This will clear ALL transient session settings in a given build state, returning the new build state. */ @@ -128,9 +126,6 @@ object SessionSettings { if (newSession.append.isEmpty && !oldSettings.isEmpty) oldState.log.warn("Discarding " + pluralize(oldSettings.size, " session setting") + ". Use 'session save' to persist session settings.") } - - - def removeRanges[T](in: Seq[T], ranges: Seq[(Int, Int)]): Seq[T] = { val asSet = (Set.empty[Int] /: ranges) { case (s, (hi, lo)) => s ++ (hi to lo) } @@ -151,11 +146,12 @@ object SessionSettings { saveSomeSettings(s)(_ == current) } - /** Saves session settings to disk if they match the filter. - * @param s The build state - * @param include A filter function to determine which project's settings to persist. - * @return The new build state. - */ + /** + * Saves session settings to disk if they match the filter. + * @param s The build state + * @param include A filter function to determine which project's settings to persist. + * @return The new build state. + */ def saveSomeSettings(s: State)(include: ProjectRef => Boolean): State = withSettings(s) { session => val newSettings = @@ -182,23 +178,21 @@ object SessionSettings { } } - val (_, oldShifted, replace, lineMap) = ((0, List[Setting[_]](), List[SessionSetting](), SortedMap.empty[Int, List[(Int, List[String])]](Ordering[Int].reverse)) /: inFile) { - case ((offs, olds, repl, lineMap), s) => + val (_, oldShifted, replace, statements) = ((0, List[Setting[_]](), List[SessionSetting](), List[List[String]]()) /: inFile) { + case ((offs, olds, repl, statements), s) => val RangePosition(_, r @ LineRange(start, end)) = s.pos settings find (_._1.key == s.key) match { case Some(ss @ (ns, newLines)) if !ns.init.dependencies.contains(ns.key) => val shifted = ns withPos RangePosition(path, LineRange(start - offs, start - offs + newLines.size)) - val head = (end, newLines) - val seq = lineMap.getOrElse(start, List()) - (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (head +: seq))) + (offs + end - start - newLines.size, shifted :: olds, ss :: repl, newLines +: statements) case _ => val shifted = s withPos RangePosition(path, r shift -offs) - (offs, shifted :: olds, repl, lineMap) + (offs, shifted :: olds, repl, statements) } } val newSettings = settings diff replace val oldContent = IO.readLines(writeTo) - val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, lineMap) + val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, statements) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala index 823696fce..191793b17 100644 --- a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -1,10 +1,7 @@ package sbt -import java.io.File - -import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } -import scala.collection.immutable.SortedMap import scala.reflect.runtime.universe._ +import sbt.internals.parser.{ XmlContent, SplitExpressionsNoBlankies } object SessionSettingsNoBlankies { @@ -12,65 +9,43 @@ object SessionSettingsNoBlankies { val REVERSE_ORDERING_INT = Ordering[Int].reverse - def oldLinesToNew(content: List[String], lineMap: SortedMap[Int, List[(Int, List[String])]]): List[String] = - if (lineMap.isEmpty) { - content - } else { - val head = lineMap.head - val newContent = toNewContent(content, head) - oldLinesToNew(newContent, lineMap.tail) - } - - private def toNewContent(content: List[String], setCommands: (Int, List[(Int, List[String])])): List[String] = { - val (from, newSettings) = setCommands - - val newTreeStringsMap = newSettings.map { - case (_, lines) => toTreeStringMap(lines) - } - val to = newSettings.map(_._1).max - val originalLine = content.slice(from - 1, to - 1) - - val operations = newTreeStringsMap.flatMap { - map => + def oldLinesToNew(lines: List[String], setCommands: List[List[String]]): List[String] = { + val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) + val recordedCommand = setCommands.flatMap { + command => + val map = toTreeStringMap(command) map.flatMap { - case (name, (startIndex, statement)) => - val validLines = cutExpression(originalLine, name) - val treeStringMap = toTreeStringMap(validLines) - treeStringMap.get(name).map { - case (t, oldContent) => - (startIndex, oldContent, statement) + case (name, (startPos, statement)) => + split.settingsTrees.foldLeft(Seq.empty[(Int, String, String)]) { + case (acc, (statement, tree)) => + val treeName = extractSettingName(tree) + if (name == treeName) { + val replacement = if (acc.isEmpty) { + command.mkString("\n") + } else { + "" + } + (tree.pos.start, statement, replacement) +: acc + } else { + acc + } } } } - val statements = XmlContent.handleXmlContent(originalLine.mkString("\n")) - val sortedOperations = operations.sortBy(_._1)(REVERSE_ORDERING_INT) - val newContent = sortedOperations.foldLeft(statements) { - case (acc, (startIndex, old, newStatement)) => - acc.replace(old, newStatement) + val sortedRecordedCommand = recordedCommand.sortBy(_._1)(REVERSE_ORDERING_INT) + + val newContent = sortedRecordedCommand.foldLeft(split.modifiedContent) { + case (acc, (from, old, replacement)) => + val before = acc.substring(0, from) + val after = acc.substring(from + old.length, acc.length) + before + replacement + after + // acc.replace(old, replacement) } - val newLines = newContent.lines.toList - content.take(from - 1) ++ newLines ++ content.drop(to - 1) + newContent.lines.toList } - private[sbt] def cutExpression(l: List[String], name: String): List[String] = l match { - case h +: t => - val statements = SplitExpressionsNoBlankies(FAKE_FILE, l).settingsTrees - val lastIndex = statements.lastIndexWhere { - tuple => extractSettingName(tuple._2) == name - } - val (statement, tree) = statements(lastIndex) - - if (tree.pos.end >= h.length) { - l - } else { - statement +: t - } - case _ => - l - } - - private def toTreeStringMap(lines: List[String]) = { - val split = SplitExpressionsNoBlankies(FAKE_FILE, lines) + private def toTreeStringMap(command: List[String]) = { + val split = SplitExpressionsNoBlankies(FAKE_FILE, command) val trees = split.settingsTrees val seq = trees.map { case (statement, tree) => diff --git a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala index 754a654cd..b975f5226 100644 --- a/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/internals/parser/SplitExpressionsNoBlankies.scala @@ -4,9 +4,8 @@ package parser import java.io.File -import sbt.internals.parser.SplitExpressionsNoBlankies._ +import SplitExpressionsNoBlankies._ import scala.annotation.tailrec -import scala.reflect.runtime.universe import scala.reflect.runtime.universe._ private[sbt] object SplitExpressionsNoBlankies { @@ -17,14 +16,14 @@ private[sbt] object SplitExpressionsNoBlankies { private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { //settingsTrees needed for "session save" - val (imports, settings, settingsTrees) = splitExpressions(file, lines) + val (imports, settings, settingsTrees, modifiedContent) = splitExpressions(file, lines) - private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { - import sbt.internals.parser.BugInParser._ - import sbt.internals.parser.XmlContent._ + private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)], String) = { + import scala.reflect.runtime._ import scala.compat.Platform.EOL import BugInParser._ + import XmlContent._ import scala.tools.reflect.{ ToolBox, ToolBoxError } val mirror = universe.runtimeMirror(this.getClass.getClassLoader) @@ -90,7 +89,7 @@ private[sbt] case class SplitExpressionsNoBlankies(file: File, lines: Seq[String None } val statementsTreeLineRange = statements flatMap convertStatement - (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2))) + (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2)), modifiedContent) } } @@ -302,31 +301,32 @@ private[sbt] object XmlContent { */ private def addExplicitXmlContent(content: String, xmlParts: Seq[(String, Int, Int)]): String = { val statements: Seq[(String, Boolean)] = splitFile(content, xmlParts) - val (builder, wasPreviousXml, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { - (acc, el) => - val (bAcc, wasXml, _, previous) = acc - val (content, isXml) = el - val contentEmpty = content.trim.isEmpty - val (isNotCommentedXml, newAcc) = if (isXml) { - if (!wasXml) { - if (areBracketsNecessary(previous)) { - (true, " ( " +: bAcc) - } else { - (false, bAcc) - } - } else { - (true, bAcc) - } - } else if (wasXml && !contentEmpty) { - (false, " ) " +: bAcc) + val (builder, addCloseBrackets, wasXml, _) = statements.foldLeft((Seq.empty[String], false, false, "")) { + case ((bAcc, addCloseBrackets, wasXml, previous), (content, isXml)) => + if (content.trim.isEmpty) { + (content +: bAcc, addCloseBrackets, wasXml, content) } else { - (false, bAcc) - } + val (newAddCloseBrackets, newAcc) = if (isXml) { + if (wasXml) { + (addCloseBrackets, bAcc) + } else { + if (areBracketsNecessary(previous)) { + (true, " ( " +: bAcc) + } else { + (false, bAcc) + } + } + } else if (addCloseBrackets) { + (false, " ) " +: bAcc) + } else { + (false, bAcc) + } - (content +: newAcc, isNotCommentedXml || (wasXml && contentEmpty), isXml, content) + (content +: newAcc, newAddCloseBrackets, isXml, content) + } } val closeIfNecessaryBuilder = - if (wasPreviousXml && !wasXml) { + if (addCloseBrackets && wasXml) { builder.head +: " ) " +: builder.tail } else { builder diff --git a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result index e1b25cd91..9ef907fc3 100644 --- a/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result +++ b/main/src/test/resources/session-settings/1.sbt.txt_1/2.xml.result @@ -18,10 +18,10 @@ val a = */ -organization := "jozwikr" // OK +organization := "scalania" // OK scalaVersion := "2.9.2" -organization := "scalania" + diff --git a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala b/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala deleted file mode 100644 index 17dfa3036..000000000 --- a/main/src/test/scala/sbt/SessionSettingsCutExpressionSpec.scala +++ /dev/null @@ -1,23 +0,0 @@ -package sbt - -import sbt.internals.parser.AbstractSpec - -class SessionSettingsCutExpressionSpec extends AbstractSpec { - - "Cut expression " should { - - "Cut only statement which we are interesting " in { - val name = "scalaVersion" - val expression = s"""$name := "2.9.2"""" - val line = s"""name := "newName";$expression; organization := "jozwikr"""" - SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(expression) - } - - "Do not cut not valid expression " in { - val name = "k4" - val line = s"$name := { val x = $name.value; () }" - SessionSettingsNoBlankies.cutExpression(List(line), name) must_== List(line) - - } - } -} diff --git a/main/src/test/scala/sbt/SessionSettingsSpec.scala b/main/src/test/scala/sbt/SessionSettingsSpec.scala index cc742897f..4c6e79100 100644 --- a/main/src/test/scala/sbt/SessionSettingsSpec.scala +++ b/main/src/test/scala/sbt/SessionSettingsSpec.scala @@ -6,7 +6,6 @@ import org.specs2.matcher.MatchResult import sbt.internals.parser.{ AbstractSpec, SplitExpressionsNoBlankies } import scala.collection.GenTraversableOnce -import scala.collection.immutable.{ SortedMap, TreeMap } import scala.io.Source import scala.xml.XML @@ -17,7 +16,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean "SessionSettings " should { "Be identical for empty map " in { - def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, SortedMap.empty[Int, List[(Int, List[String])]])) + def unit(f: File) = Seq((Source.fromFile(f).getLines().toList, Nil)) runTestOnFiles(unit) } @@ -26,7 +25,7 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean } } - private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], SortedMap[Int, List[(Int, List[String])]])]): MatchResult[GenTraversableOnce[File]] = { + private def runTestOnFiles(expectedResultAndMap: File => Seq[(List[String], List[List[String]])]): MatchResult[GenTraversableOnce[File]] = { val allFiles = rootDir.listFiles(new FilenameFilter() { def accept(dir: File, name: String) = name.endsWith(".sbt.txt") @@ -64,12 +63,10 @@ abstract class AbstractSessionSettingsSpec(folder: String, printDetails: Boolean val set = (node \\ "set").text val start = (node \\ "start").text.toInt val end = (node \\ "end").text.toInt - (start, (end, List(set))) + List(set) }.toList - val map = tupleCollection.groupBy(el => el._1).map { - case (k, seq) => (k, seq.map(el => el._2)) - } - (result, TreeMap(map.toArray: _*)(SessionSettingsNoBlankies.REVERSE_ORDERING_INT)) + + (result, tupleCollection) } } }