implements PLN-0067

This commit is contained in:
bQUARKz 2026-05-05 15:57:27 +01:00
parent 18dc64e36c
commit f6774ef568
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
18 changed files with 597 additions and 103 deletions

View File

@ -1,5 +1,5 @@
{"type":"meta","next_id":{"DSC":33,"AGD":36,"DEC":33,"PLN":68,"LSN":47,"CLSN":1}}
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"studio-new-lsp-api-and-v1-boundary","title":"Novo boundary entre lsp-api, lsp-v1 e a extensao VS Code","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","lsp","vscode","protocol","api","boundary"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0065","file":"PLN-0065-lsp-boundary-and-module-scaffolding.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0066","file":"PLN-0066-project-scoped-lsp-server-lifecycle-in-studio.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0067","file":"PLN-0067-compiler-backed-dumb-lsp-server-baseline.md","status":"open","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"studio-new-lsp-api-and-v1-boundary","title":"Novo boundary entre lsp-api, lsp-v1 e a extensao VS Code","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","lsp","vscode","protocol","api","boundary"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0065","file":"PLN-0065-lsp-boundary-and-module-scaffolding.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0066","file":"PLN-0066-project-scoped-lsp-server-lifecycle-in-studio.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0067","file":"PLN-0067-compiler-backed-dumb-lsp-server-baseline.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0030","status":"done","ticket":"studio-scene-pack-runtime-binary-contract","title":"Studio scene pack contract for runtime SCENE binary payload","created_at":"2026-04-24","updated_at":"2026-05-01","tags":["studio","packer","runtime","scene","asset-pack","binary-format","tiled"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0046","file":"discussion/lessons/DSC-0030-studio-scene-pack-runtime-binary-contract/LSN-0046-canonical-scene-owns-editorial-truth-while-pack-stays-request-driven.md","status":"done","created_at":"2026-05-01","updated_at":"2026-05-01"}]}
{"type":"discussion","id":"DSC-0029","status":"done","ticket":"studio-frame-composer-syscall-and-sprite-alignment","title":"Studio Alignment with Runtime FrameComposer Syscalls and Sprite Composition","created_at":"2026-04-18","updated_at":"2026-04-18","tags":["studio","compiler","pbs","stdlib","runtime-alignment","abi","syscall","frame-composer","sprites"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0029-studio-frame-composer-syscall-and-sprite-alignment/LSN-0041-composer-must-own-public-sprite-composition.md","status":"done","created_at":"2026-04-18","updated_at":"2026-04-18"}]}

View File

@ -2,9 +2,9 @@
id: PLN-0067
ticket: studio-new-lsp-api-and-v1-boundary
title: Compiler-Backed Dumb LSP Server Baseline
status: open
status: done
created: 2026-05-05
completed:
completed: 2026-05-05
tags: [studio, lsp, compiler, baseline, protocol, vscode]
---
@ -81,11 +81,11 @@ Establish a maintainable, compiler-backed baseline inside `lsp-v1` that keeps th
## Acceptance Criteria
- [ ] `lsp-v1` talks to `compiler` through explicit bridge services.
- [ ] The baseline server behavior remains intentionally simple but structurally clean.
- [ ] At least one minimal end-to-end capability slice proves the final layering.
- [ ] The extension can still connect over TCP without protocol regressions.
- [ ] Temporary semantic limits are explicit in tests and code structure.
- [x] `lsp-v1` talks to `compiler` through explicit bridge services.
- [x] The baseline server behavior remains intentionally simple but structurally clean.
- [x] At least one minimal end-to-end capability slice proves the final layering.
- [x] The extension can still connect over TCP without protocol regressions.
- [x] Temporary semantic limits are explicit in tests and code structure.
## Dependencies

View File

@ -0,0 +1,12 @@
package p.studio.lsp.v1.compiler;
import java.util.List;
import java.util.Objects;
public record BaselineDocumentAnalysis(
List<BaselineDocumentIssue> issues) {
public BaselineDocumentAnalysis {
issues = List.copyOf(Objects.requireNonNull(issues, "issues"));
}
}

View File

@ -0,0 +1,29 @@
package p.studio.lsp.v1.compiler;
import java.util.Objects;
public record BaselineDocumentIssue(
int startLine,
int startCharacter,
int endLine,
int endCharacter,
BaselineIssueSeverity severity,
String source,
String message) {
public BaselineDocumentIssue {
severity = Objects.requireNonNull(severity, "severity");
source = requireText(source, "source");
message = requireText(message, "message");
}
private static String requireText(
final String value,
final String field) {
final String candidate = Objects.requireNonNull(value, field).trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException(field + " must not be blank");
}
return candidate;
}
}

View File

@ -0,0 +1,15 @@
package p.studio.lsp.v1.compiler;
import java.util.Objects;
public record BaselineHover(
String markdown) {
public BaselineHover {
final String candidate = Objects.requireNonNull(markdown, "markdown").trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException("markdown must not be blank");
}
markdown = candidate;
}
}

