Skip to content

Commit fa4daf1

Browse files
authored
Merge pull request #5900 from dotty-staging/worksheet-improvements
Worksheet: improve hover support
2 parents d101462 + 50be19d commit fa4daf1

File tree

14 files changed

+281
-853
lines changed

14 files changed

+281
-853
lines changed

docs/docs/usage/worksheet-mode-implementation-details.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ indicate that worksheet execution has produced some output.
6565
* The worksheet that produced this output.
6666
*/
6767
textDocument: VersionedTextDocumentIdentifier;
68-
68+
6969
/**
70-
* The line number of the expression that produced this output.
70+
* The range of the expression that produced this output.
7171
*/
72-
line: int;
73-
72+
range: Range;
73+
7474
/**
7575
* The output that has been produced.
7676
*/

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -240,17 +240,20 @@ class DottyLanguageServer extends LanguageServer
240240
val document = params.getTextDocument
241241
val uri = new URI(document.getUri)
242242
val driver = driverFor(uri)
243+
implicit def ctx: Context = driver.currentCtx
243244
val worksheetMode = isWorksheet(uri)
244245

245-
val (text, positionMapper) =
246-
if (worksheetMode) (wrapWorksheet(document.getText), Some(toUnwrappedPosition _))
247-
else (document.getText, None)
246+
val text =
247+
if (worksheetMode)
248+
wrapWorksheet(document.getText)
249+
else
250+
document.getText
248251

249252
val diags = driver.run(uri, text)
250253

251254
client.publishDiagnostics(new PublishDiagnosticsParams(
252255
document.getUri,
253-
diags.flatMap(diagnostic(_, positionMapper)(driver.currentCtx)).asJava))
256+
diags.flatMap(diagnostic).asJava))
254257
}
255258

256259
override def didChange(params: DidChangeTextDocumentParams): Unit = {
@@ -262,19 +265,22 @@ class DottyLanguageServer extends LanguageServer
262265
checkMemory()
263266

264267
val driver = driverFor(uri)
268+
implicit def ctx: Context = driver.currentCtx
265269

266270
val change = params.getContentChanges.get(0)
267271
assert(change.getRange == null, "TextDocumentSyncKind.Incremental support is not implemented")
268272

269-
val (text, positionMapper) =
270-
if (worksheetMode) (wrapWorksheet(change.getText), Some(toUnwrappedPosition _))
271-
else (change.getText, None)
273+
val text =
274+
if (worksheetMode)
275+
wrapWorksheet(change.getText)
276+
else
277+
change.getText
272278

273279
val diags = driver.run(uri, text)
274280

275281
client.publishDiagnostics(new PublishDiagnosticsParams(
276282
document.getUri,
277-
diags.flatMap(diagnostic(_, positionMapper)(driver.currentCtx)).asJava))
283+
diags.flatMap(diagnostic).asJava))
278284
}
279285
}
280286

