dev/prometeu-lsp #8
@ -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"}]}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package p.studio.lsp.v1.compiler;
|
||||
|
||||
public enum BaselineIssueSeverity {
|
||||
INFORMATION,
|
||||
WARNING,
|
||||
ERROR
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user