Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Worksheet improvements #5230

Merged
merged 3 commits into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,9 @@ class DottyLanguageServer extends LanguageServer
rootUri = params.getRootUri
assert(rootUri != null)

val c = new ServerCapabilities
class DottyServerCapabilities(val worksheetRunProvider: Boolean = true) extends lsp4j.ServerCapabilities

val c = new DottyServerCapabilities
c.setTextDocumentSync(TextDocumentSyncKind.Full)
c.setDocumentHighlightProvider(true)
c.setDocumentSymbolProvider(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ import java.util.concurrent.CancellationException
object Worksheet {

/**
* Evaluate `tree` as a worksheet using the REPL.
* Run `tree` as a worksheet using the REPL.
*
* @param tree The top level object wrapping the worksheet.
* @param sendMessage A mean of communicating the results of evaluation back.
* @param cancelChecker A token to check whether execution should be cancelled.
*/
def evaluate(tree: SourceTree,
sendMessage: (Int, String) => Unit,
cancelChecker: CancelChecker)(
def run(tree: SourceTree,
sendMessage: (Int, String) => Unit,
cancelChecker: CancelChecker)(
implicit ctx: Context): Unit = synchronized {

Evaluator.get(cancelChecker) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import org.eclipse.lsp4j.services.LanguageClient
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification

/**
* A `LanguageClient` that supports the `worksheet/publishOutput` notification.
*
* @see dotty.tools.languageserver.worksheet.WorksheetExecOutput
* A `LanguageClient` that supports worksheet-specific notifications.
*/
trait WorksheetClient extends LanguageClient {
/**
* A notification that tells the client that a line of a worksheet produced
* the specified output.
*/
@JsonNotification("worksheet/publishOutput")
def publishOutput(output: WorksheetExecOutput): Unit
def publishOutput(output: WorksheetRunOutput): Unit
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@ package dotty.tools.languageserver.worksheet

import org.eclipse.lsp4j.VersionedTextDocumentIdentifier

/** The parameter for the `worksheet/exec` request. */
case class WorksheetExecParams(textDocument: VersionedTextDocumentIdentifier) {
// Used for deserialization
// see https://github.com/lampepfl/dotty/pull/5102#discussion_r222055355
// All case classes in this file should have zero-parameters secondary
// constructors to allow Gson to reflectively create instances on
// deserialization without relying on sun.misc.Unsafe.

/** The parameter for the `worksheet/run` request. */
case class WorksheetRunParams(textDocument: VersionedTextDocumentIdentifier) {
def this() = this(null)
}

/** The response to a `worksheet/exec` request. */
case class WorksheetExecResponse(success: Boolean) {
// Used for deserialization
// see https://github.com/lampepfl/dotty/pull/5102#discussion_r222055355
/** The response to a `worksheet/run` request. */
case class WorksheetRunResult(success: Boolean) {
def this() = this(false)
}

/**
* A notification that tells the client that a line of a worksheet
* produced the specified output.
*/
case class WorksheetExecOutput(textDocument: VersionedTextDocumentIdentifier, line: Int, content: String) {
// Used for deserialization
// see https://github.com/lampepfl/dotty/pull/5102#discussion_r222055355
/** The parameters to the `worksheet/publishOutput` notification. */
case class WorksheetRunOutput(textDocument: VersionedTextDocumentIdentifier, line: Int, content: String) {
def this() = this(null, 0, null)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ trait WorksheetService { thisServer: DottyLanguageServer =>
val worksheets: ConcurrentHashMap[URI, CompletableFuture[_]] = new ConcurrentHashMap()

@JsonRequest
def exec(params: WorksheetExecParams): CompletableFuture[WorksheetExecResponse] = thisServer.synchronized {
def run(params: WorksheetRunParams): CompletableFuture[WorksheetRunResult] = thisServer.synchronized {
val uri = new URI(params.textDocument.getUri)
val future =
computeAsync { cancelChecker =>
try {
val driver = driverFor(uri)
val sendMessage = (line: Int, msg: String) => client.publishOutput(WorksheetExecOutput(params.textDocument, line, msg))
evaluateWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx)
WorksheetExecResponse(success = true)
val sendMessage = (line: Int, msg: String) => client.publishOutput(WorksheetRunOutput(params.textDocument, line, msg))
runWorksheet(driver, uri, sendMessage, cancelChecker)(driver.currentCtx)
WorksheetRunResult(success = true)
} catch {
case _: Throwable =>
WorksheetExecResponse(success = false)
WorksheetRunResult(success = false)
} finally {
worksheets.remove(uri)
}
Expand All @@ -37,21 +37,21 @@ trait WorksheetService { thisServer: DottyLanguageServer =>
}

/**
* Evaluate the worksheet at `uri`.
* Run the worksheet at `uri`.
*
* @param driver The driver for the project that contains the worksheet.
* @param uri The URI of the worksheet.
* @param sendMessage A mean of communicating the results of evaluation back.
* @param cancelChecker Token to check whether evaluation was cancelled
*/
private def evaluateWorksheet(driver: InteractiveDriver,
private def runWorksheet(driver: InteractiveDriver,
uri: URI,
sendMessage: (Int, String) => Unit,
cancelChecker: CancelChecker)(
implicit ctx: Context): Unit = {
val trees = driver.openedTrees(uri)
trees.headOption.foreach { tree =>
Worksheet.evaluate(tree, sendMessage, cancelChecker)
Worksheet.run(tree, sendMessage, cancelChecker)
}
}
}
34 changes: 17 additions & 17 deletions language-server/test/dotty/tools/languageserver/WorksheetTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,34 @@ import java.lang.System.{lineSeparator => nl}

class WorksheetTest {

@Test def evaluateExpression: Unit = {
@Test def runExpression: Unit = {
ws"${m1}2 + 2".withSource
.evaluate(m1, "1:val res0: Int = 4")
.run(m1, "1:val res0: Int = 4")
}

@Test def evaluateSimpleVal: Unit = {
@Test def runSimpleVal: Unit = {
ws"${m1}val foo = 123".withSource
.evaluate(m1, "1:val foo: Int = 123")
.run(m1, "1:val foo: Int = 123")
}

@Test def usePreviousDefinition: Unit = {
ws"""${m1}val foo = 123
val bar = foo + 1""".withSource
.evaluate(m1, "1:val foo: Int = 123",
.run(m1, "1:val foo: Int = 123",
"2:val bar: Int = 124")
}

@Test def defineObject: Unit = {
ws"""${m1}def foo(x: Int) = x + 1
foo(1)""".withSource
.evaluate(m1, "1:def foo(x: Int): Int",
.run(m1, "1:def foo(x: Int): Int",
"2:val res0: Int = 2")
}

@Test def defineCaseClass: Unit = {
ws"""${m1} case class Foo(x: Int)
Foo(1)""".withSource
.evaluate(m1, "1:// defined case class Foo",
.run(m1, "1:// defined case class Foo",
"2:val res0: Foo = Foo(1)")
}

Expand All @@ -48,15 +48,15 @@ class WorksheetTest {
override def toString: String = "Foo"
}
new Foo(1)""".withSource
.evaluate(m1, "3:// defined class Foo",
.run(m1, "3:// defined class Foo",
"4:val res0: Foo = Foo")
}

@Test def defineAnonymousClass0: Unit = {
ws"""${m1}new {
override def toString: String = "Foo"
}""".withSource
.evaluate(m1, "3:val res0: Object = Foo")
.run(m1, "3:val res0: Object = Foo")
}

@Test def defineAnonymousClass1: Unit = {
Expand All @@ -65,33 +65,33 @@ class WorksheetTest {
new Foo with Bar {
override def toString: String = "Foo"
}""".withSource
.evaluate(m1, "1:// defined class Foo",
.run(m1, "1:// defined class Foo",
"2:// defined trait Bar",
"5:val res0: Foo & Bar = Foo")
}

@Test def produceMultilineOutput: Unit = {
ws"""${m1}1 to 3 foreach println""".withSource
.evaluate(m1, s"1:1${nl}2${nl}3")
.run(m1, s"1:1${nl}2${nl}3")
}

@Test def patternMatching0: Unit = {
ws"""${m1}1 + 2 match {
case x if x % 2 == 0 => "even"
case _ => "odd"
}""".withSource
.evaluate(m1, "4:val res0: String = odd")
.run(m1, "4:val res0: String = odd")
}

@Test def patternMatching1: Unit = {
ws"""${m1}val (foo, bar) = (1, 2)""".withSource
.evaluate(m1, s"1:val foo: Int = 1${nl}val bar: Int = 2")
.run(m1, s"1:val foo: Int = 1${nl}val bar: Int = 2")
}

@Test def evaluationException: Unit = {
ws"""${m1}val foo = 1 / 0
val bar = 2""".withSource
.evaluateNonStrict(m1, "1:java.lang.ArithmeticException: / by zero",
.runNonStrict(m1, "1:java.lang.ArithmeticException: / by zero",
"2:val bar: Int = 2")
}

Expand Down Expand Up @@ -200,19 +200,19 @@ class WorksheetTest {
val bar = 2
while (true) {}
val baz = 3""".withSource
.cancelEvaluation(m1, afterMs = 5000)
.cancelRun(m1, afterMs = 5000)
}

@Test def systemExit(): Unit = {
ws"""${m1}println("Hello, world!")
System.exit(0)
println("Goodbye!")""".withSource
.evaluate(m1, "1:Hello, world!")
.run(m1, "1:Hello, world!")
}

@Test def outputOnStdErr(): Unit = {
ws"""${m1}System.err.println("Oh no")""".withSource
.evaluate(m1, "1:Oh no")
.run(m1, "1:Oh no")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -118,39 +118,39 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
doAction(new CodeSymbol(query, symbols))

/**
* Triggers evaluation of the worksheet specified by `marker`, verifies that the results of
* evaluation match `expected.
* Triggers running the worksheet specified by `marker`, verifies that the results of
* the run matches `expected`.
*
* @param marker A marker a identifies the worksheet to evaluate.
* @param expected The expected output.
*
* @see dotty.tools.languageserver.util.actions.WorksheetEvaluate
* @see dotty.tools.languageserver.util.actions.WorksheetRun
*/
def evaluate(marker: CodeMarker, expected: String*): this.type =
doAction(new WorksheetEvaluate(marker, expected, strict = true))
def run(marker: CodeMarker, expected: String*): this.type =
doAction(new WorksheetRun(marker, expected, strict = true))

/**
* Triggers evaluation of the worksheet specified by `marker`, verifies that each line of output
* Triggers running the worksheet specified by `marker`, verifies that each line of output
* starts with `expected`.
*
* @param marker A marker a identifies the worksheet to evaluate.
* @param expected The expected starts of output.
*
* @see dotty.tools.languageserver.util.actions.WorksheetEvaluate
* @see dotty.tools.languageserver.util.actions.WorksheetRun
*/
def evaluateNonStrict(marker: CodeMarker, expected: String*): this.type =
doAction(new WorksheetEvaluate(marker, expected, strict = false))
def runNonStrict(marker: CodeMarker, expected: String*): this.type =
doAction(new WorksheetRun(marker, expected, strict = false))

/**
* Triggers evaluation of the worksheet specified by `marker`, then verifies that execution can be
* Triggers running the worksheet specified by `marker`, then verifies that execution can be
* cancelled after `afterMs` milliseconds.
*
* @param marker A marker that identifier the worksheet to evaluate.
* @param afterMs The delay in milliseconds before cancelling execution.
*
* @see dotty.tools.languageserver.util.actions.WorksheetCancel
*/
def cancelEvaluation(marker: CodeMarker, afterMs: Long): this.type =
def cancelRun(marker: CodeMarker, afterMs: Long): this.type =
doAction(new WorksheetCancel(marker, afterMs))

private def doAction(action: Action): this.type = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dotty.tools.languageserver.util.actions

import dotty.tools.languageserver.worksheet.{WorksheetExecOutput, WorksheetExecParams, WorksheetExecResponse}
import dotty.tools.languageserver.worksheet.{WorksheetRunOutput, WorksheetRunParams, WorksheetRunResult}
import dotty.tools.languageserver.util.embedded.CodeMarker

import java.net.URI
Expand All @@ -10,13 +10,13 @@ import org.eclipse.lsp4j.VersionedTextDocumentIdentifier

abstract class WorksheetAction extends Action {

/** Triggers the evaluation of the worksheet. */
def triggerEvaluation(marker: CodeMarker): Exec[CompletableFuture[WorksheetExecResponse]] = {
server.exec(WorksheetExecParams(marker.toVersionedTextDocumentIdentifier))
/** Triggers running the worksheet. */
def triggerRun(marker: CodeMarker): Exec[CompletableFuture[WorksheetRunResult]] = {
server.run(WorksheetRunParams(marker.toVersionedTextDocumentIdentifier))
}

/** The output of the worksheet that contains `marker`. */
def worksheetOutput(marker: CodeMarker): Exec[List[WorksheetExecOutput]] = {
def worksheetOutput(marker: CodeMarker): Exec[List[WorksheetRunOutput]] = {
val textDocument = marker.toVersionedTextDocumentIdentifier
client.worksheetOutput.get.filter(_.textDocument.getUri == textDocument.getUri)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit
class WorksheetCancel(marker: CodeMarker, afterMs: Long) extends WorksheetAction {

override def execute(): Exec[Unit] = {
val futureResult = triggerEvaluation(marker)
val futureResult = triggerRun(marker)
Thread.sleep(afterMs)
val cancelled = futureResult.cancel(true)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import java.util.concurrent.TimeUnit

import org.junit.Assert.{assertEquals, assertTrue, fail}

class WorksheetEvaluate(marker: CodeMarker, expected: Seq[String], strict: Boolean) extends WorksheetAction {
class WorksheetRun(marker: CodeMarker, expected: Seq[String], strict: Boolean) extends WorksheetAction {

override def execute(): Exec[Unit] = {
val result = triggerEvaluation(marker).get(30, TimeUnit.SECONDS)
val result = triggerRun(marker).get(30, TimeUnit.SECONDS)
assertTrue(result.success)

val logs = worksheetOutput(marker).map(out => s"${out.line}:${out.content}")
Expand All @@ -26,5 +26,5 @@ class WorksheetEvaluate(marker: CodeMarker, expected: Seq[String], strict: Boole
}

override def show: PositionContext.PosCtx[String] =
s"WorksheetEvaluate(${marker.file}, ${expected})"
s"WorksheetRun(${marker.file}, ${expected})"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dotty.tools.languageserver.util.server

import dotty.tools.languageserver.worksheet.{WorksheetExecOutput, WorksheetClient}
import dotty.tools.languageserver.worksheet.{WorksheetRunOutput, WorksheetClient}

import java.util.concurrent.CompletableFuture

Expand All @@ -22,7 +22,7 @@ class TestClient extends WorksheetClient {
val log = new Log[MessageParams]
val diagnostics = new Log[PublishDiagnosticsParams]
val telemetry = new Log[Any]
val worksheetOutput = new Log[WorksheetExecOutput]
val worksheetOutput = new Log[WorksheetRunOutput]

override def logMessage(message: MessageParams) = {
log += message
Expand All @@ -45,7 +45,7 @@ class TestClient extends WorksheetClient {
diagnostics += diagnosticsParams
}

override def publishOutput(output: WorksheetExecOutput) = {
override def publishOutput(output: WorksheetRunOutput) = {
worksheetOutput += output
}

Expand Down