diff --git a/main/src/main/scala/sbt/SessionSettings.scala b/main/src/main/scala/sbt/SessionSettings.scala index b1984928f..921bd5db9 100755 --- a/main/src/main/scala/sbt/SessionSettings.scala +++ b/main/src/main/scala/sbt/SessionSettings.scala @@ -12,6 +12,8 @@ import compiler.Eval import SessionSettings._ +import scala.collection.immutable.SortedMap + final case class SessionSettings(currentBuild: URI, currentProject: Map[URI, String], original: Seq[Setting[_]], append: SessionMap, rawAppend: Seq[Setting[_]], currentEval: () => Eval) { assert(currentProject contains currentBuild, "Current build (" + currentBuild + ") not associated with a current project.") def setCurrent(build: URI, project: String, eval: () => Eval): SessionSettings = copy(currentBuild = build, currentProject = currentProject.updated(build, project), currentEval = eval) @@ -100,21 +102,23 @@ object SessionSettings { } } - val (_, oldShifted, replace, lineMap) = ((0, List[Setting[_]](), List[SessionSetting](), Map.empty[Int, (Int, List[String])]) /: inFile) { + 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 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)) - (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (end, newLines))) + val head = (end, newLines) + val seq = lineMap.getOrElse(start, List()) + (offs + end - start - newLines.size, shifted :: olds, ss :: repl, lineMap + (start -> (head +: seq))) case _ => val shifted = s withPos RangePosition(path, r shift -offs) (offs, shifted :: olds, repl, lineMap) } } val newSettings = settings diff replace - val oldContentWithIndex = IO.readLines(writeTo).zipWithIndex - val exist: List[String] = toLines(oldContentWithIndex, lineMap) + val oldContent = IO.readLines(writeTo) + val exist: List[String] = SessionSettingsNoBlankies.oldLinesToNew(oldContent, lineMap) val adjusted = if (!newSettings.isEmpty && needsTrailingBlank(exist)) exist :+ "" else exist val lines = adjusted ++ newSettings.flatMap(_._2 ::: "" :: Nil) IO.writeLines(writeTo, lines) @@ -126,18 +130,6 @@ object SessionSettings { (newWithPos.reverse, other ++ oldShifted) } - private[sbt] def toLines(oldContentWithIndex: List[(String,Int)], lineMap: Map[Int, (Int, List[String])]): List[String] = { - val (tmpLines, _) = ((List[String](), 1) /: oldContentWithIndex) { - case ((accLines, n), (line, m)) if n == m + 1 => - lineMap.get(n) match { - case Some(Pair(end, lines)) => (lines reverse_::: accLines, end) - case None => (line :: accLines, n + 1) - } - case (res, _) => res - } - tmpLines.reverse - } - def needsTrailingBlank(lines: Seq[String]) = !lines.isEmpty && !lines.takeRight(1).exists(_.trim.isEmpty) def printAllSettings(s: State): State = withSettings(s) { session => diff --git a/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala new file mode 100644 index 000000000..806332def --- /dev/null +++ b/main/src/main/scala/sbt/SessionSettingsNoBlankies.scala @@ -0,0 +1,79 @@ +package sbt + +import java.io.File + +import scala.collection.immutable.SortedMap +import scala.reflect.runtime.universe._ + +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], tuple: (Int, List[(Int, List[String])])): List[String] = { + val (from, newSettingSeq) = tuple + + val newTreeStringSeqMap = newSettingSeq.seq.map { + case (_, lines) => toTreeStringMap(lines) + } + val to = newSettingSeq.map(_._1).max + val originalLine = content.slice(from - 1, to - 1) + + val operations = newTreeStringSeqMap.flatMap { + map => + 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) + } + } + } + val statements = 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 newLines = newContent.lines.toList + content.take(from - 1) ++ newLines ++ content.drop(to - 1) + } + + private def cutExpression(l: List[String], name: String): List[String] = l match { + case h +: t => + val array = h.split(";").filter(_.contains(name)) + array.mkString(";") +: t + case _ => + l + } + + private def toTreeStringMap(lines: List[String]) = { + + val trees = SplitExpressionsNoBlankies(new File("fake"), lines).settingsTrees + val seq = trees.map { + case (statement, tree) => + (extractSettingName(tree), (tree.pos.start, statement)) + } + seq.toMap + } + + private def extractSettingName(tree: Tree): String = { + tree.children match { + case h :: _ => + extractSettingName(h) + case _ => + tree.toString() + } + } + +} diff --git a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala index 3dc971dfa..14fa6285d 100644 --- a/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala +++ b/main/src/main/scala/sbt/SplitExpressionsNoBlankies.scala @@ -4,6 +4,7 @@ import java.io.File import scala.annotation.tailrec import SplitExpressionsNoBlankies._ +import scala.reflect.runtime.universe._ object SplitExpressionsNoBlankies { val END_OF_LINE_CHAR = '\n' @@ -11,11 +12,12 @@ object SplitExpressionsNoBlankies { } case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { - val (imports, settings) = splitExpressions(file, lines) + //settingsTrees needed for "session save" + val (imports, settings, settingsTrees) = splitExpressions(file, lines) - private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)]) = { + private def splitExpressions(file: File, lines: Seq[String]): (Seq[(String, Int)], Seq[(String, LineRange)], Seq[(String, Tree)]) = { import scala.reflect.runtime._ - import scala.reflect.runtime.universe._ + import scala.tools.reflect.ToolBoxError import scala.tools.reflect.ToolBox import scala.compat.Platform.EOL @@ -34,12 +36,14 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { toolbox.parse(merged) } catch { case e: ToolBoxError => - ConsoleLogger(System.err).trace(e) val seq = toolbox.frontEnd.infos.map { i => s"""[$fileName]:${i.pos.line}: ${i.msg}""" } throw new MessageOnlyException( - s"""${seq.mkString(EOL)}""".stripMargin) + s"""====== + |$merged + |====== + |${seq.mkString(EOL)}""".stripMargin) } val parsedTrees = parsed match { case Block(stmt, expr) => @@ -73,19 +77,18 @@ case class SplitExpressionsNoBlankies(file: File, lines: Seq[String]) { statement } - def convertStatement(t: Tree): Option[(String, LineRange)] = + def convertStatement(t: Tree): Option[(String, Tree, LineRange)] = if (t.pos.isDefined) { val originalStatement = merged.substring(t.pos.start, t.pos.end) val statement = parseStatementAgain(t, originalStatement) val numberLines = statement.count(c => c == END_OF_LINE_CHAR) - Some((statement, LineRange(t.pos.line - 1, t.pos.line + numberLines))) + Some((statement, t, LineRange(t.pos.line - 1, t.pos.line + numberLines))) } else { None } - - (imports map convertImport, statements flatMap convertStatement) + val statementsTreeLineRange = statements flatMap convertStatement + (imports map convertImport, statementsTreeLineRange.map(t => (t._1, t._3)), statementsTreeLineRange.map(t => (t._1, t._2))) } - } /** @@ -143,14 +146,14 @@ private[sbt] object BugInParser { /** * #ToolBox#parse(String) will fail for xml sequence: *
- * val xml =- * At least brackets have to be added + * val xml =txt- * rr - *
- * val xml = (+ * val xml = (txt- * rr) - *