From 5d42dd65c837cfc9f2103bf6523cf4135c6d01b6 Mon Sep 17 00:00:00 2001 From: Aleksandra Zdrojowa Date: Mon, 17 Nov 2025 10:16:22 +0100 Subject: [PATCH 1/3] filter out `bspReload` to avoid triggering an additional reload if bspReload is in the remaining commands #8371 --- .../main/scala/sbt/internal/server/BuildServerProtocol.scala | 2 +- main/src/main/scala/sbt/nio/CheckBuildSources.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 3aaec67a6..68d385f9f 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -62,7 +62,7 @@ object BuildServerProtocol { jvmTestEnvironmentProvider = true, ) - private val bspReload = "bspReload" + val bspReload = "bspReload" private val targetIdentifierParser: Parser[Seq[BuildTargetIdentifier]] = Def diff --git a/main/src/main/scala/sbt/nio/CheckBuildSources.scala b/main/src/main/scala/sbt/nio/CheckBuildSources.scala index 90d753c24..92930f085 100644 --- a/main/src/main/scala/sbt/nio/CheckBuildSources.scala +++ b/main/src/main/scala/sbt/nio/CheckBuildSources.scala @@ -17,6 +17,7 @@ import sbt.ProjectExtra.extract import sbt.Scope.Global import sbt.internal.CommandStrings.LoadProject import sbt.internal.SysProp +import sbt.internal.server.BuildServerProtocol import sbt.internal.util.{ AttributeKey, Terminal } import sbt.io.syntax.* import sbt.nio.FileChanges @@ -93,7 +94,7 @@ private[sbt] class CheckBuildSources extends AutoCloseable { val commands = allCmds.flatMap(_.split(";").flatMap(_.trim.split(" ").headOption).filterNot(_.isEmpty)) val filter = (c: String) => - c == LoadProject || c == RebootCommand || c == TerminateAction || c == Shutdown || + c == LoadProject || c == BuildServerProtocol.bspReload || c == RebootCommand || c == TerminateAction || c == Shutdown || c.startsWith("sbtReboot") val resetState = commands.exists(filter) if (resetState) { From 8770ad9de879eb90a50e67edc2cc62e9624643b7 Mon Sep 17 00:00:00 2001 From: Aleksandra Zdrojowa Date: Mon, 17 Nov 2025 10:18:56 +0100 Subject: [PATCH 2/3] trigger an additional reload only if it's a ConsoleChannel, an interactive NetworkChannel, or a NetworkChannel where the exec ID is known, to ensure the response can be propagated correctly #8371 --- main/src/main/scala/sbt/MainLoop.scala | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 7ccfa3a97..85ae7ce30 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -10,6 +10,7 @@ package sbt import sbt.BasicCommandStrings.{ StashOnFailure, networkExecPrefix } import sbt.ProjectExtra.extract +import sbt.internal.{ ConsoleChannel, FastTrackCommands, ShutdownHooks, SysProp, TaskProgress } import sbt.internal.langserver.ErrorCodes import sbt.internal.nio.CheckBuildSources.CheckBuildSourcesKey import sbt.internal.protocol.JsonRpcResponseError @@ -20,7 +21,6 @@ import sbt.internal.util.{ Prompt, Terminal as ITerminal } -import sbt.internal.{ FastTrackCommands, ShutdownHooks, SysProp, TaskProgress } import sbt.io.{ IO, Using } import sbt.protocol.* import sbt.util.{ Logger, LoggerContext } @@ -31,6 +31,7 @@ import java.util.concurrent.RejectedExecutionException import scala.annotation.tailrec import scala.concurrent.duration.* import scala.util.control.NonFatal +import sbt.internal.server.NetworkChannel import java.text.ParseException @@ -301,10 +302,22 @@ private[sbt] object MainLoop: .remove(Keys.terminalKey) .remove(Keys.currentCommandProgress) } + + val channel = channelName.flatMap(exchange.channelForName) + val (canReload, useLoadp) = channel match + case Some(nc: NetworkChannel) => + val isInteractive = nc.isInteractive + (isInteractive || exec.execId.nonEmpty, !isInteractive) + case Some(_: ConsoleChannel) => (true, false) + case _ => (false, false) + state.get(CheckBuildSourcesKey) match { - case Some(cbs) => - if (!cbs.needsReload(state, exec)) process() - else Exec("reload", None) +: exec +: state.remove(CheckBuildSourcesKey) + case Some(cbs) if canReload && cbs.needsReload(state, exec) => + val loadExec = + if (useLoadp) Exec("loadp", exec.execId, exec.source) + else Exec("reload", exec.source) + + loadExec +: exec +: state.remove(CheckBuildSourcesKey) case _ => process() } } catch { From da42031bb3c6e906a56c638f11f6ad5f4a908154 Mon Sep 17 00:00:00 2001 From: Aleksandra Zdrojowa Date: Tue, 18 Nov 2025 21:48:11 +0100 Subject: [PATCH 3/3] remove the `isInteractive` check when deciding whether to trigger an additional reload #8371 --- main/src/main/scala/sbt/MainLoop.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/main/src/main/scala/sbt/MainLoop.scala b/main/src/main/scala/sbt/MainLoop.scala index 85ae7ce30..d0a0f4f5d 100644 --- a/main/src/main/scala/sbt/MainLoop.scala +++ b/main/src/main/scala/sbt/MainLoop.scala @@ -305,11 +305,9 @@ private[sbt] object MainLoop: val channel = channelName.flatMap(exchange.channelForName) val (canReload, useLoadp) = channel match - case Some(nc: NetworkChannel) => - val isInteractive = nc.isInteractive - (isInteractive || exec.execId.nonEmpty, !isInteractive) - case Some(_: ConsoleChannel) => (true, false) - case _ => (false, false) + case Some(nc: NetworkChannel) => (exec.execId.nonEmpty, true) + case Some(_: ConsoleChannel) => (true, false) + case _ => (false, false) state.get(CheckBuildSourcesKey) match { case Some(cbs) if canReload && cbs.needsReload(state, exec) =>