diff --git a/main/Build.scala b/main/Build.scala index c6d1f90aa..a004e72bf 100644 --- a/main/Build.scala +++ b/main/Build.scala @@ -10,7 +10,7 @@ package sbt import complete.DefaultParsers.validID import Compiler.Compilers import Keys.{globalBaseDirectory, globalPluginsDirectory, globalSettingsDirectory, stagingDirectory, Streams} - import Project.{ScopedKey, Setting, SourceCoord} + import Project.{ScopedKey, Setting} import Keys.{globalBaseDirectory, Streams} import Scope.GlobalScope import scala.annotation.tailrec @@ -73,8 +73,8 @@ object EvaluateConfigurations def evaluateConfiguration(eval: Eval, name: String, lines: Seq[String], imports: Seq[String], offset: Int): ClassLoader => Seq[Setting[_]] = { val (importExpressions, settingExpressions) = splitExpressions(lines) - val settings = addOffset(offset, settingExpressions) map { case (settingExpression,line) => - evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ addOffset(offset, importExpressions)), settingExpression, line) + val settings = addOffsetToRange(offset, settingExpressions) map { case (settingExpression,range) => + evaluateSetting(eval, name, (imports.map(s => (s, -1)) ++ addOffset(offset, importExpressions)), settingExpression, range) } flatten(settings) } @@ -82,24 +82,26 @@ object EvaluateConfigurations loader => mksettings.flatMap(_ apply loader) def addOffset(offset: Int, lines: Seq[(String,Int)]): Seq[(String,Int)] = lines.map { case (s, i) => (s, i + offset) } + def addOffsetToRange(offset: Int, ranges: Seq[(String,LineRange)]): Seq[(String,LineRange)] = + ranges.map { case (s, r) => (s, r shift offset) } - def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, line: Int): ClassLoader => Seq[Setting[_]] = + def evaluateSetting(eval: Eval, name: String, imports: Seq[(String,Int)], expression: String, range: LineRange): ClassLoader => Seq[Setting[_]] = { val result = try { - eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.SettingsDefinition"), line = line) + eval.eval(expression, imports = new EvalImports(imports, name), srcName = name, tpeName = Some("sbt.Project.SettingsDefinition"), line = range.start) } catch { case e: sbt.compiler.EvalException => throw new MessageOnlyException(e.getMessage) } loader => { - val coord = SourceCoord(name, line + 1) - result.getValue(loader).asInstanceOf[Project.SettingsDefinition].settings map (_ withPos coord) + val pos = RangePosition(name, range shift 1) + result.getValue(loader).asInstanceOf[Project.SettingsDefinition].settings map (_ withPos pos) } } private[this] def isSpace = (c: Char) => Character isWhitespace c private[this] def fstS(f: String => Boolean): ((String,Int)) => Boolean = { case (s,i) => f(s) } private[this] def firstNonSpaceIs(lit: String) = (_: String).view.dropWhile(isSpace).startsWith(lit) private[this] def or[A](a: A => Boolean, b: A => Boolean): A => Boolean = in => a(in) || b(in) - def splitExpressions(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,Int)]) = + def splitExpressions(lines: Seq[String]): (Seq[(String,Int)], Seq[(String,LineRange)]) = { val blank = (_: String).forall(isSpace) val isImport = firstNonSpaceIs("import ") @@ -110,16 +112,16 @@ object EvaluateConfigurations val (imports, settings) = lines.zipWithIndex span importOrBlank (imports filterNot fstS( blankOrComment ), groupedLines(settings, blank, blankOrComment)) } - def groupedLines(lines: Seq[(String,Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String,Int)] = + def groupedLines(lines: Seq[(String,Int)], delimiter: String => Boolean, skipInitial: String => Boolean): Seq[(String,LineRange)] = { val fdelim = fstS(delimiter) - @tailrec def group0(lines: Seq[(String,Int)], accum: Seq[(String,Int)]): Seq[(String,Int)] = + @tailrec def group0(lines: Seq[(String,Int)], accum: Seq[(String,LineRange)]): Seq[(String,LineRange)] = if(lines.isEmpty) accum.reverse else { val start = lines dropWhile fstS( skipInitial ) val (next, tail) = start.span { case (s,_) => !delimiter(s) } - val grouped = if(next.isEmpty) accum else (next.map(_._1).mkString("\n"), next.head._2) +: accum + val grouped = if(next.isEmpty) accum else (next.map(_._1).mkString("\n"), LineRange(next.head._2, next.last._2 + 1)) +: accum group0(tail, grouped) } group0(lines, Nil) diff --git a/main/Main.scala b/main/Main.scala index 475e5db7c..bfa7dafaf 100644 --- a/main/Main.scala +++ b/main/Main.scala @@ -166,7 +166,7 @@ object BuiltinCommands def set = Command(SetCommand, setBrief, setDetailed)(setParser) { case (s, (all, arg)) => val extracted = Project extract s import extracted._ - val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "", imports(extracted), arg, 0)(currentLoader) + val settings = EvaluateConfigurations.evaluateSetting(session.currentEval(), "", imports(extracted), arg, LineRange(0,0))(currentLoader) val newSession = if(all) Project.setAll(extracted, settings) else setThis(s, extracted, settings, arg) reapply(newSession, structure, s) } diff --git a/main/Project.scala b/main/Project.scala index acb33417d..38eed14a6 100755 --- a/main/Project.scala +++ b/main/Project.scala @@ -275,7 +275,7 @@ object Project extends Init[Scope] with ProjectExtra val comp = compiled(structure.settings, actual)(structure.delegates, structure.scopeLocal, display) val definedAt = comp get scoped map { c => def fmt(s: Setting[_]) = s.pos match { - case SourceCoord(fileName, line) => Some(fileName + ":" + line) + case pos: FilePosition => Some(pos.path + ":" + pos.startLine) case NoPosition => None } val posDefined = c.settings.map(fmt).flatten diff --git a/util/collection/Positions.scala b/util/collection/Positions.scala new file mode 100755 index 000000000..2cd3f77e1 --- /dev/null +++ b/util/collection/Positions.scala @@ -0,0 +1,20 @@ +package sbt + +sealed trait SourcePosition + +sealed trait FilePosition { + def path: String + def startLine: Int +} + +case object NoPosition extends SourcePosition + +final case class LinePosition(path: String, startLine: Int) extends SourcePosition with FilePosition + +final case class LineRange(start: Int, end: Int) { + def shift(n: Int) = new LineRange(start + n, end + n) +} + +final case class RangePosition(path: String, range: LineRange) extends SourcePosition with FilePosition { + def startLine = range.start +} diff --git a/util/collection/Settings.scala b/util/collection/Settings.scala index 210c8e3c1..6df7291af 100644 --- a/util/collection/Settings.scala +++ b/util/collection/Settings.scala @@ -255,14 +255,10 @@ trait Init[Scope] def mapKey(g: MapScoped): Setting[T] = new Setting(g(key), init, pos) def mapInit(f: (ScopedKey[T], T) => T): Setting[T] = new Setting(key, init(t => f(key,t)), pos) def mapConstant(g: MapConstant): Setting[T] = new Setting(key, init mapConstant g, pos) - def withPos(pos: SourceCoord) = new Setting(key, init, pos) + def withPos(pos: SourcePosition) = new Setting(key, init, pos) override def toString = "setting(" + key + ") at " + pos } - sealed trait SourcePosition - case object NoPosition extends SourcePosition - final case class SourceCoord(fileName: String, line: Int) extends SourcePosition - // mainly for reducing generated class count private[this] def validateReferencedT(g: ValidateRef) = new (Initialize ~> ValidatedInit) { def apply[T](i: Initialize[T]) = i validateReferenced g }