Merge pull request #3777 from eed3si9n/wip/vsc-definition

textDocument/definition for LSP / VS Code
This commit is contained in:
eugene yokota 2017-11-28 14:47:09 -05:00 committed by GitHub
commit 164b29da22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 697 additions and 17 deletions

View File

@ -396,7 +396,7 @@ lazy val mainProj = (project in file("main"))
.settings(
testedBaseSettings,
name := "Main",
libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface) ++ log4jDependencies,
libraryDependencies ++= scalaXml.value ++ Seq(launcherInterface) ++ log4jDependencies ++ Seq(scalaCacheCaffeine),
managedSourceDirectories in Compile +=
baseDirectory.value / "src" / "main" / "contraband-scala",
sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala",

View File

@ -26,7 +26,7 @@ import sbt.internal.librarymanagement.mavenint.{
PomExtraDependencyAttributes,
SbtPomExtraProperties
}
import sbt.internal.server.LanguageServerReporter
import sbt.internal.server.{ LanguageServerReporter, Definition }
import sbt.internal.testing.TestLogger
import sbt.internal.util._
import sbt.internal.util.Attributed.data
@ -498,6 +498,7 @@ object Defaults extends BuildCommon {
},
compileIncSetup := compileIncSetupTask.value,
console := consoleTask.value,
collectAnalyses := Definition.collectAnalysesTask.value,
consoleQuick := consoleQuickTask.value,
discoveredMainClasses := (compile map discoverMainClasses storeAs discoveredMainClasses xtriggeredBy compile).value,
discoveredSbtPlugins := discoverSbtPluginNames.value,

View File

