Skip to content

Commit

Permalink
improvement: move bsp responsiveness into status (#5660)
Browse files Browse the repository at this point in the history
  • Loading branch information
kasiaMarek authored Sep 29, 2023
1 parent 3904ee1 commit 95e42a9
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import scala.meta.internal.builds.MillBuildTool
import scala.meta.internal.builds.SbtBuildTool
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.metals.ammonite.Ammonite
import scala.meta.internal.metals.clients.language.MetalsLanguageClient
import scala.meta.internal.metals.scalacli.ScalaCli
import scala.meta.internal.pc.InterruptException
import scala.meta.internal.semver.SemVer
Expand Down Expand Up @@ -438,7 +439,7 @@ object BuildServerConnection {
projectRoot: AbsolutePath,
bspTraceRoot: AbsolutePath,
localClient: MetalsBuildClient,
languageClient: LanguageClient,
languageClient: MetalsLanguageClient,
connect: () => Future[SocketConnection],
reconnectNotification: DismissedNotifications#Notification,
config: MetalsServerConfig,
Expand Down Expand Up @@ -488,9 +489,10 @@ object BuildServerConnection {
_,
() => server.workspaceBuildTargets(),
languageClient,
result.getDisplayName(),
config.metalsToIdleTime,
config.pingInterval,
serverName,
config.icons,
)
}

Expand Down Expand Up @@ -602,10 +604,8 @@ object BuildServerConnection {

def setReconnect(
reconnect: () => Future[Unit]
)(implicit ec: ExecutionContext): Unit = {
optLivenessMonitor.foreach(_.setReconnect(reconnect))
)(implicit ec: ExecutionContext): Unit =
socketConnection.finishedPromise.future.foreach(_ => reconnect())
}

/**
* Whether we can call buildTargetWrappedSources through the BSP connection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ final class ClientConfiguration(
.getOrElse(StatusBarState.Off),
)

def bspStatusBarState(): StatusBarState.StatusBarState =
extract(
initializationOptions.bspStatusBarState,
StatusBarState.fromString(initialConfig.bspStatusBar.value),
StatusBarState.ShowMessage,
)

def globSyntax(): GlobSyntaxConfig =
initializationOptions.globSyntax
.flatMap(GlobSyntaxConfig.fromString)
Expand Down
6 changes: 6 additions & 0 deletions metals/src/main/scala/scala/meta/internal/metals/Icons.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ abstract class Icons {
def folder: String
def github: String
def error: String
def link: String
final def all: List[String] =
List(
rocket,
Expand All @@ -23,6 +24,7 @@ abstract class Icons {
folder,
github,
error,
link,
)
}
object Icons {
Expand Down Expand Up @@ -53,6 +55,7 @@ object Icons {
override def error: String = ""
override def rightArrow: String = ""
override def ellipsis: String = ""
override def link: String = "🔗"
}
case object none extends Icons {
override def rocket: String = ""
Expand All @@ -66,6 +69,7 @@ object Icons {
override def error: String = ""
override def rightArrow: String = "=>"
override def ellipsis: String = "..."
override def link: String = ""
}
// icons for vscode can be found here("Icons in Labels"):
// https://code.visualstudio.com/api/references/icons-in-labels
Expand All @@ -81,6 +85,7 @@ object Icons {
override def error: String = "$(error)"
override def rightArrow: String = ""
override def ellipsis: String = ""
override def link: String = "$(link)"
}
case object atom extends Icons {
private def span(id: String) = s"<span class='icon icon-$id'></span> "
Expand All @@ -95,5 +100,6 @@ object Icons {
override def error: String = span("error")
override def rightArrow: String = ""
override def ellipsis: String = ""
override def link: String = span("link")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import org.eclipse.{lsp4j => l}
* @param disableColorOutput in the situation where your DAP client may not handle color codes in
* the output, you can enable this to strip them.
* @param doctorVisibilityProvider if the clients implements `metals/doctorVisibilityDidChange`
* @param bspStatusBarProvider if the client supports `metals/status` with "bsp" status type
*/
final case class InitializationOptions(
compilerOptions: CompilerInitializationOptions,
Expand Down Expand Up @@ -77,12 +78,16 @@ final case class InitializationOptions(
copyWorksheetOutputProvider: Option[Boolean],
disableColorOutput: Option[Boolean],
doctorVisibilityProvider: Option[Boolean],
bspStatusBarProvider: Option[String],
) {
def doctorFormat: Option[DoctorFormat.DoctorFormat] =
doctorProvider.flatMap(DoctorFormat.fromString)

def statusBarState: Option[StatusBarState.StatusBarState] =
statusBarProvider.flatMap(StatusBarState.fromString)

def bspStatusBarState: Option[StatusBarState.StatusBarState] =
bspStatusBarProvider.flatMap(StatusBarState.fromString)
}

object InitializationOptions {
Expand Down Expand Up @@ -115,6 +120,7 @@ object InitializationOptions {
None,
None,
None,
None,
)

def from(
Expand Down Expand Up @@ -172,6 +178,7 @@ object InitializationOptions {
disableColorOutput = jsonObj.getBooleanOption("disableColorOutput"),
doctorVisibilityProvider =
jsonObj.getBooleanOption("doctorVisibilityProvider"),
bspStatusBarProvider = jsonObj.getStringOption("bspStatusBarProvider"),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat
* that instead you configure Metals via `InitializationOptions`.
*
* @param globSyntax pattern used for `DidChangeWatchedFilesRegistrationOptions`.
* @param statusBar how to handle metals/status notifications.
* @param statusBar how to handle metals/status notifications with {"statusType": "metals"}.
* @param bspStatusBar how to handle metals/status notifications with {"statusType": "bsp"}.
* @param slowTask how to handle metals/slowTask requests.
* @param executeClientCommand whether client provides the ability to support the
* `metals/executeClientCommand` command.
Expand Down Expand Up @@ -49,6 +50,7 @@ import scala.meta.pc.PresentationCompilerConfig.OverrideDefFormat
final case class MetalsServerConfig(
globSyntax: GlobSyntaxConfig = GlobSyntaxConfig.default,
statusBar: StatusBarConfig = StatusBarConfig.default,
bspStatusBar: StatusBarConfig = StatusBarConfig.bspDefault,
slowTask: SlowTaskConfig = SlowTaskConfig.default,
executeClientCommand: ExecuteClientCommandConfig =
ExecuteClientCommandConfig.default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference

import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import scala.concurrent.duration.Duration

import scala.meta.internal.metals.MetalsEnrichments._
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 org.eclipse.lsp4j.MessageActionItem
import org.eclipse.lsp4j.MessageType
import org.eclipse.lsp4j.ShowMessageRequestParams
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
import org.eclipse.lsp4j.jsonrpc.messages.Message
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage
import org.eclipse.lsp4j.services.LanguageClient

trait RequestMonitor {
def lastOutgoing: Option[Long]
Expand Down Expand Up @@ -58,22 +54,23 @@ class RequestMonitorImpl extends RequestMonitor {
class ServerLivenessMonitor(
requestMonitor: RequestMonitor,
ping: () => Unit,
languageClient: LanguageClient,
serverName: String,
client: MetalsLanguageClient,
metalsIdleInterval: Duration,
pingInterval: Duration,
)(implicit ex: ExecutionContext) {
serverName: String,
icons: Icons,
) {
private val state: AtomicReference[ServerLivenessMonitor.State] =
new AtomicReference(ServerLivenessMonitor.Idle)
@volatile private var isDismissed = false
@volatile private var isServerResponsive = true
@volatile private var reconnectOptions
: ServerLivenessMonitor.ReconnectOptions =
ServerLivenessMonitor.ReconnectOptions.empty
def setReconnect(reconnect: () => Future[Unit]): Unit = {
reconnectOptions = ServerLivenessMonitor.ReconnectOptions(reconnect)
}
val scheduler: ScheduledExecutorService = Executors.newScheduledThreadPool(1)
val connectedParams: MetalsStatusParams =
ServerLivenessMonitor.connectedParams(serverName, icons)
val noResponseParams: MetalsStatusParams =
ServerLivenessMonitor.noResponseParams(serverName, icons)

client.metalsStatus(connectedParams)

def runnable(): Runnable = new Runnable {
def run(): Unit = {
def now = System.currentTimeMillis()
Expand All @@ -94,30 +91,11 @@ class ServerLivenessMonitor(
case _ => ServerLivenessMonitor.Running
}
if (currState == ServerLivenessMonitor.Running) {
if (notResponding) {
isServerResponsive = false
if (!isDismissed) {
languageClient
.showMessageRequest(
ServerLivenessMonitor.ServerNotResponding
.params(
pingInterval,
serverName,
includeReconnectOption = reconnectOptions.canReconnect,
)
)
.asScala
.map {
case ServerLivenessMonitor.ServerNotResponding.dismiss =>
isDismissed = true
case ServerLivenessMonitor.ServerNotResponding.reestablishConnection =>
reconnectOptions.reconnect()
case _ =>
}
}
} else {
isServerResponsive = true
}
if (notResponding && isServerResponsive)
client.metalsStatus(noResponseParams)
else if (!notResponding && !isServerResponsive)
client.metalsStatus(connectedParams)
isServerResponsive = !notResponding
}
ping()
} else {
Expand All @@ -137,35 +115,13 @@ class ServerLivenessMonitor(
def shutdown(): Unit = {
scheduled.cancel(true)
scheduler.shutdown()
client.metalsStatus(ServerLivenessMonitor.disconnectedParams)
}

def getState: ServerLivenessMonitor.State = state.get()
}

object ServerLivenessMonitor {
object ServerNotResponding {
def message(pingInterval: Duration, serverName: String): String =
s"The build server has not responded in over $pingInterval. You may want to restart $serverName build server."

def params(
pingInterval: Duration,
serverName: String,
includeReconnectOption: Boolean,
): ShowMessageRequestParams = {
val params = new ShowMessageRequestParams()
params.setMessage(message(pingInterval, serverName))
val actions =
if (includeReconnectOption) List(dismiss, reestablishConnection)
else List(dismiss)
params.setActions(actions.asJava)
params.setType(MessageType.Warning)
params
}
val dismiss = new MessageActionItem("Dismiss")
val reestablishConnection = new MessageActionItem(
"Reestablish connection with BSP server"
)
}

/**
* State of the metals server:
Expand All @@ -178,17 +134,25 @@ object ServerLivenessMonitor {
object FirstPing extends State
object Running extends State

class ReconnectOptions(optReconnect: Option[() => Future[Unit]]) {
val canReconnect = optReconnect.nonEmpty
val reconnect: () => Future[Unit] =
optReconnect.getOrElse(() => Future.successful(()))
}

object ReconnectOptions {
def empty = new ReconnectOptions(None)
def apply(reconnect: () => Future[Unit]) = new ReconnectOptions(
Some(reconnect)
)
}
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,4 +16,8 @@ object StatusBarConfig {
new StatusBarConfig(
System.getProperty("metals.status-bar", "off")
)
def bspDefault =
new StatusBarConfig(
System.getProperty("metals.bsp-status-bar", "show-message")
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class WorkspaceLspService(

private val languageClient = {
val languageClient =
new ConfiguredLanguageClient(client, clientConfig, () => userConfig)
new ConfiguredLanguageClient(client, clientConfig, () => userConfig, this)
// Set the language client so that we can forward log messages to the client
LanguageClientLogger.languageClient = Some(languageClient)
cancelables.add(() => languageClient.shutdown())
Expand Down
Loading

0 comments on commit 95e42a9

Please sign in to comment.