mirror of
https://github.com/fluencelabs/aqua.git
synced 2025-03-15 11:40:50 +00:00
120 improve output (#137)
This commit is contained in:
parent
9990eb0a66
commit
f34cd3a4e2
@ -1,28 +1,29 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
|
|
||||||
import aqua.parser.lift.{FileSpan, LiftParser, Span}
|
import aqua.parser.lift.{FileSpan, LiftParser, Span}
|
||||||
|
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
|
||||||
|
import cats.Eval
|
||||||
import cats.data.ValidatedNec
|
import cats.data.ValidatedNec
|
||||||
|
import cats.parse.LocationMap
|
||||||
|
|
||||||
object Aqua {
|
object Aqua {
|
||||||
|
|
||||||
def parseString(input: String): ValidatedNec[AquaError, Ast[Span.F]] =
|
|
||||||
Ast
|
|
||||||
.fromString[Span.F](input)
|
|
||||||
.leftMap(_.map {
|
|
||||||
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
|
|
||||||
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
|
|
||||||
case LexerError(pe) => SyntaxError(pe.failedAtOffset, pe.expected)
|
|
||||||
})
|
|
||||||
|
|
||||||
def parseFileString(name: String, input: String): ValidatedNec[AquaError, Ast[FileSpan.F]] = {
|
def parseFileString(name: String, input: String): ValidatedNec[AquaError, Ast[FileSpan.F]] = {
|
||||||
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
|
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
|
||||||
Ast
|
Ast
|
||||||
.fromString[FileSpan.F](input)
|
.fromString[FileSpan.F](input)
|
||||||
.leftMap(_.map {
|
.leftMap(_.map {
|
||||||
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1.span, message)
|
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
|
||||||
case FuncReturnError(point, message) => CustomSyntaxError(point._1.span, message)
|
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
|
||||||
case LexerError(pe) => SyntaxError(pe.failedAtOffset, pe.expected)
|
case LexerError(pe) =>
|
||||||
|
val fileSpan =
|
||||||
|
FileSpan(
|
||||||
|
name,
|
||||||
|
input,
|
||||||
|
Eval.later(LocationMap(input)),
|
||||||
|
Span(pe.failedAtOffset, pe.failedAtOffset + 1)
|
||||||
|
)
|
||||||
|
SyntaxError(fileSpan, pe.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package aqua
|
|||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import cats.data.Validated
|
import cats.data.Validated
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.effect.std.Console
|
import cats.effect.std.{Console => ConsoleEff}
|
||||||
import cats.syntax.apply._
|
import cats.syntax.apply._
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
import com.monovore.decline.Opts
|
import com.monovore.decline.Opts
|
||||||
@ -11,12 +11,22 @@ import com.monovore.decline.effect.CommandIOApp
|
|||||||
import fs2.io.file.Files
|
import fs2.io.file.Files
|
||||||
import org.typelevel.log4cats.slf4j.Slf4jLogger
|
import org.typelevel.log4cats.slf4j.Slf4jLogger
|
||||||
import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger}
|
import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger}
|
||||||
import wvlet.log.{LogSupport, Logger => WLogger}
|
import wvlet.log.LogFormatter.{appendStackTrace, highlightLog}
|
||||||
|
import wvlet.log.{LogFormatter, LogRecord, LogSupport, Logger => WLogger}
|
||||||
|
|
||||||
|
object CustomLogFormatter extends LogFormatter {
|
||||||
|
|
||||||
|
override def formatLog(r: LogRecord): String = {
|
||||||
|
val log =
|
||||||
|
s"[${highlightLog(r.level, r.level.name)}] ${highlightLog(r.level, r.getMessage)}"
|
||||||
|
appendStackTrace(log, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
object AquaCli extends IOApp with LogSupport {
|
object AquaCli extends IOApp with LogSupport {
|
||||||
import AppOps._
|
import AppOps._
|
||||||
|
|
||||||
def main[F[_]: Concurrent: Files: Console: Logger]: Opts[F[ExitCode]] = {
|
def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
|
||||||
versionOpt
|
versionOpt
|
||||||
.as(
|
.as(
|
||||||
versionAndExit
|
versionAndExit
|
||||||
@ -34,6 +44,7 @@ object AquaCli extends IOApp with LogSupport {
|
|||||||
logLevelOpt
|
logLevelOpt
|
||||||
).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) =>
|
).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) =>
|
||||||
WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel))
|
WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel))
|
||||||
|
WLogger.setDefaultFormatter(CustomLogFormatter)
|
||||||
|
|
||||||
// if there is `--help` or `--version` flag - show help and version
|
// if there is `--help` or `--version` flag - show help and version
|
||||||
// otherwise continue program execution
|
// otherwise continue program execution
|
||||||
@ -52,8 +63,8 @@ object AquaCli extends IOApp with LogSupport {
|
|||||||
case Validated.Invalid(errs) =>
|
case Validated.Invalid(errs) =>
|
||||||
errs.map(println)
|
errs.map(println)
|
||||||
ExitCode.Error
|
ExitCode.Error
|
||||||
case Validated.Valid(res) =>
|
case Validated.Valid(results) =>
|
||||||
res.map(println)
|
results.map(println)
|
||||||
ExitCode.Success
|
ExitCode.Success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import aqua.parser.lexer.Token
|
|||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import aqua.semantics.{CompilerState, Semantics}
|
import aqua.semantics.{CompilerState, Semantics}
|
||||||
import cats.Applicative
|
import cats.Applicative
|
||||||
|
import cats.data.Validated.{Invalid, Valid}
|
||||||
import cats.data._
|
import cats.data._
|
||||||
import cats.effect.kernel.Concurrent
|
import cats.effect.kernel.Concurrent
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
@ -18,19 +19,30 @@ import cats.syntax.monoid._
|
|||||||
import cats.syntax.show._
|
import cats.syntax.show._
|
||||||
import fs2.io.file.Files
|
import fs2.io.file.Files
|
||||||
import fs2.text
|
import fs2.text
|
||||||
|
import wvlet.log.LogSupport
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
object AquaCompiler {
|
object AquaCompiler extends LogSupport {
|
||||||
sealed trait CompileTarget
|
sealed trait CompileTarget
|
||||||
case object TypescriptTarget extends CompileTarget
|
case object TypescriptTarget extends CompileTarget
|
||||||
case object AirTarget extends CompileTarget
|
case object AirTarget extends CompileTarget
|
||||||
|
|
||||||
case class Prepared(target: String => Path, model: ScriptModel) {
|
case class Prepared(srcFile: Path, srcDir: Path, model: ScriptModel) {
|
||||||
|
|
||||||
def hasOutput(target: CompileTarget): Boolean = target match {
|
def hasOutput(target: CompileTarget): Boolean = target match {
|
||||||
case _ => model.funcs.nonEmpty
|
case _ => model.funcs.nonEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def targetPath(ext: String): Validated[Throwable, Path] =
|
||||||
|
Validated.catchNonFatal {
|
||||||
|
val fileName = srcFile.getFileName
|
||||||
|
if (fileName == null) {
|
||||||
|
throw new Exception(s"Unexpected: 'fileName' is null in path $srcFile")
|
||||||
|
} else {
|
||||||
|
srcDir.resolve(fileName.toString.stripSuffix(".aqua") + s".$ext")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def prepareFiles[F[_]: Files: Concurrent](
|
def prepareFiles[F[_]: Files: Concurrent](
|
||||||
@ -69,7 +81,19 @@ object AquaCompiler {
|
|||||||
(errs ++ showProcErrors(proc.errors), preps)
|
(errs ++ showProcErrors(proc.errors), preps)
|
||||||
|
|
||||||
case (_, model: ScriptModel) =>
|
case (_, model: ScriptModel) =>
|
||||||
(errs, preps :+ Prepared(modId.targetPath(srcPath, targetPath, _), model))
|
val src = Validated.catchNonFatal {
|
||||||
|
targetPath.toAbsolutePath
|
||||||
|
.normalize()
|
||||||
|
.resolve(
|
||||||
|
srcPath.toAbsolutePath
|
||||||
|
.normalize()
|
||||||
|
.relativize(modId.file.toAbsolutePath.normalize())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
src match {
|
||||||
|
case Validated.Invalid(t) => (errs :+ t.getMessage, preps)
|
||||||
|
case Validated.Valid(s) => (errs, preps :+ Prepared(s, srcPath, model))
|
||||||
|
}
|
||||||
|
|
||||||
case (_, model) =>
|
case (_, model) =>
|
||||||
(
|
(
|
||||||
@ -95,7 +119,7 @@ object AquaCompiler {
|
|||||||
): Chain[String] =
|
): Chain[String] =
|
||||||
errors.map(err =>
|
errors.map(err =>
|
||||||
err._1.unit._1
|
err._1.unit._1
|
||||||
.focus(1)
|
.focus(2)
|
||||||
.map(_.toConsoleStr(err._2, Console.CYAN))
|
.map(_.toConsoleStr(err._2, Console.CYAN))
|
||||||
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
||||||
)
|
)
|
||||||
@ -108,28 +132,47 @@ object AquaCompiler {
|
|||||||
bodyConfig: BodyConfig
|
bodyConfig: BodyConfig
|
||||||
): F[ValidatedNec[String, Chain[String]]] =
|
): F[ValidatedNec[String, Chain[String]]] =
|
||||||
prepareFiles(srcPath, imports, targetPath)
|
prepareFiles(srcPath, imports, targetPath)
|
||||||
.map(_.map(_.filter(_.hasOutput(compileTo))))
|
.map(_.map(_.filter { p =>
|
||||||
|
val hasOutput = p.hasOutput(compileTo)
|
||||||
|
if (!hasOutput) info(s"Source ${p.srcFile}: compilation OK (nothing to emit)")
|
||||||
|
else info(s"Source ${p.srcFile}: compilation OK (${p.model.funcs.length} functions)")
|
||||||
|
hasOutput
|
||||||
|
}))
|
||||||
.flatMap[ValidatedNec[String, Chain[String]]] {
|
.flatMap[ValidatedNec[String, Chain[String]]] {
|
||||||
case Validated.Invalid(e) =>
|
case Validated.Invalid(e) =>
|
||||||
Applicative[F].pure(Validated.invalid(e))
|
Applicative[F].pure(Validated.invalid(e))
|
||||||
case Validated.Valid(preps) =>
|
case Validated.Valid(preps) =>
|
||||||
(compileTo match {
|
(compileTo match {
|
||||||
case TypescriptTarget =>
|
case TypescriptTarget =>
|
||||||
preps
|
preps.map { p =>
|
||||||
.map(p => writeFile(p.target("ts"), TypescriptFile(p.model).generateTS(bodyConfig)))
|
val tpV = p.targetPath("ts")
|
||||||
|
tpV match {
|
||||||
|
case Invalid(t) =>
|
||||||
|
EitherT.pure(t.getMessage)
|
||||||
|
case Valid(tp) =>
|
||||||
|
writeFile(tp, TypescriptFile(p.model).generateTS(bodyConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TODO add function name to AirTarget class
|
// TODO add function name to AirTarget class
|
||||||
case AirTarget =>
|
case AirTarget =>
|
||||||
preps
|
preps
|
||||||
.flatMap(p =>
|
.flatMap(p =>
|
||||||
p.model.resolveFunctions.map { fc =>
|
p.model.resolveFunctions
|
||||||
fc.funcName -> FuncAirGen(fc).generateAir(bodyConfig).show
|
.map(fc => FuncAirGen(fc).generateAir(bodyConfig).show)
|
||||||
}.map { case (n, g) =>
|
.map { generated =>
|
||||||
writeFile(
|
val tpV = p.targetPath("ts")
|
||||||
p.target(n + ".air"),
|
tpV match {
|
||||||
g
|
case Invalid(t) =>
|
||||||
)
|
EitherT.pure(t.getMessage)
|
||||||
}
|
case Valid(tp) =>
|
||||||
|
writeFile(
|
||||||
|
tp,
|
||||||
|
generated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
}).foldLeft(
|
}).foldLeft(
|
||||||
@ -140,7 +183,7 @@ object AquaCompiler {
|
|||||||
w <- writeET.value
|
w <- writeET.value
|
||||||
} yield (a, w) match {
|
} yield (a, w) match {
|
||||||
case (Left(errs), Left(err)) => Left(errs :+ err)
|
case (Left(errs), Left(err)) => Left(errs :+ err)
|
||||||
case (Right(res), Right(r)) => Right(res :+ r)
|
case (Right(res), Right(_)) => Right(res)
|
||||||
case (Left(errs), _) => Left(errs)
|
case (Left(errs), _) => Left(errs)
|
||||||
case (_, Left(err)) => Left(NonEmptyChain.of(err))
|
case (_, Left(err)) => Left(NonEmptyChain.of(err))
|
||||||
})
|
})
|
||||||
@ -149,9 +192,9 @@ object AquaCompiler {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, String] =
|
def writeFile[F[_]: Files: Concurrent](file: Path, content: String): EitherT[F, String, Unit] =
|
||||||
EitherT.right[String](Files[F].deleteIfExists(file)) >>
|
EitherT.right[String](Files[F].deleteIfExists(file)) >>
|
||||||
EitherT[F, String, String](
|
EitherT[F, String, Unit](
|
||||||
fs2.Stream
|
fs2.Stream
|
||||||
.emit(
|
.emit(
|
||||||
content
|
content
|
||||||
@ -165,7 +208,7 @@ object AquaCompiler {
|
|||||||
}
|
}
|
||||||
.compile
|
.compile
|
||||||
.drain
|
.drain
|
||||||
.map(_ => Right(s"Compiled $file"))
|
.map(_ => Right(()))
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
import aqua.parser.lift.Span
|
import aqua.parser.lift.FileSpan
|
||||||
import cats.Eval
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
import cats.parse.LocationMap
|
|
||||||
import cats.parse.Parser.Expectation
|
import cats.parse.Parser.Expectation
|
||||||
|
|
||||||
sealed trait AquaError {
|
sealed trait AquaError {
|
||||||
def showForConsole(script: String): String
|
def showForConsole: String
|
||||||
}
|
}
|
||||||
|
|
||||||
case class CustomSyntaxError(span: Span, message: String) extends AquaError {
|
case class CustomSyntaxError(span: FileSpan, message: String) extends AquaError {
|
||||||
|
|
||||||
override def showForConsole(script: String): String =
|
override def showForConsole: String =
|
||||||
span
|
span
|
||||||
.focus(Eval.later(LocationMap(script)), 2)
|
.focus(3)
|
||||||
.map(
|
.map(
|
||||||
_.toConsoleStr(
|
_.toConsoleStr(
|
||||||
message,
|
message,
|
||||||
@ -27,13 +25,13 @@ case class CustomSyntaxError(span: Span, message: String) extends AquaError {
|
|||||||
) + Console.RESET + "\n"
|
) + Console.RESET + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) extends AquaError {
|
case class SyntaxError(span: FileSpan, expectations: NonEmptyList[Expectation]) extends AquaError {
|
||||||
|
|
||||||
override def showForConsole(script: String): String =
|
override def showForConsole: String =
|
||||||
Span(offset, offset + 1)
|
span
|
||||||
.focus(Eval.later(LocationMap(script)), 2)
|
.focus(3)
|
||||||
.map(
|
.map(spanFocus =>
|
||||||
_.toConsoleStr(
|
spanFocus.toConsoleStr(
|
||||||
s"Syntax error, expected: ${expectations.toList.mkString(", ")}",
|
s"Syntax error, expected: ${expectations.toList.mkString(", ")}",
|
||||||
Console.RED
|
Console.RED
|
||||||
)
|
)
|
||||||
@ -43,12 +41,3 @@ case class SyntaxError(offset: Int, expectations: NonEmptyList[Expectation]) ext
|
|||||||
.mkString(", ")
|
.mkString(", ")
|
||||||
) + Console.RESET + "\n"
|
) + Console.RESET + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
case class CompilerError(span: Span, hint: String) extends AquaError {
|
|
||||||
|
|
||||||
override def showForConsole(script: String): String =
|
|
||||||
span
|
|
||||||
.focus(Eval.later(LocationMap(script)), 1)
|
|
||||||
.map(_.toConsoleStr(hint, Console.CYAN))
|
|
||||||
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
|
||||||
}
|
|
||||||
|
@ -25,8 +25,8 @@ object Test extends IOApp.Simple {
|
|||||||
.map {
|
.map {
|
||||||
case Validated.Invalid(errs) =>
|
case Validated.Invalid(errs) =>
|
||||||
errs.map(println)
|
errs.map(println)
|
||||||
case Validated.Valid(res) =>
|
case Validated.Valid(_) =>
|
||||||
res.map(println)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,10 @@ import aqua.parser.head.ImportExpr
|
|||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import cats.data.{EitherT, NonEmptyChain}
|
import cats.data.{EitherT, NonEmptyChain}
|
||||||
import cats.effect.Concurrent
|
import cats.effect.Concurrent
|
||||||
|
import cats.syntax.apply._
|
||||||
|
import cats.syntax.functor._
|
||||||
import fs2.io.file.Files
|
import fs2.io.file.Files
|
||||||
import fs2.text
|
import fs2.text
|
||||||
import cats.syntax.functor._
|
|
||||||
import cats.syntax.apply._
|
|
||||||
|
|
||||||
import java.nio.file.{Path, Paths}
|
import java.nio.file.{Path, Paths}
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ object AquaFile {
|
|||||||
.map(source -> _)
|
.map(source -> _)
|
||||||
.toEither
|
.toEither
|
||||||
.left
|
.left
|
||||||
.map(AquaScriptErrors(file.toString, source, _))
|
.map(AquaScriptErrors(_))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -34,9 +34,8 @@ case class Unresolvable(msg: String) extends AquaFileError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO there should be no AquaErrors, as they does not fit
|
// TODO there should be no AquaErrors, as they does not fit
|
||||||
case class AquaScriptErrors(name: String, script: String, errors: NonEmptyChain[AquaError])
|
case class AquaScriptErrors(errors: NonEmptyChain[AquaError]) extends AquaFileError {
|
||||||
extends AquaFileError {
|
|
||||||
|
|
||||||
override def showForConsole: String =
|
override def showForConsole: String =
|
||||||
errors.map(_.showForConsole(script)).toChain.toList.mkString("\n")
|
errors.map(_.showForConsole).toChain.toList.mkString("\n")
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,7 @@ import cats.syntax.applicative._
|
|||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
case class FileModuleId(file: Path) {
|
case class FileModuleId(file: Path) {}
|
||||||
|
|
||||||
def targetPath(src: Path, target: Path, ext: String): Path = {
|
|
||||||
val aqua =
|
|
||||||
target.toAbsolutePath
|
|
||||||
.normalize()
|
|
||||||
.resolve(src.toAbsolutePath.normalize().relativize(file.toAbsolutePath.normalize()))
|
|
||||||
aqua.getParent.resolve(aqua.getFileName.toString.stripSuffix(".aqua") + s".$ext")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object FileModuleId {
|
object FileModuleId {
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package aqua.parser.lift
|
package aqua.parser.lift
|
||||||
|
|
||||||
import cats.{Comonad, Eval}
|
|
||||||
import cats.parse.{LocationMap, Parser => P}
|
import cats.parse.{LocationMap, Parser => P}
|
||||||
|
import cats.{Comonad, Eval}
|
||||||
|
|
||||||
import scala.language.implicitConversions
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
|
// TODO: rewrite FileSpan and Span under one trait
|
||||||
case class FileSpan(name: String, source: String, locationMap: Eval[LocationMap], span: Span) {
|
case class FileSpan(name: String, source: String, locationMap: Eval[LocationMap], span: Span) {
|
||||||
|
|
||||||
def focus(ctx: Int): Option[FileSpan.Focus] =
|
def focus(ctx: Int): Option[FileSpan.Focus] =
|
||||||
@ -16,11 +17,12 @@ object FileSpan {
|
|||||||
case class Focus(name: String, locationMap: Eval[LocationMap], ctx: Int, spanFocus: Span.Focus) {
|
case class Focus(name: String, locationMap: Eval[LocationMap], ctx: Int, spanFocus: Span.Focus) {
|
||||||
|
|
||||||
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String =
|
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String =
|
||||||
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" + spanFocus.toConsoleStr(
|
s"$name:${spanFocus.line._1 + 1}:${spanFocus.column + 1}\n" +
|
||||||
msg,
|
spanFocus.toConsoleStr(
|
||||||
onLeft,
|
msg,
|
||||||
onRight
|
onLeft,
|
||||||
)
|
onRight
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type F[T] = (FileSpan, T)
|
type F[T] = (FileSpan, T)
|
||||||
|
@ -12,18 +12,23 @@ case class Span(startIndex: Int, endIndex: Int) {
|
|||||||
map.toLineCol(startIndex).flatMap { case (line, column) =>
|
map.toLineCol(startIndex).flatMap { case (line, column) =>
|
||||||
map
|
map
|
||||||
.getLine(line)
|
.getLine(line)
|
||||||
.map(l =>
|
.map { l =>
|
||||||
|
val pre =
|
||||||
|
(Math.max(0, line - ctx) until line).map(i => map.getLine(i).map(i -> _)).toList.flatten
|
||||||
|
val linePos = {
|
||||||
|
val (l1, l2) = l.splitAt(column)
|
||||||
|
val (lc, l3) = l2.splitAt(endIndex - startIndex)
|
||||||
|
(line, l1, lc, l3)
|
||||||
|
}
|
||||||
|
val post =
|
||||||
|
((line + 1) to (line + ctx)).map(i => map.getLine(i).map(i -> _)).toList.flatten
|
||||||
Span.Focus(
|
Span.Focus(
|
||||||
(Math
|
pre,
|
||||||
.max(0, line - ctx) until line).map(i => map.getLine(i).map(i -> _)).toList.flatten, {
|
linePos,
|
||||||
val (l1, l2) = l.splitAt(column)
|
post,
|
||||||
val (lc, l3) = l2.splitAt(endIndex - startIndex)
|
|
||||||
(line, l1, lc, l3)
|
|
||||||
},
|
|
||||||
((line + 1) to (line + ctx)).map(i => map.getLine(i).map(i -> _)).toList.flatten,
|
|
||||||
column
|
column
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +53,11 @@ object Span {
|
|||||||
onLeft + s + (" " * (lastNSize - s.length)) + onRight + " "
|
onLeft + s + (" " * (lastNSize - s.length)) + onRight + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
def toConsoleStr(msg: String, onLeft: String, onRight: String = Console.RESET): String = {
|
def toConsoleStr(
|
||||||
|
msg: String,
|
||||||
|
onLeft: String,
|
||||||
|
onRight: String = Console.RESET
|
||||||
|
): String = {
|
||||||
val line3Length = line._3.length
|
val line3Length = line._3.length
|
||||||
val line3Mult = if (line3Length == 0) 1 else line3Length
|
val line3Mult = if (line3Length == 0) 1 else line3Length
|
||||||
pre.map(formatLine(_, onLeft, onRight)).mkString("\n") +
|
pre.map(formatLine(_, onLeft, onRight)).mkString("\n") +
|
||||||
|
Loading…
x
Reference in New Issue
Block a user