@@ -299,7 +305,7 @@ class DottyLanguageServer extends LanguageServer
299305
override def completion(params: CompletionParams) = computeAsync { cancelToken =>
300306
val uri = new URI(params.getTextDocument.getUri)
301307
val driver = driverFor(uri)
302-
implicit val ctx = driver.currentCtx
308+
implicit def ctx: Context = driver.currentCtx
303309

304310
val pos = sourcePosition(driver, uri, params.getPosition)
305311
val items = driver.compilationUnits.get(uri) match {
@@ -318,13 +324,13 @@ class DottyLanguageServer extends LanguageServer
318324
override def definition(params: TextDocumentPositionParams) = computeAsync { cancelToken =>
319325
val uri = new URI(params.getTextDocument.getUri)
320326
val driver = driverFor(uri)
321-
implicit val ctx = driver.currentCtx
327+
implicit def ctx: Context = driver.currentCtx
322328

323329
val pos = sourcePosition(driver, uri, params.getPosition)
324330
val path = Interactive.pathTo(driver.openedTrees(uri), pos)
325331

326332
val definitions = Interactive.findDefinitions(path, pos, driver).toList
327-
definitions.flatMap(d => location(d.namePos, positionMapperFor(d.source))).asJava
333+
definitions.flatMap(d => location(d.namePos)).asJava
328334
}
329335

330336
override def references(params: ReferenceParams) = computeAsync { cancelToken =>
@@ -341,7 +347,7 @@ class DottyLanguageServer extends LanguageServer
341347
val pos = sourcePosition(driver, uri, params.getPosition)
342348

343349
val (definitions, originalSymbols) = {
344-
implicit val ctx: Context = driver.currentCtx
350+
implicit def ctx: Context = driver.currentCtx
345351
val path = Interactive.pathTo(driver.openedTrees(uri), pos)
346352
val definitions = Interactive.findDefinitions(path, pos, driver)
347353
val originalSymbols = Interactive.enclosingSourceSymbols(path, pos)
@@ -359,7 +365,7 @@ class DottyLanguageServer extends LanguageServer
359365
val name = definition.name(ctx).sourceModuleName.toString
360366
val trees = remoteDriver.sourceTreesContaining(name)(ctx)
361367
val matches = Interactive.findTreesMatching(trees, includes, definition)(ctx)
362-
matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source)))
368+
matches.map(tree => location(tree.namePos(ctx)))
363369
}
364370
}
365371
}.toList
@@ -370,7 +376,7 @@ class DottyLanguageServer extends LanguageServer
370376
override def rename(params: RenameParams) = computeAsync { cancelToken =>
371377
val uri = new URI(params.getTextDocument.getUri)
372378
val driver = driverFor(uri)
373-
implicit val ctx = driver.currentCtx
379+
implicit def ctx: Context = driver.currentCtx
374380

375381
val uriTrees = driver.openedTrees(uri)
376382
val pos = sourcePosition(driver, uri, params.getPosition)
@@ -429,15 +435,15 @@ class DottyLanguageServer extends LanguageServer
429435
.flatMap((uriOpt, ref) => uriOpt.map(uri => (uri.toString, ref)))
430436
.mapValues(refs =>
431437
refs.flatMap(ref =>
432-
range(ref.namePos, positionMapperFor(ref.source)).map(nameRange => new TextEdit(nameRange, newName))).distinct.asJava)
438+
range(ref.namePos).map(nameRange => new TextEdit(nameRange, newName))).distinct.asJava)
433439

434440
new WorkspaceEdit(changes.asJava)
435441
}
436442

437443
override def documentHighlight(params: TextDocumentPositionParams) = computeAsync { cancelToken =>
438444
val uri = new URI(params.getTextDocument.getUri)
439445
val driver = driverFor(uri)
440-
implicit val ctx = driver.currentCtx
446+
implicit def ctx: Context = driver.currentCtx
441447

442448
val pos = sourcePosition(driver, uri, params.getPosition)
443449
val uriTrees = driver.openedTrees(uri)
@@ -449,15 +455,15 @@ class DottyLanguageServer extends LanguageServer
449455
val refs = Interactive.findTreesMatching(uriTrees, includes, sym)
450456
(for {
451457
ref <- refs
452-
nameRange <- range(ref.namePos, positionMapperFor(ref.source))
458+
nameRange <- range(ref.namePos)
453459
} yield new DocumentHighlight(nameRange, DocumentHighlightKind.Read))
454460
}.distinct.asJava
455461
}
456462

