Skip to content

Commit

Permalink
Implement goto definition fallback with the presentation compiler.
Browse files Browse the repository at this point in the history
Fixes #548. Previously, goto definition worked only for identifiers that
had successfully compiled in the build. Now, goto definition also works
for identifiers that you just typed in the buffer even if you haven't
saved the file.
  • Loading branch information
olafurpg committed Apr 10, 2019
1 parent ed7ee28 commit 7348bec
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 35 deletions.
@@ -1,6 +1,8 @@
package bench

import java.{util => ju}
import java.util.Optional
import org.eclipse.lsp4j.Location
import scala.meta.internal.metals.ClasspathSearch
import scala.meta.internal.metals.WorkspaceSymbolQuery
import scala.meta.pc.SymbolDocumentation
Expand All @@ -17,6 +19,8 @@ class ClasspathOnlySymbolSearch(classpath: ClasspathSearch)
override def documentation(symbol: String): Optional[SymbolDocumentation] =
Optional.empty()

def definition(symbol: String): ju.List[Location] = ju.Collections.emptyList()

override def search(
query: String,
buildTargetIdentifier: String,
Expand Down
15 changes: 14 additions & 1 deletion metals/src/main/scala/scala/meta/internal/metals/Compilers.scala
Expand Up @@ -4,9 +4,11 @@ import ch.epfl.scala.bsp4j.BuildTargetIdentifier
import ch.epfl.scala.bsp4j.CompileReport
import ch.epfl.scala.bsp4j.ScalaBuildTarget
import ch.epfl.scala.bsp4j.ScalacOptionsItem
import java.{util => ju}
import java.util.Collections
import java.util.Optional
import java.util.concurrent.ScheduledExecutorService
import org.eclipse.lsp4j.Location
import org.eclipse.lsp4j.CompletionItem
import org.eclipse.lsp4j.CompletionList
import org.eclipse.lsp4j.CompletionParams
Expand Down Expand Up @@ -153,6 +155,15 @@ class Compilers(
CompilerOffsetParams(pos.input.syntax, pos.input.text, pos.start, token)
)
}
def definition(
params: TextDocumentPositionParams,
token: CancelToken
): Option[ju.List[Location]] =
withPC(params, None) { (pc, pos) =>
pc.definition(
CompilerOffsetParams(pos.input.syntax, pos.input.text, pos.start, token)
)
}
def signatureHelp(
params: TextDocumentPositionParams,
token: CancelToken,
Expand Down Expand Up @@ -214,7 +225,9 @@ class Compilers(
)(fn: (PresentationCompiler, Position) => T): Option[T] = {
val path = params.getTextDocument.getUri.toAbsolutePath
loadCompiler(path, interactiveSemanticdbs).map { compiler =>
val input = path.toInputFromBuffers(buffers)
val input = path
.toInputFromBuffers(buffers)
.copy(path = params.getTextDocument.getUri())
val pos = params.getPosition.toMeta(input)
val result = fn(compiler, pos)
result
Expand Down
@@ -1,7 +1,10 @@
package scala.meta.internal.metals

import java.{util => ju}
import java.util.Collections
import org.eclipse.lsp4j.TextDocumentPositionParams
import org.eclipse.lsp4j.Location
import scala.meta.pc.CancelToken
import scala.meta.inputs.Input
import scala.meta.internal.metals.MetalsEnrichments._
import scala.meta.internal.mtags.GlobalSymbolIndex
Expand Down Expand Up @@ -36,23 +39,43 @@ final class DefinitionProvider(
semanticdbs: Semanticdbs,
icons: Icons,
statusBar: StatusBar,
warnings: Warnings
warnings: Warnings,
compilers: () => Compilers
) {

def definition(
path: AbsolutePath,
params: TextDocumentPositionParams
params: TextDocumentPositionParams,
token: CancelToken
): DefinitionResult = {
val result = semanticdbs.textDocument(path)
result.documentIncludingStale match {
semanticdbs.textDocument(path).documentIncludingStale match {
case Some(doc) =>
definitionFromSnapshot(path, params, doc)
case _ =>
warnings.noSemanticdb(path)
DefinitionResult.empty
val fromCompilers =
compilers()
.definition(params, token)
.getOrElse(Collections.emptyList())
if (!fromCompilers.isEmpty()) {
DefinitionResult(
fromCompilers,
"",
None,
None
)
} else {
warnings.noSemanticdb(path)
DefinitionResult.empty
}
}
}

def fromSymbol(sym: String): ju.List[Location] =
DefinitionDestination.fromSymbol(sym).flatMap(_.toResult) match {
case None => ju.Collections.emptyList()
case Some(destination) => destination.locations
}

def positionOccurrence(
source: AbsolutePath,
dirtyPosition: TextDocumentPositionParams,
Expand Down
Expand Up @@ -15,6 +15,7 @@ import java.net.URLClassLoader
import java.nio.charset.Charset

import scala.meta.internal.semanticdb.Scala._
import scala.meta.pc.CancelToken
import java.nio.charset.StandardCharsets
import java.nio.file._
import java.util
Expand Down Expand Up @@ -258,7 +259,8 @@ class MetalsLanguageServer(
semanticdbs,
config.icons,
statusBar,
warnings
warnings,
() => compilers
)
formattingProvider = new FormattingProvider(
workspace,
Expand Down Expand Up @@ -306,7 +308,11 @@ class MetalsLanguageServer(
() => userConfig,
buildTargets,
buffers,
new MetalsSymbolSearch(symbolDocs, workspaceSymbols),
new MetalsSymbolSearch(
symbolDocs,
workspaceSymbols,
definitionProvider
),
embedded,
statusBar,
sh
Expand Down Expand Up @@ -676,8 +682,8 @@ class MetalsLanguageServer(
def definition(
position: TextDocumentPositionParams
): CompletableFuture[util.List[Location]] =
CancelTokens { _ =>
definitionResult(position).locations
CancelTokens { token =>
definitionResult(position, token).locations
}

@JsonRequest("textDocument/typeDefinition")
Expand Down Expand Up @@ -1410,12 +1416,13 @@ class MetalsLanguageServer(
* The resolved symbol is used for testing purposes only.
*/
def definitionResult(
position: TextDocumentPositionParams
position: TextDocumentPositionParams,
token: CancelToken = EmptyCancelToken
): DefinitionResult = {
val source = position.getTextDocument.getUri.toAbsolutePath
if (source.toLanguage.isScala) {
val result = timedThunk("definition", config.statistics.isDefinition)(
definitionProvider.definition(source, position)
definitionProvider.definition(source, position, token)
)
// Record what build target this dependency source (if any) was jumped from,
// needed to know what classpath to compile the dependency source with.
Expand Down
@@ -1,7 +1,9 @@
package scala.meta.internal.metals

import ch.epfl.scala.bsp4j.BuildTargetIdentifier
import java.{util => ju}
import org.eclipse.lsp4j.Location
import java.util.Optional
import ch.epfl.scala.bsp4j.BuildTargetIdentifier
import scala.meta.pc.SymbolDocumentation
import scala.meta.pc.SymbolSearch
import scala.meta.pc.SymbolSearchVisitor
Expand All @@ -11,11 +13,16 @@ import scala.meta.pc.SymbolSearchVisitor
*/
class MetalsSymbolSearch(
docs: Docstrings,
wsp: WorkspaceSymbolProvider
wsp: WorkspaceSymbolProvider,
defn: DefinitionProvider
) extends SymbolSearch {
override def documentation(symbol: String): Optional[SymbolDocumentation] =
docs.documentation(symbol)

def definition(symbol: String): ju.List[Location] = {
defn.fromSymbol(symbol)
}

override def search(
query: String,
buildTargetIdentifier: String,
Expand Down
@@ -1,5 +1,6 @@
package scala.meta.pc;

import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.Hover;
Expand Down Expand Up @@ -55,6 +56,11 @@ public abstract class PresentationCompiler {
*/
public abstract Optional<Hover> hover(OffsetParams params);

/**
* Returns the definition of the symbol at the given position.
*/
public abstract List<Location> definition(OffsetParams params);

/**
* Returns the Protobuf byte array representation of a SemanticDB <code>TextDocument</code> for the given source.
*/
Expand Down
@@ -1,5 +1,7 @@
package scala.meta.pc;

import org.eclipse.lsp4j.Location;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -12,6 +14,11 @@ public interface SymbolSearch {
*/
Optional<SymbolDocumentation> documentation(String symbol);

/**
* Returns the definition of this symbol, if any.
*/
List<Location> definition(String symbol);

/**
* Runs fuzzy symbol search for the given query.
*
Expand Down
Expand Up @@ -14,6 +14,7 @@ import org.eclipse.lsp4j.MarkupContent
import org.eclipse.{lsp4j => l}
import scala.annotation.tailrec
import scala.collection.AbstractIterator
import scala.meta.pc.OffsetParams
import scala.meta.inputs.Input
import scala.meta.inputs.Position
import scala.meta.internal.io.FileIO
Expand Down Expand Up @@ -292,4 +293,19 @@ trait MtagsEnrichments {
)
}
}
implicit class XtensionOffsetParams(params: OffsetParams) {
def isDelimiter: Boolean = {
params.offset() < 0 ||
params.offset() >= params.text().length ||
(params.text().charAt(params.offset()) match {
case '(' | ')' | '{' | '}' | '[' | ']' | ',' | '=' | '.' => true
case _ => false
})
}
def isWhitespace: Boolean = {
params.offset() < 0 ||
params.offset() >= params.text().length ||
params.text().charAt(params.offset()).isWhitespace
}
}
}
@@ -1,5 +1,6 @@
package scala.meta.internal.pc

import java.net.URI
import java.lang.StringBuilder
import org.eclipse.{lsp4j => l}
import scala.meta.internal.semanticdb.Scala._
Expand Down Expand Up @@ -874,9 +875,10 @@ trait Completions { this: MetalsGlobal =>
override def contribute: List[Member] = {
try {
val name = Paths
.get(pos.source.file.name.stripSuffix(".scala"))
.get(URI.create(pos.source.file.name))
.getFileName()
.toString()
.stripSuffix(".scala")
val isTermName = toplevel.name.isTermName
val siblings = pkg.stats.count {
case d: DefTree =>
Expand Down
@@ -1,6 +1,8 @@
package scala.meta.internal.pc

import java.util.Optional
import java.{util => ju}
import org.eclipse.lsp4j.Location
import scala.meta.pc.SymbolDocumentation
import scala.meta.pc.SymbolSearch
import scala.meta.pc.SymbolSearchVisitor
Expand All @@ -14,6 +16,10 @@ object EmptySymbolSearch extends SymbolSearch {
SymbolSearch.Result.COMPLETE
}

def definition(symbol: String): ju.List[Location] = {
ju.Collections.emptyList()
}

override def documentation(symbol: String): Optional[SymbolDocumentation] =
Optional.empty()
}
Expand Up @@ -12,9 +12,7 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams) {
import compiler._

def hover(): Option[Hover] = {
if (params.offset() < 0 ||
params.offset() >= params.text().length ||
params.text().charAt(params.offset()).isWhitespace) {
if (params.isWhitespace) {
None
} else {
val unit = addCompilationUnit(
Expand Down Expand Up @@ -236,7 +234,7 @@ class HoverProvider(val compiler: MetalsGlobal, params: OffsetParams) {
}
val typedTree = typedTreeAt(pos)
typedTree match {
case Import(qual, _) if qual.pos.includes(pos) =>
case Import(qual, se) if qual.pos.includes(pos) =>
loop(qual)
case Apply(fun, args)
if !fun.pos.includes(pos) &&
Expand Down
26 changes: 15 additions & 11 deletions mtags/src/main/scala/scala/meta/internal/pc/MetalsGlobal.scala
Expand Up @@ -89,17 +89,21 @@ class MetalsGlobal(
}

def printPretty(pos: sourcecode.Text[Position]): Unit = {
import scala.meta.internal.metals.PositionSyntax._
val input = scala.meta.Input.String(new String(pos.value.source.content))
val (start, end) =
if (pos.value.isRange) {
(pos.value.start, pos.value.end)
} else {
(pos.value.point, pos.value.point)
}
val range =
scala.meta.Position.Range(input, start, end)
println(range.formatMessage("info", pos.source))
if (pos.value == null || pos.value == NoPosition) {
println(pos.value.toString())
} else {
import scala.meta.internal.metals.PositionSyntax._
val input = scala.meta.Input.String(new String(pos.value.source.content))
val (start, end) =
if (pos.value.isRange) {
(pos.value.start, pos.value.end)
} else {
(pos.value.point, pos.value.point)
}
val range =
scala.meta.Position.Range(input, start, end)
println(range.formatMessage("info", pos.source))
}
}

def pretty(pos: Position): String = {
Expand Down

0 comments on commit 7348bec

Please sign in to comment.