diff --git a/src/main/scala/afenton/bazel/bsp/BazelBspApp.scala b/src/main/scala/afenton/bazel/bsp/BazelBspApp.scala index ebf6588..1318542 100644 --- a/src/main/scala/afenton/bazel/bsp/BazelBspApp.scala +++ b/src/main/scala/afenton/bazel/bsp/BazelBspApp.scala @@ -140,14 +140,6 @@ object BazelBspApp case u: Unit => IO.unit } - private def processErrStream( - errPipe: Pipe[IO, String, Unit], - errQ: Queue[IO, String] - ): Stream[IO, Unit] = - Stream - .fromQueueUnterminated(errQ, 100) - .through(errPipe) - private def processOutStream( outPipe: Pipe[IO, String, Unit], outQ: Queue[IO, Message], @@ -165,22 +157,15 @@ object BazelBspApp errPipe: Pipe[IO, String, Unit] ): IO[ExitCode] = val program = for - errQ <- Queue.bounded[IO, String](100) + loggerStream <- Logger.queue(100, errPipe, verbose) + (logger, logStream) = loggerStream outQ <- Queue.bounded[IO, Message](100) - logger = Logger.toQueue(errQ, verbose) client = BspClient.toQueue(outQ, logger) - stateRef <- Ref.of[IO, BazelBspServer.ServerState]( - BazelBspServer.defaultState - ) - server = new BazelBspServer( - client, - logger, - stateRef - ) + server <- BazelBspServer.create(client, logger) all = Stream( processInStream(inStream, server, outQ, logger), processOutStream(outPipe, outQ, logger), - processErrStream(errPipe, errQ) + logStream ).parJoin(3) _ <- all.compile.drain yield () diff --git a/src/main/scala/afenton/bazel/bsp/BazelBspServer.scala b/src/main/scala/afenton/bazel/bsp/BazelBspServer.scala index 58d2823..b578d0f 100644 --- a/src/main/scala/afenton/bazel/bsp/BazelBspServer.scala +++ b/src/main/scala/afenton/bazel/bsp/BazelBspServer.scala @@ -307,7 +307,7 @@ class BazelBspServer( end BazelBspServer -object BazelBspServer: +object BazelBspServer: case class ServerState( targetSourceMap: BazelBspServer.TargetSourceMap, @@ -317,9 +317,15 @@ object BazelBspServer: targets: List[BuildTarget] ) - def defaultState: ServerState = + def defaultState: ServerState = ServerState(BazelBspServer.TargetSourceMap.empty, Nil, None, None, Nil) + def create(client: BspClient, logger: Logger): IO[BazelBspServer] = + Ref.of[IO, BazelBspServer.ServerState](defaultState) + .map { stateRef => + BazelBspServer(client, logger, stateRef) + } + protected case class TargetSourceMap( val _targetSources: Map[BuildTargetIdentifier, List[ TextDocumentIdentifier diff --git a/src/main/scala/afenton/bazel/bsp/Logger.scala b/src/main/scala/afenton/bazel/bsp/Logger.scala index cc764df..237299b 100644 --- a/src/main/scala/afenton/bazel/bsp/Logger.scala +++ b/src/main/scala/afenton/bazel/bsp/Logger.scala @@ -1,48 +1,65 @@ package afenton.bazel.bsp +import fs2.{Pipe, Stream} import cats.effect.IO -import cats.effect.std.Queue +import cats.effect.std.{Queue, QueueSink} import scala.io.AnsiColor trait Logger: - def trace(msg: String*): IO[Unit] - def info(msg: String*): IO[Unit] - def error(msg: String*): IO[Unit] + def trace(msg: => String): IO[Unit] + def info(msg: => String): IO[Unit] + def error(msg: => String): IO[Unit] object Logger: enum Level: case Trace, Info, Error - def noOp = new Logger { - def trace(msgs: String*): IO[Unit] = IO { () } - def info(msgs: String*): IO[Unit] = trace(msgs*) - def error(msgs: String*): IO[Unit] = trace(msgs*) + val noOp: Logger = new Logger { + def trace(msgs: => String): IO[Unit] = IO.unit + def info(msgs: => String): IO[Unit] = IO.unit + def error(msgs: => String): IO[Unit] = IO.unit } - def toQueue(errQ: Queue[IO, String], verbose: Boolean): Logger = - QueueLogger(errQ, verbose) + def queue(size: Int, out: Pipe[IO, String, Unit], verbose: Boolean): IO[(Logger, Stream[IO, Unit])] = + Queue.bounded[IO, () => String](size) + .map { errQ => + val logger = if verbose then QueueVerboseLogger(errQ) else QueueQuietLogger(errQ) - private class QueueLogger(errQ: Queue[IO, String], verbose: Boolean) extends Logger: - private def format(level: Logger.Level, msgs: Seq[String]) = - def fmt(level: String, color: String, msgs: Seq[String]) = - val withVisibleLineEndings = - msgs.map(_.replace("\r\n", "[CRLF]\n")).mkString("\n") + val resStream = Stream + .fromQueueUnterminated(errQ, size) + .through { s => out(s.map(_.apply())) } - s"[${color}${level}${AnsiColor.RESET}] ${color}${withVisibleLineEndings}${AnsiColor.RESET}" - - level match { - case Logger.Level.Trace => fmt("trace", AnsiColor.CYAN, msgs) - case Logger.Level.Info => fmt("info", AnsiColor.GREEN, msgs) - case Logger.Level.Error => fmt("error", AnsiColor.RED, msgs) + (logger, resStream) } - def trace(msgs: String*): IO[Unit] = - if verbose then errQ.offer(format(Logger.Level.Trace, msgs)) - else IO.unit + private inline def fmt(inline level: String, inline color: String, msgs: String) = + val withVisibleLineEndings = msgs.replace("\r\n", "[CRLF]\n") + + s"[${color}${level}${AnsiColor.RESET}] ${color}${withVisibleLineEndings}${AnsiColor.RESET}" + + private inline def format(inline level: Logger.Level, msgs: String) = + inline level match { + case Logger.Level.Trace => fmt("trace", AnsiColor.CYAN, msgs) + case Logger.Level.Info => fmt("info", AnsiColor.GREEN, msgs) + case Logger.Level.Error => fmt("error", AnsiColor.RED, msgs) + } + + private class QueueVerboseLogger(errQ: QueueSink[IO, () => String]) extends Logger: + def trace(msgs: => String): IO[Unit] = + errQ.offer(() => format(Logger.Level.Trace, msgs)) + + def info(msgs: => String): IO[Unit] = + errQ.offer(() => format(Logger.Level.Info, msgs)) + + def error(msgs: => String): IO[Unit] = + errQ.offer(() => format(Logger.Level.Error, msgs)) + + private class QueueQuietLogger(errQ: QueueSink[IO, () => String]) extends Logger: + def trace(msgs: => String): IO[Unit] = IO.unit - def info(msgs: String*): IO[Unit] = - errQ.offer(format(Logger.Level.Info, msgs)) + def info(msgs: => String): IO[Unit] = + errQ.offer(() => format(Logger.Level.Info, msgs)) - def error(msgs: String*): IO[Unit] = - errQ.offer(format(Logger.Level.Error, msgs)) + def error(msgs: => String): IO[Unit] = + errQ.offer(() => format(Logger.Level.Error, msgs)) \ No newline at end of file