@ -449,6 +449,9 @@ object Keys {
val interactionService = taskKey[InteractionService]("Service used to ask for user input through the current user interface(s).").withRank(CTask)
val insideCI = SettingKey[Boolean]("insideCI", "Determines if the SBT is running in a Continuous Integration environment", AMinusSetting)
// sbt server internal
val collectAnalyses = taskKey[Unit]("Collect analysis file locations for later use.")
// special
val sessionVars = AttributeKey[SessionVar.Map]("sessionVars", "Bindings that exist for the duration of the session.", Invisible)
val parallelExecution = settingKey[Boolean]("Enables (true) or disables (false) parallel execution of tasks.").withRank(BMinusSetting)

View File

@ -101,6 +101,9 @@ final class ConsoleMain extends xsbti.AppMain {
object StandardMain {
private[sbt] lazy val exchange = new CommandExchange()
import scalacache._
import scalacache.caffeine._
private[sbt] val cache: Cache[Any] = CaffeineCache[Any]
def runManaged(s: State): xsbti.MainResult = {
val previous = TrapExit.installManager()
@ -132,6 +135,9 @@ object StandardMain {
Exec(x, None)
}
val initAttrs = BuiltinCommands.initialAttributes
import scalacache.modes.scalaFuture._
import scala.concurrent.ExecutionContext.Implicits.global
cache.removeAll()
val s = State(
configuration,
initialDefinitions,
@ -157,7 +163,7 @@ import TemplateCommandUtil.templateCommand
object BuiltinCommands {
def initialAttributes = AttributeMap.empty
import BasicCommands.exit
def ConsoleCommands: Seq[Command] =
Seq(ignore, exit, IvyConsole.command, setLogLevel, early, act, nop)

View File

@ -0,0 +1,329 @@
/*
* sbt
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under BSD-3-Clause license (see LICENSE)
*/
package sbt
package internal
package server
import sbt.internal.inc.MixedAnalyzingCompiler
import sbt.internal.langserver.ErrorCodes
import sbt.util.Logger
import scala.annotation.tailrec
import scala.concurrent.{ ExecutionContext, Future }
import scala.concurrent.duration.Duration.Inf
import scala.util.matching.Regex.MatchIterator
import java.nio.file.{ Files, Paths }
import sbt.StandardMain
private[sbt] object Definition {
import java.net.URI
import Keys._
import sbt.internal.inc.Analysis
import sbt.internal.inc.JavaInterfaceUtil._
val AnalysesKey = "lsp.definition.analyses.key"
import sjsonnew.JsonFormat
def send[A: JsonFormat](source: CommandSource, execId: String)(params: A): Unit = {
for {
channel <- StandardMain.exchange.channels.collectFirst {
case c if c.name == source.channelName => c
}
} yield {
channel.publishEvent(params, Option(execId))
}
}
object textProcessor {
private val isIdentifier = {
import scala.tools.reflect.{ ToolBox, ToolBoxError }
lazy val tb =
scala.reflect.runtime.universe
.runtimeMirror(this.getClass.getClassLoader)
.mkToolBox()
import tb._
lazy val check = parse _ andThen compile _
(identifier: String) =>
try {
check(s"val $identifier = 0; val ${identifier}${identifier} = $identifier")
true
} catch {
case _: ToolBoxError => false
}
}
private def findInBackticks(line: String, point: Int): Option[String] = {
val (even, odd) = line.zipWithIndex
.collect {
case (char, backtickIndex) if char == '`' =>
backtickIndex
}
.zipWithIndex
.partition { bs =>
val (_, index) = bs
index % 2 == 0
}
even
.collect {
case (backtickIndex, _) => backtickIndex
}
.zip {
odd.collect {
case (backtickIndex, _) => backtickIndex + 1
}
}
.collectFirst {
case (from, to) if from <= point && point < to => line.slice(from, to)
}
}
def identifier(line: String, point: Int): Option[String] = findInBackticks(line, point).orElse {
val whiteSpaceReg = "(\\s|\\.)+".r
val (zero, end) = fold(Seq.empty)(whiteSpaceReg.findAllIn(line))
.collect {
case (white, ind) => (ind, ind + white.length)
}
.fold((0, line.length)) { (z, e) =>
val (from, to) = e
val (left, right) = z
(if (to > left && to <= point) to else left,
if (from < right && from >= point) from else right)
}
val ranges = for {
from <- zero to point
to <- point to end
} yield (from -> to)
ranges
.sortBy {
case (from, to) => -(to - from)
}
.foldLeft(Seq.empty[String]) { (z, e) =>
val (from, to) = e
val fragment = line.slice(from, to).trim
z match {
case Nil if fragment.nonEmpty && isIdentifier(fragment) => fragment +: z
case h +: _ if h.length < fragment.length && isIdentifier(fragment) =>
Seq(fragment)
case h +: _ if h.length == fragment.length && isIdentifier(fragment) =>
fragment +: z
case z => z
}
}
.headOption
}
private def asClassObjectIdentifier(sym: String) =
Seq(s".$sym", s".$sym$$", s"$$$sym", s"$$$sym$$")
def potentialClsOrTraitOrObj(sym: String): PartialFunction[String, String] = {
import scala.reflect.NameTransformer
val encodedSym = NameTransformer.encode(sym.toSeq match {
case '`' +: body :+ '`' => body.mkString
case noBackticked => noBackticked.mkString
})
val action: PartialFunction[String, String] = {
case potentialClassOrTraitOrObject
if asClassObjectIdentifier(encodedSym).exists(potentialClassOrTraitOrObject.endsWith) ||
encodedSym == potentialClassOrTraitOrObject ||
s"$encodedSym$$" == potentialClassOrTraitOrObject =>
potentialClassOrTraitOrObject
}
action
}
@tailrec
private def fold(z: Seq[(String, Int)])(it: MatchIterator): Seq[(String, Int)] = {
if (!it.hasNext) z
else fold(z :+ (it.next() -> it.start))(it)
}
def classTraitObjectInLine(sym: String)(line: String): Seq[(String, Int)] = {
import scala.util.matching.Regex.quote
val potentials =
Seq(s"object\\s+${quote(sym)}".r,
s"trait\\s+${quote(sym)} *\\[?".r,
s"class\\s+${quote(sym)} *\\[?".r)
potentials
.flatMap { reg =>
fold(Seq.empty)(reg.findAllIn(line))
}
.collect {
case (name, pos) =>
(if (name.endsWith("[")) name.init.trim else name.trim) -> pos
}
}
import java.io.File
def markPosition(file: File, sym: String): Seq[(File, Long, Long, Long)] = {
import java.nio.file._
import scala.collection.JavaConverters._
val findInLine = classTraitObjectInLine(sym)(_)
Files
.lines(file.toPath)
.iterator
.asScala
.zipWithIndex
.flatMap {
case (line, lineNumber) =>
findInLine(line)
.collect {
case (sym, from) =>
(file, lineNumber.toLong, from.toLong, from.toLong + sym.length)
}
}
.toSeq
.distinct
}
}
import sbt.internal.langserver.TextDocumentPositionParams
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
private def getDefinition(jsonDefinition: JValue): Option[TextDocumentPositionParams] = {
import sbt.internal.langserver.codec.JsonProtocol._
import sjsonnew.support.scalajson.unsafe.Converter
Converter.fromJson[TextDocumentPositionParams](jsonDefinition).toOption
}
import java.io.File
private def storeAnalysis(cacheFile: File, useBinary: Boolean): Option[Analysis] =
MixedAnalyzingCompiler
.staticCachedStore(cacheFile, !useBinary)
.get
.toOption
.collect {
case contents =>
contents.getAnalysis
}
.collect {
case a: Analysis => a
}
import scalacache._
private[sbt] def updateCache[F[_]](cache: Cache[Any])(cacheFile: String, useBinary: Boolean)(
implicit
mode: Mode[F],
flags: Flags): F[Any] = {
mode.M.flatMap(cache.get(AnalysesKey)) {
case None =>
cache.put(AnalysesKey)(Set(cacheFile -> useBinary -> None), Option(Inf))
case Some(set) =>
cache.put(AnalysesKey)(
set.asInstanceOf[Set[((String, Boolean), Option[Analysis])]].filterNot {
case ((file, _), _) => file == cacheFile
} + (cacheFile -> useBinary -> None),
Option(Inf))
case _ => mode.M.pure(())
}
}
def collectAnalysesTask = Def.task {
val cacheFile = compileIncSetup.value.cacheFile.getAbsolutePath
val useBinary = enableBinaryCompileAnalysis.value
val s = state.value
s.log.debug(s"analysis location ${(cacheFile -> useBinary)}")
import scalacache.modes.sync._
updateCache(StandardMain.cache)(cacheFile, useBinary)
}
private[sbt] def getAnalyses(log: Logger): Future[Seq[Analysis]] = {
import scalacache.modes.scalaFuture._
import scala.concurrent.ExecutionContext.Implicits.global
StandardMain.cache
.get(AnalysesKey)
.collect {
case Some(a) => a.asInstanceOf[Set[((String, Boolean), Option[Analysis])]]
}
.map { caches =>
val (working, uninitialized) = caches.partition { cacheAnalysis =>
cacheAnalysis._2 != None
}
val addToCache = uninitialized.collect {
case (title @ (file, useBinary), _) if Files.exists(Paths.get(file)) =>
(title, storeAnalysis(Paths.get(file).toFile, !useBinary))
}
val validCaches = working ++ addToCache
if (addToCache.nonEmpty)
StandardMain.cache.put(AnalysesKey)(validCaches, Option(Inf))
validCaches.toSeq.collect {
case (_, Some(analysis)) =>
analysis
}
}
}
def lspDefinition(jsonDefinition: JValue,
requestId: String,
commandSource: CommandSource,
log: Logger)(implicit ec: ExecutionContext): Future[Unit] = Future {
val LspDefinitionLogHead = "lsp-definition"
import sjsonnew.support.scalajson.unsafe.CompactPrinter
log.debug(s"$LspDefinitionLogHead json request: ${CompactPrinter(jsonDefinition)}")
lazy val analyses = getAnalyses(log)
val definition = getDefinition(jsonDefinition)
definition
.flatMap { definition =>
val uri = URI.create(definition.textDocument.uri)
import java.nio.file._
Files
.lines(Paths.get(uri))
.skip(definition.position.line)
.findFirst
.toOption
.flatMap { line =>
log.debug(s"$LspDefinitionLogHead found line: $line")
textProcessor
.identifier(line, definition.position.character.toInt)
}
}
.map { sym =>
log.debug(s"symbol $sym")
analyses
.map { analyses =>
val locations = analyses.par.flatMap { analysis =>
val selectPotentials = textProcessor.potentialClsOrTraitOrObj(sym)
val classes =
(analysis.apis.allInternalClasses ++ analysis.apis.allExternals).collect {
selectPotentials
}
log.debug(s"$LspDefinitionLogHead potentials: $classes")
classes
.flatMap { className =>
analysis.relations.definesClass(className) ++ analysis.relations
.libraryDefinesClass(className)
}
.flatMap { classFile =>
textProcessor.markPosition(classFile, sym).collect {
case (file, line, from, to) =>
import sbt.internal.langserver.{ Location, Position, Range }
Location(file.toURI.toURL.toString,
Range(Position(line, from), Position(line, to)))
}
}
}.seq
log.debug(s"$LspDefinitionLogHead locations ${locations}")
import sbt.internal.langserver.codec.JsonProtocol._
send(commandSource, requestId)(locations.toArray)
}
.recover {
case anyException @ _ =>
log.warn(
s"Problem with processing analyses $anyException for ${CompactPrinter(jsonDefinition)}")
import sbt.internal.protocol.JsonRpcResponseError
import sbt.internal.protocol.codec.JsonRPCProtocol._
send(commandSource, requestId)(
JsonRpcResponseError(ErrorCodes.InternalError,
"Problem with processing analyses.",
None))
}
}
.orElse {
log.info(s"Symbol not found in definition request ${CompactPrinter(jsonDefinition)}")
import sbt.internal.langserver.Location
import sbt.internal.langserver.codec.JsonProtocol._
send(commandSource, requestId)(Array.empty[Location])
None
}
}
}

View File

@ -35,10 +35,8 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel {
protected def onSettingQuery(execId: Option[String], req: Q): Unit
protected def onRequestMessage(request: JsonRpcRequestMessage): Unit = {
import sbt.internal.langserver.codec.JsonProtocol._
import internalJsonProtocol._
def json =
request.params.getOrElse(
throw LangServerError(ErrorCodes.InvalidParams,
@ -57,9 +55,13 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel {
else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token")
} else ()
setInitialized(true)
append(Exec(s"collectAnalyses", Some(request.id), Some(CommandSource(name))))
langRespond(InitializeResult(serverCapabilities), Option(request.id))
case "textDocument/didSave" =>
append(Exec("compile", Some(request.id), Some(CommandSource(name))))
append(Exec(";compile; collectAnalyses", Some(request.id), Some(CommandSource(name))))
case "textDocument/definition" =>
import scala.concurrent.ExecutionContext.Implicits.global
Definition.lspDefinition(json, request.id, CommandSource(name), log)
case "sbt/exec" =>
val param = Converter.fromJson[SbtExecParams](json).get
append(Exec(param.commandLine, Some(request.id), Some(CommandSource(name))))
@ -68,7 +70,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel {
val param = Converter.fromJson[Q](json).get
onSettingQuery(Option(request.id), param)
}
case _ => ()
case unhandledRequest => log.debug(s"Unhandled request received: $unhandledRequest")
}
}
@ -149,6 +151,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel {
private[sbt] lazy val serverCapabilities: ServerCapabilities = {
ServerCapabilities(textDocumentSync =
TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)),
hoverProvider = false)
hoverProvider = false,
definitionProvider = true)
}
}

View File

@ -0,0 +1,177 @@
/*
* sbt
* Copyright 2011 - 2017, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under BSD-3-Clause license (see LICENSE)
*/
package sbt
package internal
package server
import sbt.internal.inc.Analysis
class DefinitionTest extends org.specs2.mutable.Specification {
import Definition.textProcessor
"text processor" should {
"find valid standard scala identifier when caret is set at the start of it" in {
textProcessor.identifier("val identifier = 0", 4) must beSome("identifier")
}
"not find valid standard scala identifier because it is '='" in {
textProcessor.identifier("val identifier = 0", 15) must beNone
}
"find valid standard scala identifier when caret is set in the middle of it" in {
textProcessor.identifier("val identifier = 0", 11) must beSome("identifier")
}
"find valid standard scala identifier with comma" in {
textProcessor.identifier("def foo(a: identifier, b: other) = ???", 13) must beSome(
"identifier")
}
"find valid standard short scala identifier when caret is set at the start of it" in {
textProcessor.identifier("val a = 0", 4) must beSome("a")
}
"find valid standard short scala identifier when caret is set at the end of it" in {
textProcessor.identifier("def foo(f: Int) = Foo(f)", 19) must beSome("Foo")
}
"find valid non-standard short scala identifier when caret is set at the start of it" in {
textProcessor.identifier("val == = 0", 4) must beSome("==")
}
"find valid non-standard short scala identifier when caret is set in the middle of it" in {
textProcessor.identifier("val == = 0", 5) must beSome("==")
}
"find valid non-standard short scala identifier when caret is set at the end of it" in {
textProcessor.identifier("val == = 0", 6) must beSome("==")
}
"choose longest valid standard scala identifier from scala keyword when caret is set at the start of it" in {
textProcessor.identifier("val k = 0", 0) must beSome("va") or beSome("al")
}
"choose longest valid standard scala identifier from scala keyword when caret is set in the middle of it" in {
textProcessor.identifier("val k = 0", 1) must beSome("va") or beSome("al")
}
"match symbol as class name" in {
textProcessor.potentialClsOrTraitOrObj("A")("com.acme.A") must be_==("com.acme.A")
}
"match symbol as object name" in {
textProcessor.potentialClsOrTraitOrObj("A")("com.acme.A$") must be_==("com.acme.A$")
}
"match symbol as inner class name" in {
textProcessor.potentialClsOrTraitOrObj("A")("com.acme.A$A") must be_==("com.acme.A$A")
}
"match symbol as inner object name" in {
textProcessor.potentialClsOrTraitOrObj("A")("com.acme.A$A$") must be_==("com.acme.A$A$")
}
"match symbol as default package class name" in {
textProcessor.potentialClsOrTraitOrObj("A")("A") must be_==("A")
}
"match symbol as default package object name" in {
textProcessor.potentialClsOrTraitOrObj("A")("A$") must be_==("A$")
}
"match object in line version 1" in {
textProcessor.classTraitObjectInLine("A")(" object A ") must contain(("object A", 3))
}
"match object in line version 2" in {
textProcessor.classTraitObjectInLine("A")(" object A ") must contain(("object A", 3))
}
"match object in line version 3" in {
textProcessor.classTraitObjectInLine("A")("object A {") must contain(("object A", 0))
}
"not match object in line" in {
textProcessor.classTraitObjectInLine("B")("object A ") must be empty
}
"match class in line version 1" in {
textProcessor.classTraitObjectInLine("A")(" class A ") must contain(("class A", 3))
}
"match class in line version 2" in {
textProcessor.classTraitObjectInLine("A")(" class A ") must contain(("class A", 3))
}
"match class in line version 3" in {
textProcessor.classTraitObjectInLine("A")("class A {") must contain(("class A", 0))
}
"match class in line version 4" in {
textProcessor.classTraitObjectInLine("A")(" class A[A] ") must contain(
("class A", 3))
}
"match class in line version 5" in {
textProcessor.classTraitObjectInLine("A")(" class A [A] ") must contain(
("class A", 3))
}
"match class in line version 6" in {
textProcessor.classTraitObjectInLine("A")("class A[A[_]] {") must contain(("class A", 0))
}
"not match class in line" in {
textProcessor.classTraitObjectInLine("B")("class A ") must be empty
}
"match trait in line version 1" in {
textProcessor.classTraitObjectInLine("A")(" trait A ") must contain(("trait A", 3))
}
"match trait in line version 2" in {
textProcessor.classTraitObjectInLine("A")(" trait A ") must contain(("trait A", 3))
}
"match trait in line version 3" in {
textProcessor.classTraitObjectInLine("A")("trait A {") must contain(("trait A", 0))
}
"match trait in line version 4" in {
textProcessor.classTraitObjectInLine("A")(" trait A[A] ") must contain(
("trait A", 3))
}
"match trait in line version 5" in {
textProcessor.classTraitObjectInLine("A")(" trait A [A] ") must contain(
("trait A", 3))
}
"match trait in line version 6" in {
textProcessor.classTraitObjectInLine("A")("trait A[A[_]] {") must contain(("trait A", 0))
}
"not match trait in line" in {
textProcessor.classTraitObjectInLine("B")("trait A ") must be empty
}
}
"definition" should {
import scalacache.caffeine._
import scalacache.modes.sync._
"cache data in cache" in {
val cache = CaffeineCache[Any]
val cacheFile = "Test.scala"
val useBinary = true
Definition.updateCache(cache)(cacheFile, useBinary)
val actual = cache.get(Definition.AnalysesKey)
actual.collect {
case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]]
}.get should contain("Test.scala" -> true -> None)
}
"replace cache data in cache" in {
val cache = CaffeineCache[Any]
val cacheFile = "Test.scala"
val useBinary = true
val falseUseBinary = false
Definition.updateCache(cache)(cacheFile, falseUseBinary)
Definition.updateCache(cache)(cacheFile, useBinary)
val actual = cache.get(Definition.AnalysesKey)
actual.collect {
case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]]
}.get should contain("Test.scala" -> true -> None)
}
"cache more data in cache" in {
val cache = CaffeineCache[Any]
val cacheFile = "Test.scala"
val useBinary = true
val otherCacheFile = "OtherTest.scala"
val otherUseBinary = false
Definition.updateCache(cache)(otherCacheFile, otherUseBinary)
Definition.updateCache(cache)(cacheFile, useBinary)
val actual = cache.get(Definition.AnalysesKey)
actual.collect {
case s => s.asInstanceOf[Set[((String, Boolean), Option[Analysis])]]
}.get should contain("Test.scala" -> true -> None, "OtherTest.scala" -> false -> None)
}
}
}

View File

@ -125,4 +125,6 @@ object Dependencies {
val log4jSlf4jImpl = "org.apache.logging.log4j" % "log4j-slf4j-impl" % log4jVersion
// specify all of log4j modules to prevent misalignment
val log4jDependencies = Vector(log4jApi, log4jCore, log4jSlf4jImpl)
val scalaCacheCaffeine = "com.github.cb372" %% "scalacache-caffeine" % "0.20.0"
}

View File

@ -7,22 +7,24 @@ package sbt.internal.langserver
final class ServerCapabilities private (
val textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions],
/** The server provides hover support. */
val hoverProvider: Option[Boolean]) extends Serializable {
val hoverProvider: Option[Boolean],
/** Goto definition */
val definitionProvider: Option[Boolean]) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: ServerCapabilities => (this.textDocumentSync == x.textDocumentSync) && (this.hoverProvider == x.hoverProvider)
case x: ServerCapabilities => (this.textDocumentSync == x.textDocumentSync) && (this.hoverProvider == x.hoverProvider) && (this.definitionProvider == x.definitionProvider)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.langserver.ServerCapabilities".##) + textDocumentSync.##) + hoverProvider.##)
37 * (37 * (37 * (37 * (17 + "sbt.internal.langserver.ServerCapabilities".##) + textDocumentSync.##) + hoverProvider.##) + definitionProvider.##)
}
override def toString: String = {
"ServerCapabilities(" + textDocumentSync + ", " + hoverProvider + ")"
"ServerCapabilities(" + textDocumentSync + ", " + hoverProvider + ", " + definitionProvider + ")"
}
protected[this] def copy(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions] = textDocumentSync, hoverProvider: Option[Boolean] = hoverProvider): ServerCapabilities = {
new ServerCapabilities(textDocumentSync, hoverProvider)
protected[this] def copy(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions] = textDocumentSync, hoverProvider: Option[Boolean] = hoverProvider, definitionProvider: Option[Boolean] = definitionProvider): ServerCapabilities = {
new ServerCapabilities(textDocumentSync, hoverProvider, definitionProvider)
}
def withTextDocumentSync(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions]): ServerCapabilities = {
copy(textDocumentSync = textDocumentSync)
@ -36,9 +38,15 @@ final class ServerCapabilities private (
def withHoverProvider(hoverProvider: Boolean): ServerCapabilities = {
copy(hoverProvider = Option(hoverProvider))
}
def withDefinitionProvider(definitionProvider: Option[Boolean]): ServerCapabilities = {
copy(definitionProvider = definitionProvider)
}
def withDefinitionProvider(definitionProvider: Boolean): ServerCapabilities = {
copy(definitionProvider = Option(definitionProvider))
}
}
object ServerCapabilities {
def apply(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions], hoverProvider: Option[Boolean]): ServerCapabilities = new ServerCapabilities(textDocumentSync, hoverProvider)
def apply(textDocumentSync: sbt.internal.langserver.TextDocumentSyncOptions, hoverProvider: Boolean): ServerCapabilities = new ServerCapabilities(Option(textDocumentSync), Option(hoverProvider))
def apply(textDocumentSync: Option[sbt.internal.langserver.TextDocumentSyncOptions], hoverProvider: Option[Boolean], definitionProvider: Option[Boolean]): ServerCapabilities = new ServerCapabilities(textDocumentSync, hoverProvider, definitionProvider)
def apply(textDocumentSync: sbt.internal.langserver.TextDocumentSyncOptions, hoverProvider: Boolean, definitionProvider: Boolean): ServerCapabilities = new ServerCapabilities(Option(textDocumentSync), Option(hoverProvider), Option(definitionProvider))
}

View File

@ -0,0 +1,34 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.langserver
/** Text documents are identified using a URI. On the protocol level, URIs are passed as strings. */
final class TextDocumentIdentifier private (
/** The text document's URI. */
val uri: String) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: TextDocumentIdentifier => (this.uri == x.uri)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (17 + "sbt.internal.langserver.TextDocumentIdentifier".##) + uri.##)
}
override def toString: String = {
"TextDocumentIdentifier(" + uri + ")"
}
protected[this] def copy(uri: String = uri): TextDocumentIdentifier = {
new TextDocumentIdentifier(uri)
}
def withUri(uri: String): TextDocumentIdentifier = {
copy(uri = uri)
}
}
object TextDocumentIdentifier {
def apply(uri: String): TextDocumentIdentifier = new TextDocumentIdentifier(uri)
}

