Skip to content

Commit

Permalink
improvement: show correct bsp status for focused workspace folder (#5772
Browse files Browse the repository at this point in the history
)
  • Loading branch information
kasiaMarek authored Oct 24, 2023
1 parent 728623b commit b5a2d8d
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 77 deletions.
10 changes: 6 additions & 4 deletions metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class BspConnector(
bspConfigGenerator: BspConfigGenerator,
currentConnection: () => Option[BuildServerConnection],
restartBspServer: () => Future[Boolean],
bspStatus: ConnectionBspStatus,
)(implicit ec: ExecutionContext) {

/**
Expand Down Expand Up @@ -83,6 +84,7 @@ class BspConnector(
bspTraceRoot: AbsolutePath,
addLivenessMonitor: Boolean,
): Future[Option[BuildServerConnection]] = {
def bspStatusOpt = Option.when(addLivenessMonitor)(bspStatus)
scribe.info("Attempting to connect to the build server...")
resolve() match {
case ResolvedNone =>
Expand All @@ -94,7 +96,7 @@ class BspConnector(
projectRoot,
bspTraceRoot,
userConfiguration,
addLivenessMonitor,
bspStatusOpt,
)
.map(Some(_))
case ResolvedBspOne(details)
Expand All @@ -118,7 +120,7 @@ class BspConnector(
projectRoot,
bspTraceRoot,
details,
addLivenessMonitor,
bspStatusOpt,
)
_ <-
if (shouldReload) connection.workspaceReload()
Expand All @@ -130,7 +132,7 @@ class BspConnector(
case ResolvedBspOne(details) =>
tables.buildServers.chooseServer(details.getName())
bspServers
.newServer(projectRoot, bspTraceRoot, details, addLivenessMonitor)
.newServer(projectRoot, bspTraceRoot, details, bspStatusOpt)
.map(Some(_))
case ResolvedMultiple(_, availableServers) =>
val distinctServers = availableServers
Expand Down Expand Up @@ -167,7 +169,7 @@ class BspConnector(
projectRoot,
bspTraceRoot,
item,
addLivenessMonitor,
bspStatusOpt,
)
} yield Some(conn)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ final class BspServers(
projectDirectory: AbsolutePath,
bspTraceRoot: AbsolutePath,
details: BspConnectionDetails,
addLivenessMonitor: Boolean,
bspStatusOpt: Option[ConnectionBspStatus],
): Future[BuildServerConnection] = {

def newConnection(): Future[SocketConnection] = {
Expand Down Expand Up @@ -143,7 +143,7 @@ final class BspServers(
tables.dismissedNotifications.ReconnectBsp,
config,
details.getName(),
addLivenessMonitor,
bspStatusOpt,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package scala.meta.internal.bsp

import java.util.concurrent.atomic.AtomicBoolean

import scala.meta.internal.metals.BspStatus
import scala.meta.internal.metals.Icons
import scala.meta.internal.metals.ServerCommands
import scala.meta.internal.metals.clients.language.MetalsStatusParams
import scala.meta.internal.metals.clients.language.StatusType
import scala.meta.io.AbsolutePath

class ConnectionBspStatus(
bspStatus: BspStatus,
folderPath: AbsolutePath,
icons: Icons,
) {
private val isServerResponsive = new AtomicBoolean(false)
val status: MetalsStatusParams => Unit = bspStatus.status(folderPath, _)

def connected(serverName: String): Unit =
if (isServerResponsive.compareAndSet(false, true))
status(ConnectionBspStatus.connectedParams(serverName, icons))
def noResponse(serverName: String): Unit =
if (isServerResponsive.compareAndSet(true, false)) {
scribe.debug("server liveness monitor detected no response")
status(ConnectionBspStatus.noResponseParams(serverName, icons))
}

def disconnected(): Unit = {
isServerResponsive.set(false)
status(ConnectionBspStatus.disconnectedParams)
}

def isBuildServerResponsive: Boolean = isServerResponsive.get()
}

object ConnectionBspStatus {
def connectedParams(serverName: String, icons: Icons): MetalsStatusParams =
MetalsStatusParams(
s"$serverName ${icons.link}",
"info",
show = true,
tooltip = s"Metals is connected to the build server ($serverName).",
).withStatusType(StatusType.bsp)

val disconnectedParams: MetalsStatusParams =
MetalsStatusParams("", hide = true).withStatusType(StatusType.bsp)

def noResponseParams(serverName: String, icons: Icons): MetalsStatusParams =
MetalsStatusParams(
s"$serverName ${icons.error}",
"error",
show = true,
tooltip = s"Build sever ($serverName) is not responding.",
command = ServerCommands.ConnectBuildServer.id,
commandTooltip = "Reconnect.",
).withStatusType(StatusType.bsp)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import scala.util.Success
import scala.util.Try

import scala.meta.internal.bsp.BuildChange
import scala.meta.internal.bsp.ConnectionBspStatus
import scala.meta.internal.metals.BloopJsonUpdateCause.BloopJsonUpdateCause
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
Expand Down Expand Up @@ -81,7 +82,7 @@ final class BloopServers(
projectRoot: AbsolutePath,
bspTraceRoot: AbsolutePath,
userConfiguration: UserConfiguration,
addLivenessMonitor: Boolean,
bspStatusOpt: Option[ConnectionBspStatus],
): Future[BuildServerConnection] = {
val bloopVersion = userConfiguration.currentBloopVersion
BuildServerConnection
Expand All @@ -95,7 +96,7 @@ final class BloopServers(
tables.dismissedNotifications.ReconnectBsp,
config,
name,
addLivenessMonitor,
bspStatusOpt,
)
}

Expand Down
37 changes: 37 additions & 0 deletions metals/src/main/scala/scala/meta/internal/metals/BspStatus.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package scala.meta.internal.metals

import java.util.Collections
import java.util.concurrent.atomic.AtomicReference

import scala.meta.internal.bsp.ConnectionBspStatus
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.clients.language.MetalsStatusParams
import scala.meta.io.AbsolutePath

class BspStatus(client: MetalsLanguageClient, isBspStatusProvider: Boolean) {
val focusedFolder: AtomicReference[Option[AbsolutePath]] =
new AtomicReference(None)
val messages: java.util.Map[AbsolutePath, MetalsStatusParams] =
Collections.synchronizedMap(
new java.util.HashMap[AbsolutePath, MetalsStatusParams]
)

def status(folder: AbsolutePath, params: MetalsStatusParams): Unit = {
messages.put(folder, params)
if (focusedFolder.get().isEmpty || focusedFolder.get().contains(folder)) {
client.metalsStatus(params)
}
}

def focus(folder: AbsolutePath): Unit = {
if (isBspStatusProvider) {
val prev = focusedFolder.getAndSet(Some(folder))
if (!prev.contains(folder)) {
client.metalsStatus(
messages.getOrDefault(folder, ConnectionBspStatus.disconnectedParams)
)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import scala.reflect.ClassTag
import scala.util.Success
import scala.util.Try

import scala.meta.internal.bsp.ConnectionBspStatus
import scala.meta.internal.builds.MillBuildTool
import scala.meta.internal.builds.SbtBuildTool
import scala.meta.internal.metals.MetalsEnrichments._
Expand Down Expand Up @@ -444,7 +445,7 @@ object BuildServerConnection {
reconnectNotification: DismissedNotifications#Notification,
config: MetalsServerConfig,
serverName: String,
addLivenessMonitor: Boolean = false,
bspStatusOpt: Option[ConnectionBspStatus] = None,
retry: Int = 5,
supportsWrappedSources: Option[Boolean] = None,
)(implicit
Expand All @@ -454,12 +455,8 @@ object BuildServerConnection {
def setupServer(): Future[LauncherConnection] = {
connect().map { case conn @ SocketConnection(_, output, input, _, _) =>
val tracePrinter = Trace.setupTracePrinter("BSP", bspTraceRoot)
val bspStatusOpt =
if (addLivenessMonitor)
Some(new BspStatus(languageClient, serverName, config.icons))
else None
val requestMonitorOpt =
bspStatusOpt.map(new RequestMonitorImpl(_))
bspStatusOpt.map(new RequestMonitorImpl(_, serverName))
val wrapper: MessageConsumer => MessageConsumer =
requestMonitorOpt.map(_.wrapper).getOrElse(identity)
val launcher =
Expand Down Expand Up @@ -497,6 +494,7 @@ object BuildServerConnection {
config.metalsToIdleTime,
config.pingInterval,
bspStatus,
serverName,
)

LauncherConnection(
Expand Down Expand Up @@ -535,7 +533,7 @@ object BuildServerConnection {
reconnectNotification,
config,
serverName,
addLivenessMonitor,
bspStatusOpt,
retry - 1,
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import scala.meta.internal.bsp.BspConnector
import scala.meta.internal.bsp.BspServers
import scala.meta.internal.bsp.BspSession
import scala.meta.internal.bsp.BuildChange
import scala.meta.internal.bsp.ConnectionBspStatus
import scala.meta.internal.bsp.ScalaCliBspScope
import scala.meta.internal.builds.BloopInstall
import scala.meta.internal.builds.BspErrorHandler
Expand Down Expand Up @@ -130,6 +131,7 @@ class MetalsLspService(
folder: AbsolutePath,
folderVisibleName: Option[String],
headDoctor: HeadDoctor,
bspStatus: BspStatus,
) extends Folder(folder, folderVisibleName, isKnownMetalsProject = true)
with Cancelable
with TextDocumentService {
Expand Down Expand Up @@ -423,6 +425,9 @@ class MetalsLspService(
clientConfig.initialConfig,
)

private val connectionBspStatus =
new ConnectionBspStatus(bspStatus, folder, clientConfig.icons())

private val bspServers: BspServers = new BspServers(
folder,
charset,
Expand All @@ -445,6 +450,7 @@ class MetalsLspService(
bspConfigGenerator,
() => bspSession.map(_.mainConnection),
restartBspServer,
connectionBspStatus,
)

private val workspaceSymbols: WorkspaceSymbolProvider =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean

import scala.concurrent.duration.Duration

import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.clients.language.MetalsStatusParams
import scala.meta.internal.metals.clients.language.StatusType
import scala.meta.internal.bsp.ConnectionBspStatus

import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.messages.Message
Expand All @@ -23,7 +20,8 @@ trait RequestMonitor {
def lastIncoming: Option[Long]
}

class RequestMonitorImpl(bspStatus: BspStatus) extends RequestMonitor {
class RequestMonitorImpl(bspStatus: ConnectionBspStatus, serverName: String)
extends RequestMonitor {
@volatile private var lastOutgoing_ : Option[Long] = None
@volatile private var lastIncoming_ : Option[Long] = None

Expand All @@ -45,7 +43,7 @@ class RequestMonitorImpl(bspStatus: BspStatus) extends RequestMonitor {

private def outgoingMessage() = lastOutgoing_ = now
private def incomingMessage(): Unit = {
bspStatus.connected()
bspStatus.connected(serverName)
lastIncoming_ = now
}
private def now = Some(System.currentTimeMillis())
Expand All @@ -59,7 +57,8 @@ class ServerLivenessMonitor(
ping: () => Unit,
metalsIdleInterval: Duration,
pingInterval: Duration,
bspStatus: BspStatus,
bspStatus: ConnectionBspStatus,
serverName: String,
) {
@volatile private var lastPing: Long = 0
val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
Expand All @@ -75,7 +74,7 @@ class ServerLivenessMonitor(
def notResponding = lastIncoming > (pingInterval.toMillis * 2)
if (!metalsIsIdle) {
if (lastPingOk && notResponding) {
bspStatus.noResponse()
bspStatus.noResponse(serverName)
}
scribe.debug("server liveness monitor: pinging build server...")
lastPing = now
Expand Down Expand Up @@ -125,46 +124,3 @@ object ServerLivenessMonitor {
object Running extends State

}

class BspStatus(
client: MetalsLanguageClient,
serverName: String,
icons: Icons,
) {
private val isServerResponsive = new AtomicBoolean(false)

def connected(): Unit =
if (isServerResponsive.compareAndSet(false, true))
client.metalsStatus(BspStatus.connectedParams(serverName, icons))
def noResponse(): Unit =
if (isServerResponsive.compareAndSet(true, false)) {
scribe.debug("server liveness monitor detected no response")
client.metalsStatus(BspStatus.noResponseParams(serverName, icons))
}
def disconnected(): Unit = client.metalsStatus(BspStatus.disconnectedParams)

def isBuildServerResponsive: Boolean = isServerResponsive.get()
}

object BspStatus {
def connectedParams(serverName: String, icons: Icons): MetalsStatusParams =
MetalsStatusParams(
s"$serverName ${icons.link}",
"info",
show = true,
tooltip = s"Metals is connected to the build server ($serverName).",
).withStatusType(StatusType.bsp)

val disconnectedParams: MetalsStatusParams =
MetalsStatusParams("", hide = true).withStatusType(StatusType.bsp)

def noResponseParams(serverName: String, icons: Icons): MetalsStatusParams =
MetalsStatusParams(
s"$serverName ${icons.error}",
"error",
show = true,
tooltip = s"Build sever ($serverName) is not responding.",
command = ServerCommands.ConnectBuildServer.id,
commandTooltip = "Reconnect.",
).withStatusType(StatusType.bsp)
}
Loading

0 comments on commit b5a2d8d

Please sign in to comment.