DRAFT: working language server connection

This commit is contained in:
Dmitry Bushev 2024-04-11 11:12:37 +01:00
parent 071a524723
commit b51843c198
No known key found for this signature in database
GPG Key ID: 87C16090D6910E91
9 changed files with 143 additions and 9 deletions

View File

@ -10,6 +10,6 @@ declare global {
const wss = new WebSocketServer({ host: 'localhost', port: 1234 })
wss.onconnect = (socket, url) => setupGatewayClient(socket, "ws://localhost:8080", url)
wss.onconnect = (socket, url) => setupGatewayClient(socket, "ws://127.0.0.1:30616", 'index')
wss.start()

View File

@ -165,9 +165,12 @@ class YjsConnection extends ObservableV2<{ close(): void }> {
}
send(message: Uint8Array) {
if (this.ws.readyState !== WebSocket.CONNECTING && this.ws.readyState !== WebSocket.OPEN) {
if (this.ws.readyState !== 0 && this.ws.readyState !== 1) {
this.close()
}
/* if (this.ws.readyState !== WebSocket.CONNECTING && this.ws.readyState !== WebSocket.OPEN) {
this.close()
} */
try {
this.ws.send(message, (error) => error && this.close())
} catch (e) {

View File

@ -156,14 +156,14 @@ class JsonConnectionController(
.subscribe(self, classOf[RefactoringProtocol.ProjectRenamedNotification])
}
override def receive: Receive = {
override def receive: Receive = LoggingReceive {
case JsonRpcServer.WebConnect(webActor, _) =>
unstashAll()
context.become(connected(webActor))
case _ => stash()
}
private def connected(webActor: ActorRef): Receive = {
private def connected(webActor: ActorRef): Receive = LoggingReceive {
case req @ Request(Ping, _, Unused) =>
val handler = context.actorOf(
PingHandler.props(
@ -212,7 +212,7 @@ class JsonConnectionController(
clientId: UUID,
request: Request[_, _],
receiver: ActorRef
): Receive = {
): Receive = LoggingReceive {
case _: InitializationComponentInitialized =>
logger.info("RPC session initialized for client [{}].", clientId)
val session = JsonSession(clientId, self)
@ -268,7 +268,7 @@ class JsonConnectionController(
receiver: ActorRef,
cancellable: Cancellable,
rootsSoFar: List[ContentRootWithFile]
): Receive = {
): Receive = LoggingReceive {
case ContentRootManagerProtocol.ContentRootsAddedNotification(roots) =>
val allRoots = roots ++ rootsSoFar
val hasProject = roots.exists {

View File

@ -9,6 +9,7 @@ import org.enso.polyfill.encoding.EncodingPolyfill;
import org.enso.polyfill.timers.TimersPolyfill;
import org.enso.polyfill.web.AbortControllerPolyfill;
import org.enso.polyfill.web.EventTargetPolyfill;
import org.enso.polyfill.web.PerformancePolyfill;
import org.enso.polyfill.websocket.WebSocketPolyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
@ -36,7 +37,7 @@ public class Main {
.allowExperimentalOptions(true)
.option("js.commonjs-require", "true")
.option("js.commonjs-require-cwd", commonJsRoot);
var chromePort = 34567;// Integer.getInteger("inspectPort", -1);
var chromePort = 34567; //Integer.getInteger("inspectPort", -1);
if (chromePort > 0) {
b.option("inspect", ":" + chromePort);
}
@ -49,6 +50,9 @@ public class Main {
CompletableFuture
.supplyAsync(b::build, executor)
.thenAcceptAsync(ctx -> {
var performance = new PerformancePolyfill();
performance.initialize(ctx);
var eventTarget = new EventTargetPolyfill(executor);
eventTarget.initialize(ctx);

View File

@ -0,0 +1,41 @@
package org.enso.polyfill.web;
import java.util.Arrays;
import org.enso.polyfill.Polyfill;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyExecutable;
public final class PerformancePolyfill implements ProxyExecutable, Polyfill {
private static final String NOW = "now";
private static final String PERFORMANCE_POLYFILL_JS = "performance-polyfill.js";
public PerformancePolyfill() {
}
@Override
public void initialize(Context ctx) {
Source performancePolyfillJs = Source
.newBuilder("js", PerformancePolyfill.class.getResource(PERFORMANCE_POLYFILL_JS))
.buildLiteral();
ctx.eval(performancePolyfillJs).execute(this);
}
@Override
public Object execute(Value... arguments) {
var command = arguments[0].asString();
System.err.println(command + " " + Arrays.toString(arguments));
return switch (command) {
case NOW ->
System.currentTimeMillis();
default ->
throw new IllegalStateException(command);
};
}
}

View File

@ -128,7 +128,6 @@ public final class WebSocketPolyfill implements ProxyExecutable, Polyfill {
}
var wsClient = WsClient.builder()
.addHeader("Connection", "Upgrade")
.protocolConfig(protocolConfig.build())
.build();
wsClient.connect(uri, connection);
@ -284,6 +283,7 @@ public final class WebSocketPolyfill implements ProxyExecutable, Polyfill {
listener.executeVoid(event);
} catch (Exception e) {
System.err.println("Error dispatching event [" + type + "] " + listener + " " + event + " " + e);
e.printStackTrace(System.err);
}
});
}

View File

@ -0,0 +1,12 @@
(function (jvm) {
class Performance {
now() {
return jvm('now');
};
}
globalThis.performance = new Performance();
})

View File

@ -0,0 +1,57 @@
package org.enso.polyfill;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.enso.polyfill.web.PerformancePolyfill;
import org.graalvm.polyglot.Context;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class PerformanceTest {
private Context context;
private ExecutorService executor;
public PerformanceTest() {
}
@Before
public void setup() throws Exception {
executor = Executors.newSingleThreadExecutor();
var eventTarget = new PerformancePolyfill();
var b = Context.newBuilder("js");
var chromePort = Integer.getInteger("inspectPort", -1);
if (chromePort > 0) {
b.option("inspect", ":" + chromePort);
}
context = CompletableFuture
.supplyAsync(() -> {
var ctx = b.build();
eventTarget.initialize(ctx);
return ctx;
}, executor)
.get();
}
@After
public void tearDown() {
executor.close();
context.close();
}
@Test
public void now() throws Exception {
var result = CompletableFuture
.supplyAsync(() -> context.eval("js", "performance.now()"), executor)
.get();
Assert.assertTrue(result.asLong() > 0);
Assert.assertTrue(result.asLong() <= System.currentTimeMillis());
}
}

View File

@ -2,6 +2,7 @@ package org.enso.jsonrpc
import akka.NotUsed
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.http.scaladsl.model.headers
import akka.http.scaladsl.model.ws.{BinaryMessage, Message, TextMessage}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
@ -10,6 +11,7 @@ import akka.stream.{Materializer, OverflowStrategy}
import com.typesafe.scalalogging.LazyLogging
import java.util.UUID
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
@ -94,7 +96,7 @@ class JsonRpcServer(
override protected def serverRoute(port: Int): Route = {
val webSocketEndpoint =
path(config.path) {
get { handleWebSocketMessages(newUser(port)) }
get { handleWebSocketMessages1(newUser(port)) }
}
optionalEndpoints.foldLeft(webSocketEndpoint) { (chain, next) =>
@ -104,6 +106,21 @@ class JsonRpcServer(
override protected def secureConfig(): Option[SecureConnectionConfig] =
config.secureConfig
// TODO[DB]: Workaround for helidon-io/helidon#8647
private def handleWebSocketMessages1(
handler: Flow[Message, Message, Any]
): Route =
extractWebSocketUpgrade { upgrade =>
complete(
upgrade
.handleMessages(handler, None)
.mapHeaders(_.map { h =>
if (h.is("connection")) headers.Connection("Upgrade") else h
})
)
}
}
object JsonRpcServer {