457463
override def hover(params: TextDocumentPositionParams) = computeAsync { cancelToken =>
458464
val uri = new URI(params.getTextDocument.getUri)
459465
val driver = driverFor(uri)
460-
implicit val ctx = driver.currentCtx
466+
implicit def ctx: Context = driver.currentCtx
461467

462468
val pos = sourcePosition(driver, uri, params.getPosition)
463469
val trees = driver.openedTrees(uri)
@@ -481,26 +487,26 @@ class DottyLanguageServer extends LanguageServer
481487
override def documentSymbol(params: DocumentSymbolParams) = computeAsync { cancelToken =>
482488
val uri = new URI(params.getTextDocument.getUri)
483489
val driver = driverFor(uri)
484-
implicit val ctx = driver.currentCtx
490+
implicit def ctx: Context = driver.currentCtx
485491

486492
val uriTrees = driver.openedTrees(uri)
487493

488494
val defs = Interactive.namedTrees(uriTrees, Include.empty)
489495
(for {
490496
d <- defs if !isWorksheetWrapper(d)
491-
info <- symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source))
497+
info <- symbolInfo(d.tree.symbol, d.namePos)
492498
} yield JEither.forLeft(info)).asJava
493499
}
494500

495501
override def symbol(params: WorkspaceSymbolParams) = computeAsync { cancelToken =>
496502
val query = params.getQuery
497503

498504
drivers.values.toList.flatMap { driver =>
499-
implicit val ctx = driver.currentCtx
505+
implicit def ctx: Context = driver.currentCtx
500506

501507
val trees = driver.sourceTreesContaining(query)
502508
val defs = Interactive.namedTrees(trees, Include.empty, _.name.toString.contains(query))
503-
defs.flatMap(d => symbolInfo(d.tree.symbol, d.namePos, positionMapperFor(d.source)))
509+
defs.flatMap(d => symbolInfo(d.tree.symbol, d.namePos))
504510
}.asJava
505511
}
506512

@@ -511,7 +517,7 @@ class DottyLanguageServer extends LanguageServer
511517
val pos = sourcePosition(driver, uri, params.getPosition)
512518

513519
val (definitions, originalSymbols) = {
514-
implicit val ctx: Context = driver.currentCtx
520+
implicit def ctx: Context = driver.currentCtx
515521
val path = Interactive.pathTo(driver.openedTrees(uri), pos)
516522
val originalSymbols = Interactive.enclosingSourceSymbols(path, pos)
517523
val definitions = Interactive.findDefinitions(path, pos, driver)
@@ -528,7 +534,7 @@ class DottyLanguageServer extends LanguageServer
528534
tree => predicates.exists(_(tree))
529535
}
530536
val matches = Interactive.namedTrees(trees, Include.local, predicate)(ctx)
531-
matches.map(tree => location(tree.namePos(ctx), positionMapperFor(tree.source)))
537+
matches.map(tree => location(tree.namePos(ctx)))
532538
}
533539
}.toList
534540

@@ -539,7 +545,7 @@ class DottyLanguageServer extends LanguageServer
539545

540546
val uri = new URI(params.getTextDocument.getUri)
541547
val driver = driverFor(uri)
542-
implicit val ctx = driver.currentCtx
548+
implicit def ctx: Context = driver.currentCtx
543549

544550
val pos = sourcePosition(driver, uri, params.getPosition)
545551
val trees = driver.openedTrees(uri)
@@ -653,9 +659,9 @@ object DottyLanguageServer {
653659
}
654660

