mirror of https://github.com/sbt/sbt.git
Merge pull request #3777 from eed3si9n/wip/vsc-definition
textDocument/definition for LSP / VS Code
This commit is contained in:
commit
164b29da22
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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!
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue