clean up and simplify

This commit is contained in:
bQUARKz 2026-05-06 02:58:18 +01:00
parent 14d4751f9a
commit 41f2c804a0
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
26 changed files with 497 additions and 1141 deletions

View File

@ -6,8 +6,6 @@ import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.events.StudioEventBus; import p.studio.lsp.events.StudioEventBus;
import p.studio.lsp.events.StudioPackerEventAdapter; import p.studio.lsp.events.StudioPackerEventAdapter;
import p.studio.lsp.services.LspServerLifecycleImpl; import p.studio.lsp.services.LspServerLifecycleImpl;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
import p.studio.lsp.services.host.TcpLspServerHostFactory;
import p.studio.utilities.ThemeService; import p.studio.utilities.ThemeService;
import p.studio.utilities.i18n.I18nService; import p.studio.utilities.i18n.I18nService;
@ -34,9 +32,7 @@ public final class AppContainer implements Container {
this.backgroundTasks = new StudioBackgroundTasks(backgroundExecutor); this.backgroundTasks = new StudioBackgroundTasks(backgroundExecutor);
final Packer packer = Packer.bootstrap(this.mapper, new StudioPackerEventAdapter(studioEventBus)); final Packer packer = Packer.bootstrap(this.mapper, new StudioPackerEventAdapter(studioEventBus));
this.embeddedPacker = new EmbeddedPacker(packer.workspaceService(), packer::close); this.embeddedPacker = new EmbeddedPacker(packer.workspaceService(), packer::close);
this.lspServerLifecycle = new LspServerLifecycleImpl( this.lspServerLifecycle = new LspServerLifecycleImpl();
new TcpLspServerHostFactory(),
new CompilerLanguageServiceBridge());
} }
@Override @Override

View File

@ -1,11 +1,10 @@
package p.studio.lsp.api; package p.studio.lsp.api;
import p.studio.lsp.messages.LspProjectContext; import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest; import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.lsp.messages.LspServerHandle;
public interface LspServerLifecycle { public interface LspServerLifecycle {
LspServerHandle bootServer(LspServerBootRequest request); void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context);
void shutdownServer(LspProjectContext project); void shutdownServer(LspProjectContext context);
} }

View File

@ -1,17 +0,0 @@
package p.studio.lsp.messages;
import java.util.Objects;
public record LspServerBootRequest(
LspProjectContext project,
LspServerConfiguration configuration) {
public LspServerBootRequest {
project = Objects.requireNonNull(project, "project");
configuration = Objects.requireNonNull(configuration, "configuration");
}
public static LspServerBootRequest defaults(final LspProjectContext project) {
return new LspServerBootRequest(project, LspServerConfiguration.defaults());
}
}

View File

@ -1,29 +1,19 @@
package p.studio.lsp.messages; package p.studio.lsp.messages;
import java.util.Objects; import lombok.Builder;
import lombok.Getter;
public record LspServerConfiguration( @Builder
String host, @Getter
int port) { public class LspServerConfiguration {
public static final String DEFAULT_HOST = "127.0.0.1"; public static final String DEFAULT_HOST = "127.0.0.1";
public static final int DEFAULT_PORT = 7777; public static final int DEFAULT_PORT = 7777;
public LspServerConfiguration { @Builder.Default
host = requireHost(host); private final LspServerEndpoint endpoint = new LspServerEndpoint(DEFAULT_HOST, DEFAULT_PORT);
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("port must be between 0 and 65535");
}
}
public static LspServerConfiguration defaults() { public static LspServerConfiguration defaults() {
return new LspServerConfiguration(DEFAULT_HOST, DEFAULT_PORT); return LspServerConfiguration.builder().build();
}
private static String requireHost(final String value) {
final String candidate = Objects.requireNonNull(value, "host").trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException("host must not be blank");
}
return candidate;
} }
} }

View File

@ -1,13 +0,0 @@
package p.studio.lsp.messages;
import java.util.Objects;
public record LspServerHandle(
LspProjectContext project,
LspServerEndpoint endpoint) {
public LspServerHandle {
project = Objects.requireNonNull(project, "project");
endpoint = Objects.requireNonNull(endpoint, "endpoint");
}
}

View File

@ -1,32 +0,0 @@
package p.studio.lsp.api;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
class LspApiBoundaryTest {
@Test
void apiSourceMustRemainFreeOfLsp4jAndProtocolPackages() throws IOException {
final Path sourceRoot = Path.of("src/main/java");
final List<Path> javaFiles;
try (var stream = Files.walk(sourceRoot)) {
javaFiles = stream
.filter(path -> path.toString().endsWith(".java"))
.toList();
}
for (final Path javaFile : javaFiles) {
final String source = Files.readString(javaFile);
assertTrue(!source.contains("org.eclipse.lsp4j"),
() -> "LSP4J leaked into lsp-api: " + javaFile);
assertTrue(!source.contains(".protocol."),
() -> "Protocol package leaked into lsp-api: " + javaFile);
}
}
}

View File

@ -1,44 +0,0 @@
package p.studio.lsp.api;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.messages.LspServerConfiguration;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class LspServerBootRequestTest {
@Test
void defaultsUseDeterministicLoopbackConfiguration() {
final LspProjectContext project = new LspProjectContext("demo", "pbs", Path.of("."));
final LspServerBootRequest request = LspServerBootRequest.defaults(project);
assertEquals(project, request.project());
assertEquals(LspServerConfiguration.DEFAULT_HOST, request.configuration().host());
assertEquals(LspServerConfiguration.DEFAULT_PORT, request.configuration().port());
}
@Test
void configurationRejectsInvalidValues() {
assertThrows(NullPointerException.class, () -> new LspServerConfiguration(null, 7777));
assertThrows(IllegalArgumentException.class, () -> new LspServerConfiguration(" ", 7777));
assertThrows(IllegalArgumentException.class, () -> new LspServerConfiguration("127.0.0.1", -1));
}
@Test
void projectContextNormalizesAndRejectsBlankFields() {
final LspProjectContext context = new LspProjectContext(" demo ", " pbs ", Path.of("."));
assertEquals("demo", context.projectKey());
assertEquals("pbs", context.languageId());
assertEquals(Path.of(".").toAbsolutePath().normalize(), context.projectRoot());
assertThrows(IllegalArgumentException.class, () -> new LspProjectContext(" ", "pbs", Path.of(".")));
assertThrows(IllegalArgumentException.class, () -> new LspProjectContext("demo", " ", Path.of(".")));
}
}

View File

@ -6,11 +6,11 @@ import p.studio.lsp.messages.BaselineServerDescription;
import p.studio.lsp.messages.LspProjectContext; import p.studio.lsp.messages.LspProjectContext;
public interface LanguageServiceBridge { public interface LanguageServiceBridge {
BaselineServerDescription describeServer(LspProjectContext project); BaselineServerDescription describeServer(LspProjectContext context);
BaselineDocumentAnalysis analyzeDocument(LspProjectContext project, String documentUri, String text); BaselineDocumentAnalysis analyzeDocument(LspProjectContext context, String documentUri, String text);
BaselineHover hover(LspProjectContext project, String documentUri, int line, int character); BaselineHover hover(LspProjectContext context, String documentUri, int line, int character);
String onSave(LspProjectContext project, String documentUri); String onSave(LspProjectContext context, String documentUri);
} }

View File

@ -2,43 +2,36 @@ package p.studio.lsp.services;
import p.studio.lsp.api.LspServerLifecycle; import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.messages.LspProjectContext; import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest; import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.lsp.messages.LspServerHandle;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge; import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
import p.studio.lsp.services.host.ProjectScopedLspServerHost;
import p.studio.lsp.services.host.ProjectScopedLspServerHostFactory;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
public final class LspServerLifecycleImpl implements LspServerLifecycle { public final class LspServerLifecycleImpl implements LspServerLifecycle {
private final ProjectScopedLspServerHostFactory serverHostFactory; private final TcpLspServerHostFactory serverHostFactory;
private final CompilerLanguageServiceBridge compilerBridge; private final CompilerLanguageServiceBridge compilerBridge;
private final ConcurrentMap<String, ProjectScopedLspServerHost> activeServers; private final ConcurrentMap<String, TcpLspServerHost> activeServers;
public LspServerLifecycleImpl( public LspServerLifecycleImpl() {
final ProjectScopedLspServerHostFactory serverHostFactory, this.compilerBridge = new CompilerLanguageServiceBridge();
final CompilerLanguageServiceBridge compilerBridge) { this.serverHostFactory = new TcpLspServerHostFactory();
this.serverHostFactory = Objects.requireNonNull(serverHostFactory, "serverHostFactory");
this.compilerBridge = Objects.requireNonNull(compilerBridge, "compilerBridge");
this.activeServers = new ConcurrentHashMap<>(); this.activeServers = new ConcurrentHashMap<>();
} }
@Override @Override
public LspServerHandle bootServer(final LspServerBootRequest request) { public void bootServer(final LspServerConfiguration serverConfiguration, LspProjectContext context) {
final LspServerBootRequest safeRequest = Objects.requireNonNull(request, "request"); final var host = serverHostFactory.create(serverConfiguration, context, compilerBridge);
final ProjectScopedLspServerHost host = serverHostFactory.create(safeRequest, compilerBridge); final var previous = activeServers.putIfAbsent(context.projectKey(), host);
final ProjectScopedLspServerHost previous = activeServers.putIfAbsent(safeRequest.project().projectKey(), host);
if (previous != null) { if (previous != null) {
throw new IllegalStateException("LSP server already booted for project " + safeRequest.project().projectKey()); throw new IllegalStateException("LSP server already booted for project " + context.projectKey());
} }
try { try {
final var endpoint = host.start(); host.start();
return new LspServerHandle(safeRequest.project(), endpoint);
} catch (RuntimeException runtimeException) { } catch (RuntimeException runtimeException) {
activeServers.remove(safeRequest.project().projectKey(), host); activeServers.remove(context.projectKey(), host);
closeQuietly(host); closeQuietly(host);
throw runtimeException; throw runtimeException;
} }
@ -46,15 +39,15 @@ public final class LspServerLifecycleImpl implements LspServerLifecycle {
@Override @Override
public void shutdownServer(final LspProjectContext project) { public void shutdownServer(final LspProjectContext project) {
final LspProjectContext safeProject = Objects.requireNonNull(project, "project"); final var safeProject = Objects.requireNonNull(project, "project");
final ProjectScopedLspServerHost host = activeServers.remove(safeProject.projectKey()); final var host = activeServers.remove(safeProject.projectKey());
if (host == null) { if (host == null) {
return; return;
} }
closeQuietly(host); closeQuietly(host);
} }
private static void closeQuietly(final ProjectScopedLspServerHost host) { private static void closeQuietly(final TcpLspServerHost host) {
try { try {
host.close(); host.close();
} catch (Exception exception) { } catch (Exception exception) {

View File

@ -1,10 +1,10 @@
package p.studio.lsp.services.host; package p.studio.lsp.services;
import org.eclipse.lsp4j.jsonrpc.Launcher; import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.launch.LSPLauncher; import org.eclipse.lsp4j.launch.LSPLauncher;
import org.eclipse.lsp4j.services.LanguageClient; import org.eclipse.lsp4j.services.LanguageClient;
import p.studio.lsp.messages.LspServerBootRequest; import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerEndpoint; import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge; import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
import p.studio.lsp.services.protocol.PrometeuLanguageServer; import p.studio.lsp.services.protocol.PrometeuLanguageServer;
@ -16,19 +16,21 @@ import java.util.Objects;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
public final class TcpLspServerHost implements ProjectScopedLspServerHost { public final class TcpLspServerHost {
private final LspServerBootRequest request; private final LspServerConfiguration serverConfiguration;
private final LspProjectContext context;
private final CompilerLanguageServiceBridge compilerBridge; private final CompilerLanguageServiceBridge compilerBridge;
private final ExecutorService clientExecutor; private final ExecutorService clientExecutor;
private volatile boolean closed; private volatile boolean closed;
private volatile ServerSocket serverSocket; private volatile ServerSocket serverSocket;
private volatile Thread acceptThread; private volatile Thread acceptThread;
private volatile LspServerEndpoint endpoint;
public TcpLspServerHost( public TcpLspServerHost(
final LspServerBootRequest request, final LspServerConfiguration serverConfiguration,
final LspProjectContext context,
final CompilerLanguageServiceBridge compilerBridge) { final CompilerLanguageServiceBridge compilerBridge) {
this.request = Objects.requireNonNull(request, "request"); this.serverConfiguration = Objects.requireNonNull(serverConfiguration, "serverConfiguration");
this.context = Objects.requireNonNull(context, "context");
this.compilerBridge = Objects.requireNonNull(compilerBridge, "compilerBridge"); this.compilerBridge = Objects.requireNonNull(compilerBridge, "compilerBridge");
this.clientExecutor = Executors.newCachedThreadPool(runnable -> { this.clientExecutor = Executors.newCachedThreadPool(runnable -> {
final Thread thread = new Thread(runnable); final Thread thread = new Thread(runnable);
@ -38,20 +40,16 @@ public final class TcpLspServerHost implements ProjectScopedLspServerHost {
}); });
} }
@Override public synchronized void start() {
public synchronized LspServerEndpoint start() { if (serverSocket != null) {
if (endpoint != null) { return;
return endpoint;
} }
try { try {
final InetAddress bindAddress = InetAddress.getByName(request.configuration().host()); final InetAddress bindAddress = InetAddress.getByName(serverConfiguration.getEndpoint().host());
final ServerSocket socket = new ServerSocket(request.configuration().port(), 50, bindAddress); serverSocket = new ServerSocket(serverConfiguration.getEndpoint().port(), 50, bindAddress);
serverSocket = socket;
endpoint = new LspServerEndpoint(bindAddress.getHostAddress(), socket.getLocalPort());
acceptThread = new Thread(this::acceptLoop, "prometeu-lsp-v1-accept"); acceptThread = new Thread(this::acceptLoop, "prometeu-lsp-v1-accept");
acceptThread.setDaemon(true); acceptThread.setDaemon(true);
acceptThread.start(); acceptThread.start();
return endpoint;
} catch (IOException exception) { } catch (IOException exception) {
throw new IllegalStateException("failed to start TCP LSP host", exception); throw new IllegalStateException("failed to start TCP LSP host", exception);
} }
@ -72,7 +70,7 @@ public final class TcpLspServerHost implements ProjectScopedLspServerHost {
private void handleClient(final Socket socket) { private void handleClient(final Socket socket) {
try (socket) { try (socket) {
final var server = new PrometeuLanguageServer(request.project(), compilerBridge); final var server = new PrometeuLanguageServer(context, compilerBridge);
final Launcher<LanguageClient> launcher = LSPLauncher.createServerLauncher( final Launcher<LanguageClient> launcher = LSPLauncher.createServerLauncher(
server, server,
socket.getInputStream(), socket.getInputStream(),
@ -84,7 +82,6 @@ public final class TcpLspServerHost implements ProjectScopedLspServerHost {
} }
} }
@Override
public synchronized void close() throws Exception { public synchronized void close() throws Exception {
if (closed) { if (closed) {
return; return;

View File

@ -0,0 +1,14 @@
package p.studio.lsp.services;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
public final class TcpLspServerHostFactory {
public TcpLspServerHost create(
final LspServerConfiguration serverConfiguration,
final LspProjectContext context,
final CompilerLanguageServiceBridge compilerBridge) {
return new TcpLspServerHost(serverConfiguration, context, compilerBridge);
}
}

View File

@ -4,10 +4,11 @@ import p.studio.compiler.workspaces.BuilderPipelineService;
import p.studio.lsp.messages.*; import p.studio.lsp.messages.*;
import p.studio.lsp.services.LanguageServiceBridge; import p.studio.lsp.services.LanguageServiceBridge;
import java.util.List;
import java.util.Objects; import java.util.Objects;
public final class CompilerLanguageServiceBridge implements LanguageServiceBridge { public record CompilerLanguageServiceBridge(
private final BuilderPipelineService builderPipelineService; BuilderPipelineService builderPipelineService) implements LanguageServiceBridge {
public CompilerLanguageServiceBridge() { public CompilerLanguageServiceBridge() {
this(BuilderPipelineService.INSTANCE); this(BuilderPipelineService.INSTANCE);
@ -17,13 +18,9 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
this.builderPipelineService = Objects.requireNonNull(builderPipelineService, "builderPipelineService"); this.builderPipelineService = Objects.requireNonNull(builderPipelineService, "builderPipelineService");
} }
public BuilderPipelineService builderPipelineService() {
return builderPipelineService;
}
@Override @Override
public BaselineServerDescription describeServer(final LspProjectContext project) { public BaselineServerDescription describeServer(final LspProjectContext context) {
Objects.requireNonNull(project, "project"); Objects.requireNonNull(context, "context");
return new BaselineServerDescription( return new BaselineServerDescription(
"Prometeu Studio LSP", "Prometeu Studio LSP",
"0.1.0", "0.1.0",
@ -32,13 +29,13 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
@Override @Override
public BaselineDocumentAnalysis analyzeDocument( public BaselineDocumentAnalysis analyzeDocument(
final LspProjectContext project, final LspProjectContext context,
final String documentUri, final String documentUri,
final String text) { final String text) {
Objects.requireNonNull(project, "project"); Objects.requireNonNull(context, "context");
// Wave 1 keeps responses intentionally synthetic while hardening the bridge boundary. // Wave 1 keeps responses intentionally synthetic while hardening the bridge boundary.
final String message = "Compiler-backed baseline via " + builderPipelineService.getClass().getSimpleName(); final String message = "Compiler-backed baseline via " + builderPipelineService.getClass().getSimpleName();
return new BaselineDocumentAnalysis(java.util.List.of(new BaselineDocumentIssue( return new BaselineDocumentAnalysis(List.of(new BaselineDocumentIssue(
0, 0,
0, 0,
0, 0,
@ -50,11 +47,11 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
@Override @Override
public BaselineHover hover( public BaselineHover hover(
final LspProjectContext project, final LspProjectContext context,
final String documentUri, final String documentUri,
final int line, final int line,
final int character) { final int character) {
Objects.requireNonNull(project, "project"); Objects.requireNonNull(context, "context");
return new BaselineHover(""" return new BaselineHover("""
**Prometeu Studio LSP** **Prometeu Studio LSP**
@ -66,9 +63,9 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
@Override @Override
public String onSave( public String onSave(
final LspProjectContext project, final LspProjectContext context,
final String documentUri) { final String documentUri) {
Objects.requireNonNull(project, "project"); Objects.requireNonNull(context, "context");
return "Prometeu Studio saw save: " + documentUri; return "Prometeu Studio saw save: " + documentUri;
} }

View File

@ -1,10 +0,0 @@
package p.studio.lsp.services.host;
import p.studio.lsp.messages.LspServerEndpoint;
public interface ProjectScopedLspServerHost extends AutoCloseable {
LspServerEndpoint start();
@Override
void close() throws Exception;
}

View File

@ -1,10 +0,0 @@
package p.studio.lsp.services.host;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
public interface ProjectScopedLspServerHostFactory {
ProjectScopedLspServerHost create(
LspServerBootRequest request,
CompilerLanguageServiceBridge compilerBridge);
}

View File

@ -1,13 +0,0 @@
package p.studio.lsp.services.host;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
public final class TcpLspServerHostFactory implements ProjectScopedLspServerHostFactory {
@Override
public ProjectScopedLspServerHost create(
final LspServerBootRequest request,
final CompilerLanguageServiceBridge compilerBridge) {
return new TcpLspServerHost(request, compilerBridge);
}
}

View File

@ -1,63 +0,0 @@
package p.studio.lsp.services;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.messages.LspServerEndpoint;
import p.studio.lsp.services.compiler.CompilerLanguageServiceBridge;
import p.studio.lsp.services.host.ProjectScopedLspServerHost;
import p.studio.lsp.services.host.ProjectScopedLspServerHostFactory;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class LspServerLifecycleImplTest {
@Test
void lifecycleBootsAndShutsDownProjectScopedHost() {
final FakeHost host = new FakeHost();
final ProjectScopedLspServerHostFactory factory = (request, compilerBridge) -> host;
final var lifecycle = new LspServerLifecycleImpl(factory, new CompilerLanguageServiceBridge());
final var project = new LspProjectContext("demo", "pbs", Path.of("."));
final var handle = lifecycle.bootServer(LspServerBootRequest.defaults(project));
assertEquals(project, handle.project());
assertEquals("127.0.0.1", handle.endpoint().host());
assertEquals(7777, handle.endpoint().port());
assertTrue(host.started);
lifecycle.shutdownServer(project);
assertTrue(host.closed);
}
@Test
void lifecycleRejectsDoubleBootForSameProject() {
final ProjectScopedLspServerHostFactory factory = (request, compilerBridge) -> new FakeHost();
final var lifecycle = new LspServerLifecycleImpl(factory, new CompilerLanguageServiceBridge());
final var request = LspServerBootRequest.defaults(new LspProjectContext("demo", "pbs", Path.of(".")));
lifecycle.bootServer(request);
assertThrows(IllegalStateException.class, () -> lifecycle.bootServer(request));
}
private static final class FakeHost implements ProjectScopedLspServerHost {
private boolean started;
private boolean closed;
@Override
public LspServerEndpoint start() {
started = true;
return new LspServerEndpoint("127.0.0.1", 7777);
}
@Override
public void close() {
closed = true;
}
}
}

View File

@ -1,46 +0,0 @@
package p.studio.lsp.services.boundary;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class LspV1BoundaryTest {
@Test
void lsp4jMustRemainContainedInsideLspV1Sources() throws IOException {
final List<Path> forbiddenRoots = List.of(
Path.of("../prometeu-lsp-api/src/main/java"),
Path.of("../../../prometeu-studio/src/main/java"),
Path.of("../../../prometeu-app/src/main/java"),
Path.of("../../../prometeu-compiler"),
Path.of("../../../prometeu-packer"),
Path.of("../../../prometeu-infra"));
for (final Path root : forbiddenRoots) {
assertNoLsp4jImports(root.normalize());
}
}
private static void assertNoLsp4jImports(final Path root) throws IOException {
if (!Files.exists(root)) {
return;
}
final List<Path> javaFiles;
try (var stream = Files.walk(root)) {
javaFiles = stream
.filter(path -> path.toString().endsWith(".java"))
.toList();
}
for (final Path javaFile : javaFiles) {
final String source = Files.readString(javaFile);
assertFalse(source.contains("org.eclipse.lsp4j"), () -> "LSP4J leaked outside lsp-v1: " + javaFile);
}
}
}

View File

@ -1,32 +0,0 @@
package p.studio.lsp.services.compiler;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.BaselineDocumentAnalysis;
import p.studio.lsp.messages.BaselineHover;
import p.studio.lsp.messages.BaselineServerDescription;
import p.studio.lsp.messages.LspProjectContext;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CompilerLanguageServiceBridgeTest {
@Test
void bridgeProvidesStableBaselineResponses() {
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
final LspProjectContext project = new LspProjectContext("demo", "pbs", Path.of("."));
final BaselineServerDescription server = bridge.describeServer(project);
final BaselineDocumentAnalysis analysis = bridge.analyzeDocument(project, "file:///demo.pbs", "let x = 1");
final BaselineHover hover = bridge.hover(project, "file:///demo.pbs", 0, 0);
assertEquals("Prometeu Studio LSP", server.name());
assertTrue(server.hoverSupported());
assertFalse(analysis.issues().isEmpty());
assertTrue(analysis.issues().getFirst().message().contains("BuilderPipelineService"));
assertTrue(hover.markdown().contains("Compiler-backed baseline"));
}
}

View File

@ -1,175 +0,0 @@
package p.studio.lsp.services.protocol;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.BaselineDocumentAnalysis;
import p.studio.lsp.messages.BaselineHover;
import p.studio.lsp.messages.BaselineServerDescription;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.services.LanguageServiceBridge;
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletionException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PrometeuLanguageServerTest {
@Test
void initializeDelegatesThroughBridgeAndMapper() {
final RecordingBridge bridge = new RecordingBridge();
final RecordingMapper mapper = new RecordingMapper();
final InitializeResult expected = new InitializeResult();
mapper.initializeResult = expected;
final PrometeuLanguageServer server = new PrometeuLanguageServer(
new LspProjectContext("demo", "pbs", Path.of(".")),
bridge,
mapper,
new NoopTextDocumentService(),
new PrometeuWorkspaceService());
final InitializeParams params = new InitializeParams();
params.setRootUri(Path.of(".").toAbsolutePath().normalize().toUri().toString());
final InitializeResult result = server.initialize(params).join();
assertSame(expected, result);
assertEquals(1, bridge.describeServerCalls);
assertSame(bridge.serverDescription, mapper.lastServerDescription);
}
@Test
void initializeRejectsClientBoundToDifferentProjectRoot() {
final RecordingBridge bridge = new RecordingBridge();
final RecordingMapper mapper = new RecordingMapper();
final PrometeuLanguageServer server = new PrometeuLanguageServer(
new LspProjectContext("demo", "pbs", Path.of("/tmp/studio-project")),
bridge,
mapper,
new NoopTextDocumentService(),
new PrometeuWorkspaceService());
final InitializeParams params = new InitializeParams();
params.setRootUri(Path.of("/tmp/another-project").toUri().toString());
final CompletionException failure = org.junit.jupiter.api.Assertions.assertThrows(
CompletionException.class,
() -> server.initialize(params).join());
final ResponseErrorException responseError = assertInstanceOf(ResponseErrorException.class, failure.getCause());
assertTrue(responseError.getMessage().contains("/tmp/studio-project"));
assertEquals(0, bridge.describeServerCalls);
}
@Test
void initializeAcceptsMatchingWorkspaceFolder() {
final RecordingBridge bridge = new RecordingBridge();
final RecordingMapper mapper = new RecordingMapper();
mapper.initializeResult = new InitializeResult();
final Path projectRoot = Path.of("/tmp/studio-project").toAbsolutePath().normalize();
final PrometeuLanguageServer server = new PrometeuLanguageServer(
new LspProjectContext("demo", "pbs", projectRoot),
bridge,
mapper,
new NoopTextDocumentService(),
new PrometeuWorkspaceService());
final InitializeParams params = new InitializeParams();
params.setWorkspaceFolders(List.of(new WorkspaceFolder(projectRoot.toUri().toString(), "studio-project")));
server.initialize(params).join();
assertEquals(1, bridge.describeServerCalls);
}
private static final class RecordingBridge implements LanguageServiceBridge {
private int describeServerCalls;
private final BaselineServerDescription serverDescription = new BaselineServerDescription("demo", "1", true);
@Override
public BaselineServerDescription describeServer(final LspProjectContext project) {
describeServerCalls++;
return serverDescription;
}
@Override
public BaselineDocumentAnalysis analyzeDocument(final LspProjectContext project, final String documentUri, final String text) {
return new BaselineDocumentAnalysis(List.of());
}
@Override
public BaselineHover hover(final LspProjectContext project, final String documentUri, final int line, final int character) {
return new BaselineHover("hover");
}
@Override
public String onSave(final LspProjectContext project, final String documentUri) {
return "saved";
}
}
private static final class RecordingMapper implements ProtocolMessageMapper {
private InitializeResult initializeResult;
private BaselineServerDescription lastServerDescription;
@Override
public InitializeResult mapInitializeResult(final BaselineServerDescription description) {
lastServerDescription = description;
return initializeResult;
}
@Override
public PublishDiagnosticsParams mapDiagnostics(final String uri, final BaselineDocumentAnalysis analysis) {
throw new UnsupportedOperationException();
}
@Override
public PublishDiagnosticsParams emptyDiagnostics(final String uri) {
throw new UnsupportedOperationException();
}
@Override
public org.eclipse.lsp4j.Hover mapHover(final BaselineHover hover) {
throw new UnsupportedOperationException();
}
@Override
public org.eclipse.lsp4j.MessageParams mapInfoMessage(final String message) {
return new org.eclipse.lsp4j.MessageParams();
}
@Override
public org.eclipse.lsp4j.MessageParams mapErrorMessage(final String message) {
return new org.eclipse.lsp4j.MessageParams();
}
}
private static final class NoopTextDocumentService implements TextDocumentService {
@Override
public void didOpen(final org.eclipse.lsp4j.DidOpenTextDocumentParams params) {
}
@Override
public void didChange(final org.eclipse.lsp4j.DidChangeTextDocumentParams params) {
}
@Override
public void didClose(final org.eclipse.lsp4j.DidCloseTextDocumentParams params) {
}
@Override
public void didSave(final org.eclipse.lsp4j.DidSaveTextDocumentParams params) {
}
}
}

View File

@ -1,141 +0,0 @@
package p.studio.lsp.services.protocol;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;
import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.*;
import p.studio.lsp.services.LanguageServiceBridge;
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
class PrometeuTextDocumentServiceTest {
@Test
void documentEventsDelegateToBridgeAndMapper() {
final RecordingBridge bridge = new RecordingBridge();
final RecordingMapper mapper = new RecordingMapper();
final RecordingClient client = new RecordingClient();
final PrometeuTextDocumentService service = new PrometeuTextDocumentService(
new LspProjectContext("demo", "pbs", Path.of(".")),
bridge,
mapper);
service.connect(client);
service.didOpen(new DidOpenTextDocumentParams(new TextDocumentItem("file:///demo.pbs", "pbs", 1, "abc")));
service.didSave(new DidSaveTextDocumentParams(new TextDocumentIdentifier("file:///demo.pbs"), "abc"));
service.hover(new HoverParams(new TextDocumentIdentifier("file:///demo.pbs"), new Position(0, 0))).join();
assertEquals(1, bridge.analyzeCalls);
assertEquals(1, bridge.saveCalls);
assertEquals(1, bridge.hoverCalls);
assertSame(bridge.lastAnalysis, mapper.lastAnalysis);
assertSame(bridge.lastHover, mapper.lastHover);
assertSame(mapper.diagnostics, client.lastDiagnostics);
assertSame(mapper.infoMessage, client.lastLogMessage);
}
private static final class RecordingBridge implements LanguageServiceBridge {
private int analyzeCalls;
private int saveCalls;
private int hoverCalls;
private final BaselineDocumentAnalysis lastAnalysis = new BaselineDocumentAnalysis(List.of(
new BaselineDocumentIssue(0, 0, 0, 1, BaselineIssueSeverity.INFORMATION, "src", "msg")));
private final BaselineHover lastHover = new BaselineHover("hover");
@Override
public BaselineServerDescription describeServer(final LspProjectContext project) {
return new BaselineServerDescription("demo", "1", true);
}
@Override
public BaselineDocumentAnalysis analyzeDocument(final LspProjectContext project, final String documentUri, final String text) {
analyzeCalls++;
return lastAnalysis;
}
@Override
public BaselineHover hover(final LspProjectContext project, final String documentUri, final int line, final int character) {
hoverCalls++;
return lastHover;
}
@Override
public String onSave(final LspProjectContext project, final String documentUri) {
saveCalls++;
return "saved";
}
}
private static final class RecordingMapper implements ProtocolMessageMapper {
private final PublishDiagnosticsParams diagnostics = new PublishDiagnosticsParams("file:///demo.pbs", List.of());
private final MessageParams infoMessage = new MessageParams(MessageType.Info, "saved");
private BaselineDocumentAnalysis lastAnalysis;
private BaselineHover lastHover;
@Override
public InitializeResult mapInitializeResult(final BaselineServerDescription description) {
throw new UnsupportedOperationException();
}
@Override
public PublishDiagnosticsParams mapDiagnostics(final String uri, final BaselineDocumentAnalysis analysis) {
lastAnalysis = analysis;
return diagnostics;
}
@Override
public PublishDiagnosticsParams emptyDiagnostics(final String uri) {
return new PublishDiagnosticsParams(uri, List.of());
}
@Override
public Hover mapHover(final BaselineHover hover) {
lastHover = hover;
return new Hover();
}
@Override
public MessageParams mapInfoMessage(final String message) {
return infoMessage;
}
@Override
public MessageParams mapErrorMessage(final String message) {
return infoMessage;
}
}
private static final class RecordingClient implements LanguageClient {
private PublishDiagnosticsParams lastDiagnostics;
private MessageParams lastLogMessage;
@Override
public void telemetryEvent(final Object object) {
}
@Override
public void publishDiagnostics(final PublishDiagnosticsParams diagnostics) {
lastDiagnostics = diagnostics;
}
@Override
public void showMessage(final MessageParams messageParams) {
}
@Override
public CompletableFuture<MessageActionItem> showMessageRequest(final ShowMessageRequestParams requestParams) {
return CompletableFuture.completedFuture(null);
}
@Override
public void logMessage(final MessageParams message) {
lastLogMessage = message;
}
}
}

View File

@ -2,6 +2,7 @@ package p.studio.projects;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import p.packer.messages.PackerProjectContext; import p.packer.messages.PackerProjectContext;
import p.studio.lsp.messages.LspProjectContext;
import java.nio.file.Path; import java.nio.file.Path;
@ -35,4 +36,11 @@ public record ProjectReference(
public PackerProjectContext toPackerProjectContext() { public PackerProjectContext toPackerProjectContext() {
return new PackerProjectContext(name, absoluteRootPath()); return new PackerProjectContext(name, absoluteRootPath());
} }
public LspProjectContext toLspProjectContext() {
return new LspProjectContext(
rootPath().toAbsolutePath().normalize().toString(),
languageId(),
rootPath());
}
} }

View File

@ -1,13 +1,11 @@
package p.studio.projectsessions; package p.studio.projectsessions;
import p.studio.execution.StudioExecutionSessionService; import p.studio.execution.StudioExecutionSessionService;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerHandle;
import p.studio.lsp.api.LspServerLifecycle; import p.studio.lsp.api.LspServerLifecycle;
import p.studio.projects.ProjectReference;
import p.studio.projectstate.ProjectLocalStudioSetup; import p.studio.projectstate.ProjectLocalStudioSetup;
import p.studio.projectstate.ProjectLocalStudioState; import p.studio.projectstate.ProjectLocalStudioState;
import p.studio.projectstate.ProjectLocalStudioStateService; import p.studio.projectstate.ProjectLocalStudioStateService;
import p.studio.projects.ProjectReference;
import java.util.Objects; import java.util.Objects;
@ -17,7 +15,6 @@ public final class StudioProjectSession implements AutoCloseable {
private final ProjectLocalStudioSetup projectLocalStudioSetup; private final ProjectLocalStudioSetup projectLocalStudioSetup;
private final StudioExecutionSessionService executionSessionService; private final StudioExecutionSessionService executionSessionService;
private final LspServerLifecycle lspServerLifecycle; private final LspServerLifecycle lspServerLifecycle;
private final LspServerHandle lspServerHandle;
private ProjectLocalStudioState projectLocalStudioState; private ProjectLocalStudioState projectLocalStudioState;
private boolean closed; private boolean closed;
@ -29,7 +26,6 @@ public final class StudioProjectSession implements AutoCloseable {
ProjectLocalStudioSetup.defaults(), ProjectLocalStudioSetup.defaults(),
new StudioExecutionSessionService(), new StudioExecutionSessionService(),
null, null,
null,
ProjectLocalStudioState.defaults()); ProjectLocalStudioState.defaults());
} }
@ -39,14 +35,12 @@ public final class StudioProjectSession implements AutoCloseable {
final ProjectLocalStudioSetup projectLocalStudioSetup, final ProjectLocalStudioSetup projectLocalStudioSetup,
final StudioExecutionSessionService executionSessionService, final StudioExecutionSessionService executionSessionService,
final LspServerLifecycle lspServerLifecycle, final LspServerLifecycle lspServerLifecycle,
final LspServerHandle lspServerHandle,
final ProjectLocalStudioState projectLocalStudioState) { final ProjectLocalStudioState projectLocalStudioState) {
this.projectReference = Objects.requireNonNull(projectReference, "projectReference"); this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
this.projectLocalStudioStateService = Objects.requireNonNull(projectLocalStudioStateService, "projectLocalStudioStateService"); this.projectLocalStudioStateService = Objects.requireNonNull(projectLocalStudioStateService, "projectLocalStudioStateService");
this.projectLocalStudioSetup = Objects.requireNonNull(projectLocalStudioSetup, "projectLocalStudioSetup"); this.projectLocalStudioSetup = Objects.requireNonNull(projectLocalStudioSetup, "projectLocalStudioSetup");
this.executionSessionService = Objects.requireNonNull(executionSessionService, "executionSessionService"); this.executionSessionService = Objects.requireNonNull(executionSessionService, "executionSessionService");
this.lspServerLifecycle = lspServerLifecycle; this.lspServerLifecycle = lspServerLifecycle;
this.lspServerHandle = lspServerHandle;
this.projectLocalStudioState = Objects.requireNonNull(projectLocalStudioState, "projectLocalStudioState"); this.projectLocalStudioState = Objects.requireNonNull(projectLocalStudioState, "projectLocalStudioState");
} }
@ -66,10 +60,6 @@ public final class StudioProjectSession implements AutoCloseable {
return executionSessionService; return executionSessionService;
} }
public LspServerHandle lspServerHandle() {
return lspServerHandle;
}
public void replaceProjectLocalStudioState(final ProjectLocalStudioState nextProjectLocalStudioState) { public void replaceProjectLocalStudioState(final ProjectLocalStudioState nextProjectLocalStudioState) {
this.projectLocalStudioState = Objects.requireNonNull(nextProjectLocalStudioState, "nextProjectLocalStudioState"); this.projectLocalStudioState = Objects.requireNonNull(nextProjectLocalStudioState, "nextProjectLocalStudioState");
} }
@ -106,10 +96,9 @@ public final class StudioProjectSession implements AutoCloseable {
} }
private void shutdownLspServer() { private void shutdownLspServer() {
if (lspServerLifecycle == null || lspServerHandle == null) { if (lspServerLifecycle == null) {
return; return;
} }
final LspProjectContext projectContext = lspServerHandle.project(); lspServerLifecycle.shutdownServer(projectReference.toLspProjectContext());
lspServerLifecycle.shutdownServer(projectContext);
} }
} }

View File

@ -1,13 +1,11 @@
package p.studio.projectsessions; package p.studio.projectsessions;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.messages.LspServerHandle;
import p.studio.lsp.api.LspServerLifecycle;
import p.studio.execution.StudioExecutionSessionService; import p.studio.execution.StudioExecutionSessionService;
import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.projects.ProjectReference;
import p.studio.projectstate.ProjectLocalStudioSetupService; import p.studio.projectstate.ProjectLocalStudioSetupService;
import p.studio.projectstate.ProjectLocalStudioStateService; import p.studio.projectstate.ProjectLocalStudioStateService;
import p.studio.projects.ProjectReference;
import java.util.Objects; import java.util.Objects;
@ -36,22 +34,13 @@ public final class StudioProjectSessionFactory {
final ProjectReference target = Objects.requireNonNull(projectReference, "projectReference"); final ProjectReference target = Objects.requireNonNull(projectReference, "projectReference");
final var setup = projectLocalStudioSetupService.load(target); final var setup = projectLocalStudioSetupService.load(target);
final var state = projectLocalStudioStateService.load(target); final var state = projectLocalStudioStateService.load(target);
final LspProjectContext lspProjectContext = toLspProjectContext(target); lspServerLifecycle.bootServer(LspServerConfiguration.defaults(), target.toLspProjectContext());
final LspServerHandle lspServerHandle = lspServerLifecycle.bootServer(LspServerBootRequest.defaults(lspProjectContext));
return new StudioProjectSession( return new StudioProjectSession(
target, target,
projectLocalStudioStateService, projectLocalStudioStateService,
setup, setup,
new StudioExecutionSessionService(), new StudioExecutionSessionService(),
lspServerLifecycle, lspServerLifecycle,
lspServerHandle,
state); state);
} }
private static LspProjectContext toLspProjectContext(final ProjectReference projectReference) {
return new LspProjectContext(
projectReference.rootPath().toAbsolutePath().normalize().toString(),
projectReference.languageId(),
projectReference.rootPath());
}
} }

View File

@ -1,23 +1,18 @@
package p.studio.projectsessions; package p.studio.projectsessions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerBootRequest;
import p.studio.lsp.messages.LspServerHandle;
import p.studio.lsp.api.LspServerLifecycle; import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.messages.LspServerEndpoint; import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.projects.ProjectReference;
import p.studio.projectstate.ProjectLocalStudioSetup; import p.studio.projectstate.ProjectLocalStudioSetup;
import p.studio.projectstate.ProjectLocalStudioSetupService; import p.studio.projectstate.ProjectLocalStudioSetupService;
import p.studio.projectstate.ProjectLocalStudioState; import p.studio.projectstate.ProjectLocalStudioState;
import p.studio.projectstate.ProjectLocalStudioStateService; import p.studio.projectstate.ProjectLocalStudioStateService;
import p.studio.projects.ProjectReference;
import java.nio.file.Path; import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
final class StudioProjectSessionFactoryTest { final class StudioProjectSessionFactoryTest {
@Test @Test
@ -44,10 +39,9 @@ final class StudioProjectSessionFactoryTest {
assertEquals("IDLE", session.executionSession().snapshot().state().name()); assertEquals("IDLE", session.executionSession().snapshot().state().name());
assertSame(projectReference, stateService.loadedProjectReference); assertSame(projectReference, stateService.loadedProjectReference);
assertSame(projectReference, setupService.loadedProjectReference); assertSame(projectReference, setupService.loadedProjectReference);
assertNotNull(session.lspServerHandle());
assertEquals(1, lspServerLifecycle.bootCalls); assertEquals(1, lspServerLifecycle.bootCalls);
assertEquals(projectReference.languageId(), lspServerLifecycle.lastRequest.project().languageId()); assertEquals(projectReference.languageId(), lspServerLifecycle.lastProjectContext.languageId());
assertEquals(projectReference.rootPath().toAbsolutePath().normalize(), lspServerLifecycle.lastRequest.project().projectRoot()); assertEquals(projectReference.rootPath().toAbsolutePath().normalize(), lspServerLifecycle.lastProjectContext.projectRoot());
} }
@Test @Test
@ -98,15 +92,12 @@ final class StudioProjectSessionFactoryTest {
private static final class RecordingLspServerLifecycle implements LspServerLifecycle { private static final class RecordingLspServerLifecycle implements LspServerLifecycle {
private int bootCalls; private int bootCalls;
private LspServerBootRequest lastRequest; private LspProjectContext lastProjectContext;
@Override @Override
public LspServerHandle bootServer(final LspServerBootRequest request) { public void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context) {
bootCalls++; bootCalls++;
lastRequest = request; lastProjectContext = context;
return new LspServerHandle(
request.project(),
new LspServerEndpoint("127.0.0.1", request.configuration().port()));
} }
@Override @Override
@ -116,7 +107,7 @@ final class StudioProjectSessionFactoryTest {
private static final class FailingLspServerLifecycle implements LspServerLifecycle { private static final class FailingLspServerLifecycle implements LspServerLifecycle {
@Override @Override
public LspServerHandle bootServer(final LspServerBootRequest request) { public void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context) {
throw new IllegalStateException("boom"); throw new IllegalStateException("boom");
} }

View File

@ -2,15 +2,13 @@ package p.studio.projectsessions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import p.studio.execution.StudioExecutionSessionService; import p.studio.execution.StudioExecutionSessionService;
import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerHandle;
import p.studio.lsp.api.LspServerLifecycle; import p.studio.lsp.api.LspServerLifecycle;
import p.studio.lsp.messages.LspServerBootRequest; import p.studio.lsp.messages.LspProjectContext;
import p.studio.lsp.messages.LspServerEndpoint; import p.studio.lsp.messages.LspServerConfiguration;
import p.studio.projects.ProjectReference;
import p.studio.projectstate.ProjectLocalStudioSetup; import p.studio.projectstate.ProjectLocalStudioSetup;
import p.studio.projectstate.ProjectLocalStudioState; import p.studio.projectstate.ProjectLocalStudioState;
import p.studio.projectstate.ProjectLocalStudioStateService; import p.studio.projectstate.ProjectLocalStudioStateService;
import p.studio.projects.ProjectReference;
import java.nio.file.Path; import java.nio.file.Path;
@ -27,7 +25,6 @@ final class StudioProjectSessionTest {
ProjectLocalStudioSetup.defaults(), ProjectLocalStudioSetup.defaults(),
new StudioExecutionSessionService(), new StudioExecutionSessionService(),
null, null,
null,
ProjectLocalStudioState.defaults()); ProjectLocalStudioState.defaults());
session.close(); session.close();
@ -46,7 +43,6 @@ final class StudioProjectSessionTest {
ProjectLocalStudioSetup.defaults(), ProjectLocalStudioSetup.defaults(),
new StudioExecutionSessionService(), new StudioExecutionSessionService(),
null, null,
null,
ProjectLocalStudioState.defaults()); ProjectLocalStudioState.defaults());
final ProjectLocalStudioState nextState = ProjectLocalStudioState.defaults() final ProjectLocalStudioState nextState = ProjectLocalStudioState.defaults()
.withOpenShellState(new ProjectLocalStudioState.OpenShellState("ASSETS")); .withOpenShellState(new ProjectLocalStudioState.OpenShellState("ASSETS"));
@ -61,23 +57,18 @@ final class StudioProjectSessionTest {
void closeShutsDownLspServerOnlyOnce() { void closeShutsDownLspServerOnlyOnce() {
final RecordingProjectLocalStudioStateService stateService = new RecordingProjectLocalStudioStateService(); final RecordingProjectLocalStudioStateService stateService = new RecordingProjectLocalStudioStateService();
final RecordingLspServerLifecycle lspServerLifecycle = new RecordingLspServerLifecycle(); final RecordingLspServerLifecycle lspServerLifecycle = new RecordingLspServerLifecycle();
final LspServerHandle lspServerHandle = new LspServerHandle(
new LspProjectContext("demo", "pbs", Path.of("/tmp/example")),
new LspServerEndpoint("127.0.0.1", 7777));
final StudioProjectSession session = new StudioProjectSession( final StudioProjectSession session = new StudioProjectSession(
projectReference(), projectReference(),
stateService, stateService,
ProjectLocalStudioSetup.defaults(), ProjectLocalStudioSetup.defaults(),
new StudioExecutionSessionService(), new StudioExecutionSessionService(),
lspServerLifecycle, lspServerLifecycle,
lspServerHandle,
ProjectLocalStudioState.defaults()); ProjectLocalStudioState.defaults());
session.close(); session.close();
session.close(); session.close();
assertEquals(1, lspServerLifecycle.shutdownCalls); assertEquals(1, lspServerLifecycle.shutdownCalls);
assertEquals(lspServerHandle.project(), lspServerLifecycle.lastProject);
} }
private ProjectReference projectReference() { private ProjectReference projectReference() {
@ -99,17 +90,15 @@ final class StudioProjectSessionTest {
private static final class RecordingLspServerLifecycle implements LspServerLifecycle { private static final class RecordingLspServerLifecycle implements LspServerLifecycle {
private int shutdownCalls; private int shutdownCalls;
private LspProjectContext lastProject;
@Override @Override
public LspServerHandle bootServer(final LspServerBootRequest request) { public void bootServer(LspServerConfiguration serverConfiguration, LspProjectContext context) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public void shutdownServer(final LspProjectContext project) { public void shutdownServer(final LspProjectContext project) {
shutdownCalls++; shutdownCalls++;
lastProject = project;
} }
} }
} }

File diff suppressed because it is too large Load Diff