Skip to content

Commit

Permalink
improvement: show bsp errors to the user
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored Aug 30, 2023
1 parent 7b59a3a commit 88fec28
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 5 deletions.
126 changes: 126 additions & 0 deletions metals/src/main/scala/scala/meta/internal/builds/BSPErrorHandler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package scala.meta.internal.builds

import java.nio.file.Files
import java.util.concurrent.atomic.AtomicReference

import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.util.control.NonFatal

import scala.meta.internal.bsp.BspSession
import scala.meta.internal.metals.ClientCommands
import scala.meta.internal.metals.Directories
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.io.AbsolutePath

import org.eclipse.{lsp4j => l}

class BspErrorHandler(
languageClient: MetalsLanguageClient,
workspaceFolder: AbsolutePath,
restartBspServer: () => Future[Boolean],
currentSession: () => Option[BspSession],
)(implicit context: ExecutionContext) {

private def logsPath = workspaceFolder.resolve(Directories.log)
private val lastError = new AtomicReference[String]("")

def onError(message: String): Unit = {
if (shouldShowBspError) {
val previousError = lastError.getAndSet(message)
if (message != previousError) {
showError(message)
}
} else {
scribe.error(message)
}
}

private def shouldShowBspError = currentSession().exists(session =>
session.main.isBloop || session.main.isScalaCLI
)

private def showError(message: String): Future[Unit] = {
val bspError = s"${BspErrorHandler.errorInBsp}: $message"
scribe.error(bspError)
val params = BspErrorHandler.makeShowMessage(message)
languageClient.showMessageRequest(params).asScala.flatMap {
case BspErrorHandler.goToLogs =>
val errorMsgStartLine =
bspError.linesIterator.headOption
.flatMap(findLine(_))
.getOrElse(0)
Future.successful(gotoLogs(errorMsgStartLine))
case BspErrorHandler.restartBuildServer =>
restartBspServer().ignoreValue
case _ => Future.successful(())
}
}

private def findLine(line: String): Option[Int] =
try {
val lineNumber =
Files
.readAllLines(logsPath.toNIO)
.asScala
.lastIndexWhere(_.contains(line))
if (lineNumber >= 0) Some(lineNumber) else None
} catch {
case NonFatal(_) => None
}

private def gotoLogs(line: Int) = {
val pos = new l.Position(line, 0)
val location = new l.Location(
logsPath.toURI.toString(),
new l.Range(pos, pos),
)
languageClient.metalsExecuteClientCommand(
ClientCommands.GotoLocation.toExecuteCommandParams(
ClientCommands.WindowLocation(
location.getUri(),
location.getRange(),
)
)
)
}
}

object BspErrorHandler {
def makeShowMessage(
message: String
): l.ShowMessageRequestParams = {
val (msg, actions) =
if (message.length() <= MESSAGE_MAX_LENGTH) {
(
s"""|$errorHeader
|$message""".stripMargin,
List(restartBuildServer, dismiss),
)
} else {
(
s"""|$errorHeader
|${message.take(MESSAGE_MAX_LENGTH)}...
|$gotoLogsToSeeFull""".stripMargin,
List(goToLogs, restartBuildServer, dismiss),
)
}
val params = new l.ShowMessageRequestParams()
params.setType(l.MessageType.Error)
params.setMessage(msg)
params.setActions(actions.asJava)
params
}

private val errorHeader = "Encountered an error in the build server:"
private val goToLogs = new l.MessageActionItem("Go to logs.")
private val restartBuildServer =
new l.MessageActionItem("Restart build server.")
private val dismiss = new l.MessageActionItem("Dismiss.")
private val gotoLogsToSeeFull = "Go to logs to see the full error"
private val errorInBsp = "Build server error:"

private val MESSAGE_MAX_LENGTH = 150

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import scala.collection.concurrent.TrieMap
import scala.concurrent.Promise
import scala.util.control.NonFatal

import scala.meta.internal.builds.BspErrorHandler
import scala.meta.internal.metals.BuildTargets
import scala.meta.internal.metals.Cancelable
import scala.meta.internal.metals.ClientConfiguration
Expand Down Expand Up @@ -41,6 +42,7 @@ final class ForwardingMetalsBuildClient(
didCompile: CompileReport => Unit,
onBuildTargetDidCompile: BuildTargetIdentifier => Unit,
onBuildTargetDidChangeFunc: b.DidChangeBuildTarget => Unit,
bspErrorHandler: BspErrorHandler,
) extends MetalsBuildClient
with Cancelable {

Expand Down Expand Up @@ -97,7 +99,7 @@ final class ForwardingMetalsBuildClient(
def onBuildLogMessage(params: l.MessageParams): Unit =
params.getType match {
case l.MessageType.Error =>
scribe.error(params.getMessage)
bspErrorHandler.onError(params.getMessage())
case l.MessageType.Warning =>
scribe.warn(params.getMessage)
case l.MessageType.Info =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ object MetalsEnrichments
* As long as the color codes are valid this should correctly strip
* anything that is ESC (U+001B) plus [
*/
def filerANSIColorCodes(str: String): String =
def filterANSIColorCodes(str: String): String =
str.replaceAll("\u001B\\[[;\\d]*m", "")

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import scala.meta.internal.bsp.BspSession
import scala.meta.internal.bsp.BuildChange
import scala.meta.internal.bsp.ScalaCliBspScope
import scala.meta.internal.builds.BloopInstall
import scala.meta.internal.builds.BspErrorHandler
import scala.meta.internal.builds.BuildServerProvider
import scala.meta.internal.builds.BuildTool
import scala.meta.internal.builds.BuildToolSelector
Expand Down Expand Up @@ -365,6 +366,14 @@ class MetalsLspService(
)
)

private val bspErrorHandler: BspErrorHandler =
new BspErrorHandler(
languageClient,
folder,
restartBspServer,
() => bspSession,
)

private val buildClient: ForwardingMetalsBuildClient =
new ForwardingMetalsBuildClient(
languageClient,
Expand All @@ -390,6 +399,7 @@ class MetalsLspService(
onBuildTargetDidChangeFunc = params => {
maybeQuickConnectToBuildServer(params)
},
bspErrorHandler,
)

private val bloopServers: BloopServers = new BloopServers(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ private[debug] final class DebugProxy(

case message @ OutputNotification(output) if stripColor =>
val raw = output.getOutput()
val msgWithoutColorCodes = filerANSIColorCodes(raw)
val msgWithoutColorCodes = filterANSIColorCodes(raw)
output.setOutput(msgWithoutColorCodes)
message.setParams(output)
client.consume(message)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ object MdocEnrichments {
statement: EvaluatedWorksheetStatement
) {
def prettyDetails(): String =
MetalsEnrichments.filerANSIColorCodes(statement.details())
MetalsEnrichments.filterANSIColorCodes(statement.details())
def prettySummary(): String =
MetalsEnrichments.filerANSIColorCodes(statement.summary())
MetalsEnrichments.filterANSIColorCodes(statement.summary())
}

}

0 comments on commit 88fec28

Please sign in to comment.