From a5951fd141358f38614ac8a15e463d71c36358d5 Mon Sep 17 00:00:00 2001 From: Tomasz Godzik Date: Fri, 3 Jan 2025 10:21:37 +0100 Subject: [PATCH] improvement: Ask to start http server if not running This will show up after indexing and users can start the server themselves or ignore it for the session. Should help with https://github.com/scalameta/metals/issues/7072 --- .../metals/DismissedNotifications.scala | 4 +- .../scala/meta/internal/metals/Messages.scala | 22 +++++++++++ .../internal/metals/WorkspaceLspService.scala | 37 ++++++++++++++++--- .../internal/metals/doctor/HeadDoctor.scala | 9 +++-- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/DismissedNotifications.scala b/metals/src/main/scala/scala/meta/internal/metals/DismissedNotifications.scala index 01c681574f8..72c253c5158 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/DismissedNotifications.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/DismissedNotifications.scala @@ -24,7 +24,7 @@ final class DismissedNotifications(conn: () => Connection, time: Time) { val ScalaCliImportAuto = new Notification(14) val BspErrors = new Notification(15) val RequestTimeout = new Notification(16) - val ScalafixConfAmend = new Notification(16) + val ScalafixConfAmend = new Notification(17) val all: List[Notification] = List( Only212Navigation, @@ -42,6 +42,8 @@ final class DismissedNotifications(conn: () => Connection, time: Time) { ReconnectScalaCli, ScalaCliImportAuto, BspErrors, + RequestTimeout, + ScalafixConfAmend, ) def resetAll(): Unit = { diff --git a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala index b6e8ea9747a..53ae6bb6c12 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala @@ -184,6 +184,28 @@ object Messages { } } + object StartHttpServer { + def yes = new MessageActionItem("Start") + + def notNow: MessageActionItem = Messages.notNow + + def params(): ShowMessageRequestParams = { + val params = new ShowMessageRequestParams() + params.setMessage( + s"Http server is required for such features as Metals Doctor, do you want to start it now?" + ) + params.setType(MessageType.Info) + params.setActions( + List( + yes, + notNow, + dontShowAgain, + ).asJava + ) + params + } + } + object GenerateBspAndConnect { def yes = new MessageActionItem("Connect") diff --git a/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala index 0d69a1b9228..7b7be59493c 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala @@ -111,7 +111,12 @@ class WorkspaceLspService( private val cancelables = new MutableCancelable() val fallbackIsInitialized: ju.concurrent.atomic.AtomicBoolean = new ju.concurrent.atomic.AtomicBoolean(false) - var httpServer: Option[MetalsHttpServer] = None + @volatile var httpServer: HttpServerStatus = HttpServerOff + + sealed trait HttpServerStatus + case object HttpServerOff extends HttpServerStatus + case object HttpServerIgnored extends HttpServerStatus + case class HttpServerOn(server: MetalsHttpServer) extends HttpServerStatus private val clientConfig = ClientConfiguration( @@ -152,10 +157,32 @@ class WorkspaceLspService( private val timerProvider: TimerProvider = new TimerProvider(time) + def getHttpServer(): Future[Option[MetalsHttpServer]] = { + httpServer match { + case HttpServerOff => + languageClient + .showMessageRequest(Messages.StartHttpServer.params()) + .asScala + .flatMap { item => + if (item == Messages.StartHttpServer.yes) { + startHttpServer(force = true) + getHttpServer() + } else if (item == Messages.dontShowAgain) { + httpServer = HttpServerIgnored + Future.successful(None) + } else { + Future.successful(None) + } + } + case HttpServerOn(server) => Future.successful(Some(server)) + case HttpServerIgnored => Future.successful(None) + } + } + val doctor: HeadDoctor = new HeadDoctor( () => folderServices.map(_.doctor) ++ optFallback.map(_.doctor), - () => httpServer, + getHttpServer, clientConfig, languageClient, ) @@ -1268,8 +1295,8 @@ class WorkspaceLspService( } yield () } - private def startHttpServer(): Unit = { - if (clientConfig.isHttpEnabled()) { + private def startHttpServer(force: Boolean = false): Unit = { + if (force || clientConfig.isHttpEnabled()) { val host = "localhost" val port = 5031 var url = s"http://$host:$port" @@ -1288,7 +1315,7 @@ class WorkspaceLspService( this, ) ) - httpServer = Some(server) + httpServer = HttpServerOn(server) val newClient = new MetalsHttpClient( folders.map(_.path), () => url, diff --git a/metals/src/main/scala/scala/meta/internal/metals/doctor/HeadDoctor.scala b/metals/src/main/scala/scala/meta/internal/metals/doctor/HeadDoctor.scala index 8b6b974ab13..fa53ccdaf19 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/doctor/HeadDoctor.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/doctor/HeadDoctor.scala @@ -2,6 +2,9 @@ package scala.meta.internal.metals.doctor import java.util.concurrent.atomic.AtomicBoolean +import scala.concurrent.ExecutionContext +import scala.concurrent.Future + import scala.meta.internal.metals.BuildInfo import scala.meta.internal.metals.ClientCommands import scala.meta.internal.metals.ClientConfiguration @@ -14,10 +17,10 @@ import scala.meta.internal.metals.config.DoctorFormat class HeadDoctor( doctors: () => List[Doctor], - httpServer: () => Option[MetalsHttpServer], + httpServer: () => Future[Option[MetalsHttpServer]], clientConfig: ClientConfiguration, languageClient: MetalsLanguageClient, -) { +)(implicit ec: ExecutionContext) { private val isVisible = new AtomicBoolean(false) def onVisibilityDidChange(newState: Boolean): Unit = { @@ -82,7 +85,7 @@ class HeadDoctor( val params = clientCommand.toExecuteCommandParams(output) languageClient.metalsExecuteClientCommand(params) } else { - httpServer() match { + httpServer().map { case Some(server) => onServer(server) case None =>