Skip to content

Commit 965a2c0

Browse files
authored
Merge pull request #5277 from dotty-staging/worksheet-fix-change
Worksheet improvements
2 parents c63cbd5 + 8c3319c commit 965a2c0

File tree

7 files changed

+213
-99
lines changed

7 files changed

+213
-99
lines changed

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ class DottyLanguageServer extends LanguageServer
107107
if (Memory.isCritical()) CompletableFutures.computeAsync { _ => restart() }
108108

109109
/** The driver instance responsible for compiling `uri` */
110-
def driverFor(uri: URI): InteractiveDriver = {
110+
def driverFor(uri: URI): InteractiveDriver = thisServer.synchronized {
111111
val matchingConfig =
112112
drivers.keys.find(config => config.sourceDirectories.exists(sourceDir =>
113113
new File(uri.getPath).getCanonicalPath.startsWith(sourceDir.getCanonicalPath)))
@@ -133,10 +133,10 @@ class DottyLanguageServer extends LanguageServer
133133
CompletableFuture.completedFuture(new Object)
134134
}
135135

136-
def computeAsync[R](fun: CancelChecker => R): CompletableFuture[R] =
136+
def computeAsync[R](fun: CancelChecker => R, synchronize: Boolean = true): CompletableFuture[R] =
137137
CompletableFutures.computeAsync { cancelToken =>
138138
// We do not support any concurrent use of the compiler currently.
139-
thisServer.synchronized {
139+
def computation(): R = {
140140
cancelToken.checkCanceled()
141141
checkMemory()
142142
try {
@@ -147,6 +147,10 @@ class DottyLanguageServer extends LanguageServer
147147
throw ex
148148
}
149149
}
150+
if (synchronize)
151+
thisServer.synchronized { computation() }
152+
else
153+
computation()
150154
}
151155

152156
override def initialize(params: InitializeParams) = computeAsync { cancelToken =>
@@ -202,10 +206,6 @@ class DottyLanguageServer extends LanguageServer
202206
val uri = new URI(document.getUri)
203207
val worksheetMode = isWorksheet(uri)
204208

205-
if (worksheetMode) {
206-
Option(worksheets.get(uri)).foreach(_.cancel(true))
207-
}
208-
209209
thisServer.synchronized {
210210
checkMemory()
211211

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ object Main {
7373
.setInput(in)
7474
.setOutput(out)
7575
// For debugging JSON messages:
76-
// .traceMessages(new java.io.PrintWriter(System.err, true))
76+
//.traceMessages(new java.io.PrintWriter(System.err, true))
7777
.create();
7878

7979
val client = launcher.getRemoteProxy()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ private object Evaluator {
2828
* @param cancelChecker The token that indicates whether evaluation has been cancelled.
2929
* @return A JVM running the REPL.
3030
*/
31-
def get(cancelChecker: CancelChecker)(implicit ctx: Context): Option[Evaluator] = {
31+
def get(cancelChecker: CancelChecker)(implicit ctx: Context): Option[Evaluator] = synchronized {
3232
val classpath = ctx.settings.classpath.value
3333
previousEvaluator match {
3434
case Some(cp, evaluator) if evaluator.isAlive() && cp == classpath =>

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

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,60 +10,65 @@ import dotty.tools.dotc.core.Flags.Synthetic
1010

1111
import org.eclipse.lsp4j.jsonrpc.CancelChecker
1212

13-
import java.util.concurrent.CancellationException
14-
1513
object Worksheet {
1614

1715
/**
1816
* Run `tree` as a worksheet using the REPL.
1917
*
2018
* @param tree The top level object wrapping the worksheet.
19+
* @param treeLock Object on which to lock when doing operations on trees.
2120
* @param sendMessage A mean of communicating the results of evaluation back.
2221
* @param cancelChecker A token to check whether execution should be cancelled.
2322
*/
2423
def run(tree: SourceTree,
24+
treeLock: Object,
2525
sendMessage: (Int, String) => Unit,
2626
cancelChecker: CancelChecker)(
27-
implicit ctx: Context): Unit = synchronized {
28-
29-
Evaluator.get(cancelChecker) match {
30-
case None =>
31-
sendMessage(1, "Couldn't start JVM.")
32-
case Some(evaluator) =>
33-
tree.tree match {
34-
case td @ TypeDef(_, template: Template) =>
35-
val executed = collection.mutable.Set.empty[(Int, Int)]
36-
37-
template.body.foreach {
38-
case statement: DefTree if statement.symbol.is(Synthetic) =>
39-
()
27+
implicit ctx: Context): Unit = {
28+
// For now, don't try to run multiple evaluators in parallel, this would require
29+
// changes to the logic of Evaluator.get among other things.
30+
Evaluator.synchronized {
31+
Evaluator.get(cancelChecker) match {
32+
case None =>
33+
sendMessage(1, "Couldn't start the JVM.")
34+
case Some(evaluator) =>
35+
val queries = treeLock.synchronized {
36+
tree.tree match {
37+
case td @ TypeDef(_, template: Template) =>
38+
val seen = collection.mutable.Set.empty[(Int, Int)]
4039

41-
case statement if evaluator.isAlive() && executed.add(bounds(statement.pos)) =>
42-
try {
43-
cancelChecker.checkCanceled()
44-
val (line, result) = execute(evaluator, statement, tree.source)
45-
if (result.nonEmpty) sendMessage(line, result)
46-
} catch { case _: CancellationException => () }
47-
48-
case _ =>
49-
()
40+
template.body.flatMap {
41+
case statement: DefTree if statement.symbol.is(Synthetic) =>
42+
None
43+
case statement if seen.add(bounds(statement.pos)) =>
44+
Some(query(statement, tree.source))
45+
case _ =>
46+
None
47+
}
5048
}
51-
}
49+
}
50+
queries.foreach { (line, code) =>
51+
cancelChecker.checkCanceled()
52+
val res = evaluator.eval(code).getOrElse("")
53+
cancelChecker.checkCanceled()
54+
if (res.nonEmpty)
55+
sendMessage(line, res)
56+
}
57+
}
5258
}
5359
}
5460

5561
/**
56-
* Extract `tree` from the source and evaluate it in the REPL.
62+
* Extract the line number and source code corresponding to this tree
5763
*
5864
* @param evaluator The JVM that runs the REPL.
5965
* @param tree The compiled tree to evaluate.
6066
* @param sourcefile The sourcefile of the worksheet.
61-
* @return The line in the sourcefile that corresponds to `tree`, and the result.
6267
*/
63-
private def execute(evaluator: Evaluator, tree: Tree, sourcefile: SourceFile): (Int, String) = {
64-
val source = sourcefile.content.slice(tree.pos.start, tree.pos.end).mkString
68+
private def query(tree: Tree, sourcefile: SourceFile): (Int, String) = {
6569
val line = sourcefile.offsetToLine(tree.pos.end)
66-
(line, evaluator.eval(source).getOrElse(""))
70+
val source = sourcefile.content.slice(tree.pos.start, tree.pos.end).mkString
71+
(line, source)
6772
}
6873

6974
private def bounds(pos: Position): (Int, Int) = (pos.start, pos.end)

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

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,23 @@ import java.util.concurrent.{CompletableFuture, ConcurrentHashMap}
1313
@JsonSegment("worksheet")
1414
trait WorksheetService { thisServer: DottyLanguageServer =>
1515

16-
val worksheets: ConcurrentHashMap[URI, CompletableFuture[_]] = new ConcurrentHashMap()
17-
1816
@JsonRequest
19-
def run(params: WorksheetRunParams): CompletableFuture[WorksheetRunResult] = thisServer.synchronized {
20-
val uri = new URI(params.textDocument.getUri)
21-
val future =
22-
computeAsync { cancelChecker =>
23-
try {
24-
val driver = driverFor(uri)
25-
val sendMessage = (line: Int, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, line, msg))
26-
runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx)
27-
WorksheetRunResult(success = true)
28-
} catch {
29-
case _: Throwable =>
30-
WorksheetRunResult(success = false)
31-
} finally {
32-
worksheets.remove(uri)
33-
}
17+
def run(params: WorksheetRunParams): CompletableFuture[WorksheetRunResult] =
18+
computeAsync(synchronize = false, fun = { cancelChecker =>
19+
val uri = new URI(params.textDocument.getUri)
20+
try {
21+
val driver = driverFor(uri)
22+
val sendMessage =
23+
(line: Int, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, line, msg))
24+
25+
runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx)
26+
cancelChecker.checkCanceled()
27+
WorksheetRunResult(success = true)
28+
} catch {
29+
case _: Throwable =>
30+
WorksheetRunResult(success = false)
3431
}
35-
worksheets.put(uri, future)
36-
future
37-
}
32+
})
3833

3934
/**
4035
* Run the worksheet at `uri`.
@@ -45,13 +40,13 @@ trait WorksheetService { thisServer: DottyLanguageServer =>
4540
* @param cancelChecker Token to check whether evaluation was cancelled
4641
*/
4742
private def runWorksheet(driver: InteractiveDriver,
48-
uri: URI,
49-
sendMessage: (Int, String) => Unit,
50-
cancelChecker: CancelChecker)(
43+
uri: URI,
44+
sendMessage: (Int, String) => Unit,
45+
cancelChecker: CancelChecker)(
5146
implicit ctx: Context): Unit = {
52-
val trees = driver.openedTrees(uri)
53-
trees.headOption.foreach { tree =>
54-
Worksheet.run(tree, sendMessage, cancelChecker)
47+
val treeOpt = thisServer.synchronized {
48+
driver.openedTrees(uri).headOption
5549
}
50+
treeOpt.foreach(tree => Worksheet.run(tree, thisServer, sendMessage, cancelChecker))
5651
}
5752
}

vscode-dotty/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
},
6060
{
6161
"command": "dotty.worksheet.cancel",
62-
"title": "Cancel worksheet evaluation",
62+
"title": "Cancel running worksheet",
6363
"category": "Scala"
6464
}
6565
],

0 commit comments

Comments
 (0)