Merge branch 'bport1/fix-lsp' into 1.12.x

This commit is contained in:
Eugene Yokota
2026-08-06 21:27:36 -04:00
7 changed files with 76 additions and 30 deletions
+1
View File
@@ -786,6 +786,7 @@ lazy val commandProj = (project in file("main-command"))
Compile / generateContrabands / contrabandFormatsForType := ContrabandConfig.getFormats,
mimaSettings,
mimaBinaryIssueFilters ++= Vector(
exclude[ReversedMissingMethodProblem]("sbt.internal.server.ServerCallback.*"),
exclude[DirectMissingMethodProblem]("sbt.Exit.apply"),
exclude[DirectMissingMethodProblem]("sbt.Reboot.apply"),
exclude[DirectMissingMethodProblem]("sbt.TemplateResolverInfo.apply"),
@@ -78,6 +78,7 @@ trait ServerCallback {
private[sbt] def authOptions: Set[ServerAuthentication]
private[sbt] def authenticate(token: String): Boolean
private[sbt] def isAuthenticated: Boolean
private[sbt] def setInitialized(value: Boolean): Unit
private[sbt] def setInitializeOption(opts: InitializeOption): Unit
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit
@@ -43,6 +43,7 @@ object LintUnused {
onUnload,
sbt.nio.Keys.watchTriggers,
serverConnectionType,
serverPort,
serverIdleTimeout,
shellPrompt,
sonaDeploymentName,
@@ -39,12 +39,22 @@ private[sbt] object LanguageServerProtocol {
ServerCapabilities(
textDocumentSync = TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)),
hoverProvider = false,
definitionProvider = true
definitionProvider = false
)
}
def handler(converter: FileConverter): ServerHandler = ServerHandler { callback =>
import callback._
def checkAuthenticated(r: JsonRpcRequestMessage)(f: => Unit): Unit =
if (!isAuthenticated)
jsonRpcRespondError(
Some(r.id),
ErrorCodes.InvalidRequest,
s"'${r.method}' is not allowed before authentication."
)
else f
ServerIntent(
onRequest = {
case r: JsonRpcRequestMessage if r.method == "initialize" =>
@@ -66,33 +76,38 @@ private[sbt] object LanguageServerProtocol {
if (!opt.skipAnalysis.getOrElse(false)) appendExec("collectAnalyses", None)
jsonRpcRespond(InitializeResult(serverCapabilities), Some(r.id))
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>
val _ = Definition.lspDefinition(json(r), r.id, CommandSource(name), converter, log)(
StandardMain.executionContext
)
case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>
val param = Converter.fromJson[SbtExecParams](json(r)).get
val _ = appendExec(param.commandLine, Some(r.id))
checkAuthenticated(r) {
val param = Converter.fromJson[SbtExecParams](json(r)).get
val _ = appendExec(param.commandLine, Some(r.id))
}
case r: JsonRpcRequestMessage if r.method == "sbt/setting" =>
val param = Converter.fromJson[Q](json(r)).get
onSettingQuery(Option(r.id), param)
checkAuthenticated(r) {
val param = Converter.fromJson[Q](json(r)).get
onSettingQuery(Option(r.id), param)
}
case r: JsonRpcRequestMessage if r.method == "sbt/cancelRequest" =>
val param = Converter.fromJson[CancelRequestParams](json(r)).get
onCancellationRequest(Option(r.id), param)
checkAuthenticated(r) {
val param = Converter.fromJson[CancelRequestParams](json(r)).get
onCancellationRequest(Option(r.id), param)
}
case r: JsonRpcRequestMessage if r.method == "sbt/completion" =>
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[CP](json(r)).get
onCompletionRequest(Option(r.id), param)
checkAuthenticated(r) {
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[CP](json(r)).get
onCompletionRequest(Option(r.id), param)
}
},
onResponse = PartialFunction.empty,
onNotification = {
case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" =>
val _ = appendExec(";Test/compile; collectAnalyses", None)
if (isAuthenticated) {
val _ = appendExec(";Test/compile; collectAnalyses", None)
} else log.warn(s"ignoring '${n.method}' before authentication")
}
)
}
@@ -84,6 +84,7 @@ final class NetworkChannel(
private val delimiter: Byte = '\n'.toByte
private val out = connection.getOutputStream
private var initialized = false
private var authenticated = false
/** Reference to the client-side custom options
*/
@@ -137,6 +138,7 @@ final class NetworkChannel(
def name: String = self.name
private[sbt] def authOptions: Set[ServerAuthentication] = self.authOptions
private[sbt] def authenticate(token: String): Boolean = self.authenticate(token)
private[sbt] def isAuthenticated: Boolean = self.isAuthenticated
private[sbt] def setInitialized(value: Boolean): Unit = self.setInitialized(value)
private[sbt] def setInitializeOption(opts: InitializeOption): Unit =
self.setInitializeOption(opts)
@@ -157,7 +159,14 @@ final class NetworkChannel(
case _ => false
}
protected def authenticate(token: String): Boolean = instance.authenticate(token)
protected def authenticate(token: String): Boolean = {
val result = instance.authenticate(token)
if (result) authenticated = true
result
}
private[sbt] def isAuthenticated: Boolean =
authenticated || authOptions.isEmpty
protected def setInitialized(value: Boolean): Unit = initialized = value
@@ -12,6 +12,7 @@ package server
import java.util.concurrent.{ ArrayBlockingQueue, ConcurrentHashMap }
import java.util.UUID
import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.{
JsonRpcNotificationMessage,
JsonRpcRequestMessage,
@@ -177,13 +178,20 @@ object VirtualTerminal {
private val requestHandler: Handler[JsonRpcRequestMessage] =
callback => {
case r if r.method == attach =>
val isInteractive = r.params
.flatMap(Converter.fromJson[Attach](_).toOption.map(_.interactive))
.exists(identity)
StandardMain.exchange.channelForName(callback.name) match {
case Some(nc: NetworkChannel) => nc.setInteractive(r.id, isInteractive)
case _ =>
}
if (callback.isAuthenticated) {
val isInteractive = r.params
.flatMap(Converter.fromJson[Attach](_).toOption.map(_.interactive))
.exists(identity)
StandardMain.exchange.channelForName(callback.name) match {
case Some(nc: NetworkChannel) => nc.setInteractive(r.id, isInteractive)
case _ =>
}
} else
callback.jsonRpcRespondError(
Some(r.id),
ErrorCodes.InvalidRequest,
s"'$attach' is not allowed before authentication."
)
}
private val responseHandler: Handler[JsonRpcResponseMessage] =
callback => {
@@ -243,12 +251,14 @@ object VirtualTerminal {
private val notificationHandler: Handler[JsonRpcNotificationMessage] =
callback => {
case n if n.method == systemIn =>
import sjsonnew.BasicJsonProtocol._
n.params.flatMap(Converter.fromJson[Byte](_).toOption).foreach { byte =>
StandardMain.exchange.channelForName(callback.name) match {
case Some(nc: NetworkChannel) => nc.write(byte)
case _ =>
if (callback.isAuthenticated) {
import sjsonnew.BasicJsonProtocol._
n.params.flatMap(Converter.fromJson[Byte](_).toOption).foreach { byte =>
StandardMain.exchange.channelForName(callback.name) match {
case Some(nc: NetworkChannel) => nc.write(byte)
case _ =>
}
}
}
} else callback.log.warn(s"ignoring '$systemIn' before authentication")
}
}
@@ -0,0 +1,9 @@
scalaVersion := "3.8.4"
Global / serverConnectionType := ConnectionType.Tcp
Global / serverPort := 5002
lazy val root = (project in file("."))
.settings(
name := "tcp",
)