View File

@ -0,0 +1,7 @@
package p.studio.lsp.v1.compiler;
public enum BaselineIssueSeverity {
INFORMATION,
WARNING,
ERROR
}

View File

@ -0,0 +1,24 @@
package p.studio.lsp.v1.compiler;
import java.util.Objects;
public record BaselineServerDescription(
String name,
String version,
boolean hoverSupported) {
public BaselineServerDescription {
name = requireText(name, "name");
version = requireText(version, "version");
}
private static String requireText(
final String value,
final String field) {
final String candidate = Objects.requireNonNull(value, field).trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException(field + " must not be blank");
}
return candidate;
}
}

View File

@ -1,10 +1,11 @@
package p.studio.lsp.v1.compiler;
import p.studio.compiler.workspaces.BuilderPipelineService;
import p.studio.lsp.api.LspProjectContext;
import java.util.Objects;
public final class CompilerLanguageServiceBridge {
public final class CompilerLanguageServiceBridge implements LanguageServiceBridge {
private final BuilderPipelineService builderPipelineService;
public CompilerLanguageServiceBridge() {
@ -18,4 +19,63 @@ public final class CompilerLanguageServiceBridge {
public BuilderPipelineService builderPipelineService() {
return builderPipelineService;
}
@Override
public BaselineServerDescription describeServer(final LspProjectContext project) {
Objects.requireNonNull(project, "project");
return new BaselineServerDescription(
"Prometeu Studio LSP",
"0.1.0",
true);
}
@Override
public BaselineDocumentAnalysis analyzeDocument(
final LspProjectContext project,
final String documentUri,
final String text) {
Objects.requireNonNull(project, "project");
// Wave 1 keeps responses intentionally synthetic while hardening the bridge boundary.
final String message = "Compiler-backed baseline via " + builderPipelineService.getClass().getSimpleName();
return new BaselineDocumentAnalysis(java.util.List.of(new BaselineDocumentIssue(
0,
0,
0,
Math.max(1, firstLineLength(text)),
BaselineIssueSeverity.INFORMATION,
"Prometeu Studio",
message)));
}
@Override
public BaselineHover hover(
final LspProjectContext project,
final String documentUri,
final int line,
final int character) {
Objects.requireNonNull(project, "project");
return new BaselineHover("""
**Prometeu Studio LSP**
Compiler-backed baseline.
Semantic enrichment will be layered later.
""");
}
@Override
public String onSave(
final LspProjectContext project,
final String documentUri) {
Objects.requireNonNull(project, "project");
return "Prometeu Studio saw save: " + documentUri;
}
private int firstLineLength(final String text) {
if (text == null || text.isEmpty()) {
return 1;
}
final int newline = text.indexOf('\n');
return newline < 0 ? text.length() : newline;
}
}

View File

@ -0,0 +1,13 @@
package p.studio.lsp.v1.compiler;
import p.studio.lsp.api.LspProjectContext;
public interface LanguageServiceBridge {
BaselineServerDescription describeServer(LspProjectContext project);
BaselineDocumentAnalysis analyzeDocument(LspProjectContext project, String documentUri, String text);
BaselineHover hover(LspProjectContext project, String documentUri, int line, int character);
String onSave(LspProjectContext project, String documentUri);
}

View File

@ -8,16 +8,17 @@ import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import p.studio.lsp.api.LspProjectContext;
import p.studio.lsp.v1.compiler.CompilerLanguageServiceBridge;
import p.studio.lsp.v1.protocol.mapping.ProtocolServerProfile;
import p.studio.lsp.v1.protocol.mapping.ServerCapabilitiesMapper;
import p.studio.lsp.v1.compiler.LanguageServiceBridge;
import p.studio.lsp.v1.protocol.mapping.Lsp4jProtocolMessageMapper;
import p.studio.lsp.v1.protocol.mapping.ProtocolMessageMapper;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public final class PrometeuLanguageServer implements LanguageServer, LanguageClientAware {
private final LspProjectContext project;
private final CompilerLanguageServiceBridge compilerBridge;
private final ServerCapabilitiesMapper capabilitiesMapper;
private final LanguageServiceBridge languageServiceBridge;
private final ProtocolMessageMapper protocolMessageMapper;
private final TextDocumentService textDocumentService;
private final WorkspaceService workspaceService;
private volatile LanguageClient client;
@ -28,21 +29,23 @@ public final class PrometeuLanguageServer implements LanguageServer, LanguageCli
final CompilerLanguageServiceBridge compilerBridge) {
this(project,
compilerBridge,
new ServerCapabilitiesMapper(),
new PrometeuTextDocumentService(),
new Lsp4jProtocolMessageMapper(),
null,
new PrometeuWorkspaceService());
}
PrometeuLanguageServer(
final LspProjectContext project,
final CompilerLanguageServiceBridge compilerBridge,
final ServerCapabilitiesMapper capabilitiesMapper,
final LanguageServiceBridge languageServiceBridge,
final ProtocolMessageMapper protocolMessageMapper,
final TextDocumentService textDocumentService,
final WorkspaceService workspaceService) {
this.project = Objects.requireNonNull(project, "project");
this.compilerBridge = Objects.requireNonNull(compilerBridge, "compilerBridge");
this.capabilitiesMapper = Objects.requireNonNull(capabilitiesMapper, "capabilitiesMapper");
this.textDocumentService = Objects.requireNonNull(textDocumentService, "textDocumentService");
this.languageServiceBridge = Objects.requireNonNull(languageServiceBridge, "languageServiceBridge");
this.protocolMessageMapper = Objects.requireNonNull(protocolMessageMapper, "protocolMessageMapper");
this.textDocumentService = textDocumentService != null
? textDocumentService
: new PrometeuTextDocumentService(project, languageServiceBridge, protocolMessageMapper);
this.workspaceService = Objects.requireNonNull(workspaceService, "workspaceService");
}
@ -56,8 +59,8 @@ public final class PrometeuLanguageServer implements LanguageServer, LanguageCli
@Override
public CompletableFuture<InitializeResult> initialize(final InitializeParams params) {
final ProtocolServerProfile profile = ProtocolServerProfile.defaults();
return CompletableFuture.completedFuture(capabilitiesMapper.map(profile));
return CompletableFuture.completedFuture(protocolMessageMapper.mapInitializeResult(
languageServiceBridge.describeServer(project)));
}
@Override
@ -66,8 +69,7 @@ public final class PrometeuLanguageServer implements LanguageServer, LanguageCli
if (currentClient == null) {
return;
}
currentClient.logMessage(new MessageParams(
MessageType.Info,
currentClient.logMessage(protocolMessageMapper.mapInfoMessage(
"Prometeu Studio LSP connected for project " + project.projectKey()));
}
@ -82,8 +84,7 @@ public final class PrometeuLanguageServer implements LanguageServer, LanguageCli
if (!shutdownRequested) {
final LanguageClient currentClient = client;
if (currentClient != null) {
currentClient.logMessage(new MessageParams(
MessageType.Warning,
currentClient.logMessage(new MessageParams(MessageType.Warning,
"Prometeu Studio LSP received exit before shutdown"));
}
}

View File

@ -3,20 +3,35 @@ package p.studio.lsp.v1.protocol;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.TextDocumentService;
import p.studio.lsp.api.LspProjectContext;
import p.studio.lsp.v1.compiler.LanguageServiceBridge;
import p.studio.lsp.v1.protocol.mapping.ProtocolMessageMapper;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public final class PrometeuTextDocumentService implements TextDocumentService {
private final LspProjectContext project;
private final LanguageServiceBridge languageServiceBridge;
private final ProtocolMessageMapper protocolMessageMapper;
private volatile LanguageClient client;
public PrometeuTextDocumentService(
final LspProjectContext project,
final LanguageServiceBridge languageServiceBridge,
final ProtocolMessageMapper protocolMessageMapper) {
this.project = Objects.requireNonNull(project, "project");
this.languageServiceBridge = Objects.requireNonNull(languageServiceBridge, "languageServiceBridge");
this.protocolMessageMapper = Objects.requireNonNull(protocolMessageMapper, "protocolMessageMapper");
}
void connect(final LanguageClient client) {
this.client = client;
}
@Override
public void didOpen(final DidOpenTextDocumentParams params) {
publishHelloDiagnostic(params.getTextDocument().getUri(), params.getTextDocument().getText());
publishAnalysis(params.getTextDocument().getUri(), params.getTextDocument().getText());
}
@Override
@ -24,7 +39,7 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
final String text = params.getContentChanges().isEmpty()
? ""
: params.getContentChanges().getFirst().getText();
publishHelloDiagnostic(params.getTextDocument().getUri(), text);
publishAnalysis(params.getTextDocument().getUri(), text);
}
@Override
@ -33,7 +48,7 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
if (currentClient == null) {
return;
}
currentClient.publishDiagnostics(new PublishDiagnosticsParams(params.getTextDocument().getUri(), List.of()));
currentClient.publishDiagnostics(protocolMessageMapper.emptyDiagnostics(params.getTextDocument().getUri()));
}
@Override
@ -42,52 +57,28 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
if (currentClient == null) {
return;
}
currentClient.logMessage(new MessageParams(
MessageType.Info,
"Prometeu Studio saw save: " + params.getTextDocument().getUri()));
currentClient.logMessage(protocolMessageMapper.mapInfoMessage(
languageServiceBridge.onSave(project, params.getTextDocument().getUri())));
}
@Override
public CompletableFuture<Hover> hover(final HoverParams params) {
final MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.MARKDOWN);
content.setValue("""
**Prometeu Studio LSP**
Structured baseline server.
Semantic enrichment will be layered later.
""");
final Hover hover = new Hover();
hover.setContents(content);
return CompletableFuture.completedFuture(hover);
return CompletableFuture.completedFuture(protocolMessageMapper.mapHover(languageServiceBridge.hover(
project,
params.getTextDocument().getUri(),
params.getPosition().getLine(),
params.getPosition().getCharacter())));
}
private void publishHelloDiagnostic(
private void publishAnalysis(
final String uri,
final String text) {
final LanguageClient currentClient = client;
if (currentClient == null) {
return;
}
final Diagnostic diagnostic = new Diagnostic();
diagnostic.setRange(new Range(
new Position(0, 0),
new Position(0, Math.max(1, firstLineLength(text)))));
diagnostic.setSeverity(DiagnosticSeverity.Information);
diagnostic.setSource("Prometeu Studio");
diagnostic.setMessage("Structured baseline LSP response");
currentClient.publishDiagnostics(new PublishDiagnosticsParams(uri, List.of(diagnostic)));
}
private int firstLineLength(final String text) {
if (text == null || text.isEmpty()) {
return 1;
}
final int newline = text.indexOf('\n');
return newline < 0 ? text.length() : newline;
currentClient.publishDiagnostics(protocolMessageMapper.mapDiagnostics(
uri,
languageServiceBridge.analyzeDocument(project, uri, text)));
}
}

View File

@ -0,0 +1,78 @@
package p.studio.lsp.v1.protocol.mapping;
import org.eclipse.lsp4j.*;
import p.studio.lsp.v1.compiler.*;
import java.util.List;
public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
@Override
public InitializeResult mapInitializeResult(final BaselineServerDescription description) {
final ServerCapabilities capabilities = new ServerCapabilities();
final TextDocumentSyncOptions syncOptions = new TextDocumentSyncOptions();
syncOptions.setOpenClose(true);
syncOptions.setChange(TextDocumentSyncKind.Full);
final SaveOptions saveOptions = new SaveOptions();
saveOptions.setIncludeText(true);
syncOptions.setSave(saveOptions);
capabilities.setTextDocumentSync(syncOptions);
capabilities.setHoverProvider(description.hoverSupported());
final InitializeResult result = new InitializeResult(capabilities);
final ServerInfo serverInfo = new ServerInfo();
serverInfo.setName(description.name());
serverInfo.setVersion(description.version());
result.setServerInfo(serverInfo);
return result;
}
@Override
public PublishDiagnosticsParams mapDiagnostics(
final String uri,
final BaselineDocumentAnalysis analysis) {
return new PublishDiagnosticsParams(uri, analysis.issues().stream().map(this::mapDiagnostic).toList());
}
@Override
public PublishDiagnosticsParams emptyDiagnostics(final String uri) {
return new PublishDiagnosticsParams(uri, List.of());
}
@Override
public Hover mapHover(final BaselineHover hover) {
final MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.MARKDOWN);
content.setValue(hover.markdown());
final Hover result = new Hover();
result.setContents(content);
return result;
}
@Override
public MessageParams mapInfoMessage(final String message) {
return new MessageParams(MessageType.Info, message);
}
private Diagnostic mapDiagnostic(final BaselineDocumentIssue issue) {
final Diagnostic diagnostic = new Diagnostic();
diagnostic.setRange(new Range(
new Position(issue.startLine(), issue.startCharacter()),
new Position(issue.endLine(), issue.endCharacter())));
diagnostic.setSeverity(mapSeverity(issue.severity()));
diagnostic.setSource(issue.source());
diagnostic.setMessage(issue.message());
return diagnostic;
}
private DiagnosticSeverity mapSeverity(final BaselineIssueSeverity severity) {
return switch (severity) {
case INFORMATION -> DiagnosticSeverity.Information;
case WARNING -> DiagnosticSeverity.Warning;
case ERROR -> DiagnosticSeverity.Error;
};
}
}

View File

@ -0,0 +1,21 @@
package p.studio.lsp.v1.protocol.mapping;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import p.studio.lsp.v1.compiler.BaselineDocumentAnalysis;
import p.studio.lsp.v1.compiler.BaselineHover;
import p.studio.lsp.v1.compiler.BaselineServerDescription;
public interface ProtocolMessageMapper {
InitializeResult mapInitializeResult(BaselineServerDescription description);
PublishDiagnosticsParams mapDiagnostics(String uri, BaselineDocumentAnalysis analysis);
PublishDiagnosticsParams emptyDiagnostics(String uri);
Hover mapHover(BaselineHover hover);
MessageParams mapInfoMessage(String message);
}

View File

@ -1,11 +0,0 @@
package p.studio.lsp.v1.protocol.mapping;
public record ProtocolServerProfile(
String name,
String version,
boolean hoverSupported) {
public static ProtocolServerProfile defaults() {
return new ProtocolServerProfile("Prometeu Studio LSP", "0.1.0", true);
}
}

View File

@ -1,27 +0,0 @@
package p.studio.lsp.v1.protocol.mapping;
import org.eclipse.lsp4j.*;
public final class ServerCapabilitiesMapper {
public InitializeResult map(final ProtocolServerProfile profile) {
final ServerCapabilities capabilities = new ServerCapabilities();
final TextDocumentSyncOptions syncOptions = new TextDocumentSyncOptions();
syncOptions.setOpenClose(true);
syncOptions.setChange(TextDocumentSyncKind.Full);
final SaveOptions saveOptions = new SaveOptions();
saveOptions.setIncludeText(true);
syncOptions.setSave(saveOptions);
capabilities.setTextDocumentSync(syncOptions);
capabilities.setHoverProvider(profile.hoverSupported());
final InitializeResult result = new InitializeResult(capabilities);
final ServerInfo serverInfo = new ServerInfo();
serverInfo.setName(profile.name());
serverInfo.setVersion(profile.version());
result.setServerInfo(serverInfo);
return result;
}
}

View File

@ -0,0 +1,29 @@
package p.studio.lsp.v1.compiler;
import org.junit.jupiter.api.Test;
import p.studio.lsp.api.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

@ -0,0 +1,116 @@
package p.studio.lsp.v1.protocol;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.junit.jupiter.api.Test;
import p.studio.lsp.api.LspProjectContext;
import p.studio.lsp.v1.compiler.*;
import p.studio.lsp.v1.protocol.mapping.ProtocolMessageMapper;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
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 InitializeResult result = server.initialize(new InitializeParams()).join();
assertSame(expected, result);
assertEquals(1, bridge.describeServerCalls);
assertSame(bridge.serverDescription, mapper.lastServerDescription);
}
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();
}
}
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

@ -0,0 +1,136 @@
package p.studio.lsp.v1.protocol;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4j.services.LanguageClient;
import org.junit.jupiter.api.Test;
import p.studio.lsp.api.LspProjectContext;
import p.studio.lsp.v1.compiler.*;
import p.studio.lsp.v1.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;
}
}
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;
}
}
}