View File

@ -0,0 +1,39 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.langserver
/** Goto definition params model */
final class TextDocumentPositionParams private (
/** The text document. */
val textDocument: sbt.internal.langserver.TextDocumentIdentifier,
/** The position inside the text document. */
val position: sbt.internal.langserver.Position) extends Serializable {
override def equals(o: Any): Boolean = o match {
case x: TextDocumentPositionParams => (this.textDocument == x.textDocument) && (this.position == x.position)
case _ => false
}
override def hashCode: Int = {
37 * (37 * (37 * (17 + "sbt.internal.langserver.TextDocumentPositionParams".##) + textDocument.##) + position.##)
}
override def toString: String = {
"TextDocumentPositionParams(" + textDocument + ", " + position + ")"
}
protected[this] def copy(textDocument: sbt.internal.langserver.TextDocumentIdentifier = textDocument, position: sbt.internal.langserver.Position = position): TextDocumentPositionParams = {
new TextDocumentPositionParams(textDocument, position)
}
def withTextDocument(textDocument: sbt.internal.langserver.TextDocumentIdentifier): TextDocumentPositionParams = {
copy(textDocument = textDocument)
}
def withPosition(position: sbt.internal.langserver.Position): TextDocumentPositionParams = {
copy(position = position)
}
}
object TextDocumentPositionParams {
def apply(textDocument: sbt.internal.langserver.TextDocumentIdentifier, position: sbt.internal.langserver.Position): TextDocumentPositionParams = new TextDocumentPositionParams(textDocument, position)
}

View File

@ -19,4 +19,6 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol
with sbt.internal.langserver.codec.LogMessageParamsFormats
with sbt.internal.langserver.codec.PublishDiagnosticsParamsFormats
with sbt.internal.langserver.codec.SbtExecParamsFormats
with sbt.internal.langserver.codec.TextDocumentIdentifierFormats
with sbt.internal.langserver.codec.TextDocumentPositionParamsFormats
object JsonProtocol extends JsonProtocol

View File

@ -13,8 +13,9 @@ implicit lazy val ServerCapabilitiesFormat: JsonFormat[sbt.internal.langserver.S
unbuilder.beginObject(js)
val textDocumentSync = unbuilder.readField[Option[sbt.internal.langserver.TextDocumentSyncOptions]]("textDocumentSync")
val hoverProvider = unbuilder.readField[Option[Boolean]]("hoverProvider")
val definitionProvider = unbuilder.readField[Option[Boolean]]("definitionProvider")
unbuilder.endObject()
sbt.internal.langserver.ServerCapabilities(textDocumentSync, hoverProvider)
sbt.internal.langserver.ServerCapabilities(textDocumentSync, hoverProvider, definitionProvider)
case None =>
deserializationError("Expected JsObject but found None")
}
@ -23,6 +24,7 @@ implicit lazy val ServerCapabilitiesFormat: JsonFormat[sbt.internal.langserver.S
builder.beginObject()
builder.addField("textDocumentSync", obj.textDocumentSync)
builder.addField("hoverProvider", obj.hoverProvider)
builder.addField("definitionProvider", obj.definitionProvider)
builder.endObject()
}
}

View File

@ -0,0 +1,27 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.langserver.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait TextDocumentIdentifierFormats { self: sjsonnew.BasicJsonProtocol =>
implicit lazy val TextDocumentIdentifierFormat: JsonFormat[sbt.internal.langserver.TextDocumentIdentifier] = new JsonFormat[sbt.internal.langserver.TextDocumentIdentifier] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.langserver.TextDocumentIdentifier = {
jsOpt match {
case Some(js) =>
unbuilder.beginObject(js)
val uri = unbuilder.readField[String]("uri")
unbuilder.endObject()
sbt.internal.langserver.TextDocumentIdentifier(uri)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.langserver.TextDocumentIdentifier, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("uri", obj.uri)
builder.endObject()
}
}
}

View File

@ -0,0 +1,29 @@
/**
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
*/
// DO NOT EDIT MANUALLY
package sbt.internal.langserver.codec
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
trait TextDocumentPositionParamsFormats { self: sbt.internal.langserver.codec.TextDocumentIdentifierFormats with sbt.internal.langserver.codec.PositionFormats with sjsonnew.BasicJsonProtocol =>
implicit lazy val TextDocumentPositionParamsFormat: JsonFormat[sbt.internal.langserver.TextDocumentPositionParams] = new JsonFormat[sbt.internal.langserver.TextDocumentPositionParams] {
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.langserver.TextDocumentPositionParams = {
jsOpt match {
case Some(js) =>
unbuilder.beginObject(js)
val textDocument = unbuilder.readField[sbt.internal.langserver.TextDocumentIdentifier]("textDocument")
val position = unbuilder.readField[sbt.internal.langserver.Position]("position")
unbuilder.endObject()
sbt.internal.langserver.TextDocumentPositionParams(textDocument, position)
case None =>
deserializationError("Expected JsObject but found None")
}
}
override def write[J](obj: sbt.internal.langserver.TextDocumentPositionParams, builder: Builder[J]): Unit = {
builder.beginObject()
builder.addField("textDocument", obj.textDocument)
builder.addField("position", obj.position)
builder.endObject()
}
}
}

View File

@ -83,6 +83,9 @@ type ServerCapabilities {
## The server provides hover support.
hoverProvider: Boolean
## Goto definition
definitionProvider: Boolean
}
type TextDocumentSyncOptions {
@ -127,3 +130,18 @@ type PublishDiagnosticsParams {
type SbtExecParams {
commandLine: String!
}
## Goto definition params model
type TextDocumentPositionParams {
## The text document.
textDocument: sbt.internal.langserver.TextDocumentIdentifier!
## The position inside the text document.
position: sbt.internal.langserver.Position!
}
## Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
type TextDocumentIdentifier {
## The text document's URI.
uri: String!
}