655661
/** Convert a SourcePosition to an lsp4j.Range */
656-
def range(p: SourcePosition, positionMapper: Option[SourcePosition => SourcePosition] = None): Option[lsp4j.Range] =
662+
def range(p: SourcePosition): Option[lsp4j.Range] =
657663
if (p.exists) {
658-
val mappedPosition = positionMapper.map(_(p)).getOrElse(p)
664+
val mappedPosition = positionMapperFor(p.source).map(_(p)).getOrElse(p)
659665
Some(new lsp4j.Range(
660666
new lsp4j.Position(mappedPosition.startLine, mappedPosition.startColumn),
661667
new lsp4j.Position(mappedPosition.endLine, mappedPosition.endColumn)
@@ -664,19 +670,16 @@ object DottyLanguageServer {
664670
None
665671

666672
/** Convert a SourcePosition to an lsp4.Location */
667-
def location(p: SourcePosition, positionMapper: Option[SourcePosition => SourcePosition] = None): Option[lsp4j.Location] =
673+
def location(p: SourcePosition): Option[lsp4j.Location] =
668674
for {
669675
uri <- toUriOption(p.source)
670-
r <- range(p, positionMapper)
676+
r <- range(p)
671677
} yield new lsp4j.Location(uri.toString, r)
672678

673679
/**
674-
* Convert a MessageContainer to an lsp4j.Diagnostic. The positions are transformed vy
675-
* `positionMapper`.
680+
* Convert a MessageContainer to an lsp4j.Diagnostic.
676681
*/
677-
def diagnostic(mc: MessageContainer,
678-
positionMapper: Option[SourcePosition => SourcePosition] = None
679-
)(implicit ctx: Context): Option[lsp4j.Diagnostic] =
682+
def diagnostic(mc: MessageContainer)(implicit ctx: Context): Option[lsp4j.Diagnostic] =
680683
if (!mc.pos.exists)
681684
None // diagnostics without positions are not supported: https://github.com/Microsoft/language-server-protocol/issues/249
682685
else {
@@ -697,7 +700,7 @@ object DottyLanguageServer {
697700
val message = mc.contained()
698701
if (displayMessage(message, mc.pos.source)) {
699702
val code = message.errorId.errorNumber.toString
700-
range(mc.pos, positionMapper).map(r =>
703+
range(mc.pos).map(r =>
701704
new lsp4j.Diagnostic(
702705
r, mc.message, severity(mc.level), /*source =*/ "", code))
703706
} else {
@@ -866,7 +869,7 @@ object DottyLanguageServer {
866869
}
867870

868871
/** Create an lsp4j.SymbolInfo from a Symbol and a SourcePosition */
869-
def symbolInfo(sym: Symbol, pos: SourcePosition, positionMapper: Option[SourcePosition => SourcePosition])(implicit ctx: Context): Option[lsp4j.SymbolInformation] = {
872+
def symbolInfo(sym: Symbol, pos: SourcePosition)(implicit ctx: Context): Option[lsp4j.SymbolInformation] = {
870873
def symbolKind(sym: Symbol)(implicit ctx: Context): lsp4j.SymbolKind = {
871874
import lsp4j.{SymbolKind => SK}
872875

@@ -893,7 +896,7 @@ object DottyLanguageServer {
893896
else
894897
null
895898

896-
location(pos, positionMapper).map(l => new lsp4j.SymbolInformation(name, symbolKind(sym), l, containerName))
899+
location(pos).map(l => new lsp4j.SymbolInformation(name, symbolKind(sym), l, containerName))
897900
}
898901

899902
/** Convert `signature` to a `SignatureInformation` */

language-server/src/dotty/tools/languageserver/worksheet/Worksheet.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import dotty.tools.dotc.ast.tpd.{DefTree, Template, Tree, TypeDef}
44
import dotty.tools.dotc.core.Contexts.Context
55
import dotty.tools.dotc.interactive.SourceTree
66
import dotty.tools.dotc.util.Spans.Span
7-
import dotty.tools.dotc.util.SourceFile
7+
import dotty.tools.dotc.util.{ SourceFile, SourcePosition, NoSourcePosition }
88

99
import dotty.tools.dotc.core.Flags.Synthetic
1010

@@ -22,15 +22,15 @@ object Worksheet {
2222
*/
2323
def run(tree: SourceTree,
2424
treeLock: Object,
25-
sendMessage: (Int, String) => Unit,
25+
sendMessage: (SourcePosition, String) => Unit,
2626
cancelChecker: CancelChecker)(
2727
implicit ctx: Context): Unit = {
2828
// For now, don't try to run multiple evaluators in parallel, this would require
2929
// changes to the logic of Evaluator.get among other things.
3030
Evaluator.synchronized {
3131
Evaluator.get(cancelChecker) match {
3232
case None =>
33-
sendMessage(1, "Couldn't start the JVM.")
33+
sendMessage(NoSourcePosition, "Couldn't start the JVM.")
3434
case Some(evaluator) =>
3535
val queries = treeLock.synchronized {
3636
tree.tree match {
@@ -59,16 +59,15 @@ object Worksheet {
5959
}
6060

6161
/**
62-
* Extract the line number and source code corresponding to this tree
62+
* Extract the position and source code corresponding to this tree
6363
*
6464
* @param evaluator The JVM that runs the REPL.
6565
* @param tree The compiled tree to evaluate.
6666
* @param sourcefile The sourcefile of the worksheet.
6767
*/
68-
private def query(tree: Tree, sourcefile: SourceFile): (Int, String) = {
69-
val line = sourcefile.offsetToLine(tree.span.end)
68+
private def query(tree: Tree, sourcefile: SourceFile)(implicit ctx: Context): (SourcePosition, String) = {
7069
val source = sourcefile.content.slice(tree.span.start, tree.span.end).mkString
71-
(line, source)
70+
(tree.sourcePos, source)
7271
}
7372

7473
private def bounds(span: Span): (Int, Int) = (span.start, span.end)

language-server/src/dotty/tools/languageserver/worksheet/WorksheetMessages.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dotty.tools.languageserver.worksheet
22

3-
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier
3+
import org.eclipse.lsp4j.{ Range, VersionedTextDocumentIdentifier }
44

55
// All case classes in this file should have zero-parameters secondary
66
// constructors to allow Gson to reflectively create instances on
@@ -17,6 +17,6 @@ case class WorksheetRunResult(success: Boolean) {
1717
}
1818

1919
/** The parameters to the `worksheet/publishOutput` notification. */
20-
case class WorksheetRunOutput(textDocument: VersionedTextDocumentIdentifier, line: Int, content: String) {
21-
def this() = this(null, 0, null)
20+
case class WorksheetRunOutput(textDocument: VersionedTextDocumentIdentifier, range: Range, content: String) {
21+
def this() = this(null, null, null)
2222
}

language-server/src/dotty/tools/languageserver/worksheet/WorksheetService.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package dotty.tools.languageserver.worksheet
22

33
import dotty.tools.dotc.core.Contexts.Context
4+
import dotty.tools.dotc.util.SourcePosition
45
import dotty.tools.dotc.interactive.InteractiveDriver
56
import dotty.tools.languageserver.DottyLanguageServer
7+
import dotty.tools.languageserver.DottyLanguageServer.range
68

79
import org.eclipse.lsp4j.jsonrpc._//{CancelChecker, CompletableFutures}
810
import org.eclipse.lsp4j.jsonrpc.services._//{JsonSegment, JsonRequest}
@@ -19,8 +21,8 @@ trait WorksheetService { thisServer: DottyLanguageServer =>
1921
val uri = new URI(params.textDocument.getUri)
2022
try {
2123
val driver = driverFor(uri)
22-
val sendMessage =
23-
(line: Int, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, line, msg))
24+
val sendMessage = (pos: SourcePosition, msg: String) =>
25+
client.publishOutput(WorksheetRunOutput(params.textDocument, range(pos).get, msg))
2426

2527
runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx)
2628
cancelChecker.checkCanceled()
@@ -41,7 +43,7 @@ trait WorksheetService { thisServer: DottyLanguageServer =>
4143
*/
4244
private def runWorksheet(driver: InteractiveDriver,
4345
uri: URI,
44-
sendMessage: (Int, String) => Unit,
46+
sendMessage: (SourcePosition, String) => Unit,
4547
cancelChecker: CancelChecker)(
4648
implicit ctx: Context): Unit = {
4749
val treeOpt = thisServer.synchronized {

0 commit comments

Comments
 (0)