implements PLN-0076
This commit is contained in:
parent
7f40541e96
commit
7cbe61f5eb
@ -5,7 +5,10 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":prometeu-lsp:prometeu-lsp-api"))
|
implementation(project(":prometeu-lsp:prometeu-lsp-api"))
|
||||||
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
implementation(project(":prometeu-compiler:prometeu-compiler-core"))
|
||||||
|
implementation(project(":prometeu-compiler:prometeu-frontend-api"))
|
||||||
implementation(project(":prometeu-compiler:prometeu-build-pipeline"))
|
implementation(project(":prometeu-compiler:prometeu-build-pipeline"))
|
||||||
|
implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs"))
|
||||||
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
|
implementation(project(":prometeu-compiler:prometeu-frontend-registry"))
|
||||||
|
implementation(project(":prometeu-compiler:prometeu-deps"))
|
||||||
implementation(libs.lsp4j)
|
implementation(libs.lsp4j)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record BaselineCompletion(
|
||||||
|
boolean incomplete,
|
||||||
|
List<BaselineCompletionItem> items) {
|
||||||
|
public BaselineCompletion {
|
||||||
|
items = List.copyOf(Objects.requireNonNull(items, "items"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record BaselineCompletionItem(
|
||||||
|
String label,
|
||||||
|
BaselineCompletionItemKind kind,
|
||||||
|
String detail,
|
||||||
|
String documentation) {
|
||||||
|
public BaselineCompletionItem {
|
||||||
|
label = requireText(label, "label");
|
||||||
|
kind = Objects.requireNonNull(kind, "kind");
|
||||||
|
detail = normalize(detail);
|
||||||
|
documentation = normalize(documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(
|
||||||
|
final String value,
|
||||||
|
final String field) {
|
||||||
|
final String candidate = normalize(value);
|
||||||
|
if (candidate.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(final String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
public enum BaselineCompletionItemKind {
|
||||||
|
KEYWORD,
|
||||||
|
VARIABLE,
|
||||||
|
FIELD,
|
||||||
|
FUNCTION,
|
||||||
|
METHOD,
|
||||||
|
CONSTRUCTOR,
|
||||||
|
STRUCT,
|
||||||
|
BUILTIN_TYPE,
|
||||||
|
SERVICE,
|
||||||
|
HOST,
|
||||||
|
CONTRACT,
|
||||||
|
CALLBACK,
|
||||||
|
ENUM,
|
||||||
|
ERROR,
|
||||||
|
GLOBAL,
|
||||||
|
CONST
|
||||||
|
}
|
||||||
@ -8,6 +8,8 @@ public record BaselineServerDescription(
|
|||||||
String version,
|
String version,
|
||||||
String frontendLanguageId,
|
String frontendLanguageId,
|
||||||
boolean hoverSupported,
|
boolean hoverSupported,
|
||||||
|
boolean completionSupported,
|
||||||
|
boolean signatureHelpSupported,
|
||||||
List<String> semanticTokenTypes,
|
List<String> semanticTokenTypes,
|
||||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||||
List<BaselineVisualTheme> visualThemes,
|
List<BaselineVisualTheme> visualThemes,
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record BaselineSignature(
|
||||||
|
String label,
|
||||||
|
List<String> parameters,
|
||||||
|
String documentation) {
|
||||||
|
public BaselineSignature {
|
||||||
|
label = requireText(label, "label");
|
||||||
|
parameters = List.copyOf(Objects.requireNonNull(parameters, "parameters"));
|
||||||
|
documentation = normalize(documentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(
|
||||||
|
final String value,
|
||||||
|
final String field) {
|
||||||
|
final String candidate = normalize(value);
|
||||||
|
if (candidate.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(final String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record BaselineSignatureHelp(
|
||||||
|
List<BaselineSignature> signatures,
|
||||||
|
int activeSignature,
|
||||||
|
int activeParameter) {
|
||||||
|
public BaselineSignatureHelp {
|
||||||
|
signatures = List.copyOf(Objects.requireNonNull(signatures, "signatures"));
|
||||||
|
if (!signatures.isEmpty() && (activeSignature < 0 || activeSignature >= signatures.size())) {
|
||||||
|
throw new IllegalArgumentException("activeSignature must reference one of the provided signatures");
|
||||||
|
}
|
||||||
|
if (activeParameter < 0) {
|
||||||
|
throw new IllegalArgumentException("activeParameter must be >= 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
package p.studio.lsp.services;
|
package p.studio.lsp.services;
|
||||||
|
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
import p.studio.lsp.messages.LspProjectContext;
|
import p.studio.lsp.messages.LspProjectContext;
|
||||||
|
|
||||||
@ -11,7 +13,11 @@ public interface LanguageServiceBridge {
|
|||||||
|
|
||||||
BaselineDocumentAnalysis analyzeDocument(LspProjectContext context, String documentUri, String text);
|
BaselineDocumentAnalysis analyzeDocument(LspProjectContext context, String documentUri, String text);
|
||||||
|
|
||||||
BaselineHover hover(LspProjectContext context, String documentUri, int line, int character);
|
BaselineCompletion completion(LspProjectContext context, String documentUri, String text, int line, int character);
|
||||||
|
|
||||||
|
BaselineHover hover(LspProjectContext context, String documentUri, String text, int line, int character);
|
||||||
|
|
||||||
|
BaselineSignatureHelp signatureHelp(LspProjectContext context, String documentUri, String text, int line, int character);
|
||||||
|
|
||||||
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,10 @@ package p.studio.lsp.services.compiler;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import p.studio.compiler.FrontendRegistryService;
|
import p.studio.compiler.FrontendRegistryService;
|
||||||
import p.studio.compiler.messages.BuilderPipelineConfig;
|
import p.studio.compiler.messages.BuilderPipelineConfig;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
import p.studio.compiler.messages.BuildingIssue;
|
import p.studio.compiler.messages.BuildingIssue;
|
||||||
|
import p.studio.compiler.messages.HostAdmissionContext;
|
||||||
import p.studio.compiler.models.AnalysisSnapshot;
|
import p.studio.compiler.models.AnalysisSnapshot;
|
||||||
import p.studio.compiler.models.BuilderPipelineContext;
|
import p.studio.compiler.models.BuilderPipelineContext;
|
||||||
import p.studio.compiler.models.FrontendEditorPaletteSpec;
|
import p.studio.compiler.models.FrontendEditorPaletteSpec;
|
||||||
@ -14,20 +17,33 @@ import p.studio.compiler.models.FrontendSemanticToken;
|
|||||||
import p.studio.compiler.models.FrontendTokenStyleSpec;
|
import p.studio.compiler.models.FrontendTokenStyleSpec;
|
||||||
import p.studio.compiler.models.FrontendVisualThemeSpec;
|
import p.studio.compiler.models.FrontendVisualThemeSpec;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.services.PBSFrontendPhaseService;
|
||||||
|
import p.studio.compiler.workspaces.AssetSurfaceContextLoader;
|
||||||
import p.studio.compiler.utilities.SourceProviderFactory;
|
import p.studio.compiler.utilities.SourceProviderFactory;
|
||||||
import p.studio.compiler.workspaces.BuilderPipelineService;
|
import p.studio.compiler.workspaces.BuilderPipelineService;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialCompletionCandidate;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialResolvedSymbol;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialSignature;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialSymbolKind;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService;
|
||||||
import p.studio.lsp.messages.*;
|
import p.studio.lsp.messages.*;
|
||||||
import p.studio.lsp.services.LanguageServiceBridge;
|
import p.studio.lsp.services.LanguageServiceBridge;
|
||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public final class CompilerLanguageServiceBridge implements LanguageServiceBridge {
|
public final class CompilerLanguageServiceBridge implements LanguageServiceBridge {
|
||||||
|
private final AssetSurfaceContextLoader assetSurfaceContextLoader = new AssetSurfaceContextLoader();
|
||||||
|
private final PbsEditorialSupportService editorialSupportService = new PbsEditorialSupportService();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineServerDescription describeServer(final LspProjectContext context) {
|
public BaselineServerDescription describeServer(final LspProjectContext context) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
@ -38,6 +54,8 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
"0.1.0",
|
"0.1.0",
|
||||||
context.languageId(),
|
context.languageId(),
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
presentation.semanticKeys(),
|
presentation.semanticKeys(),
|
||||||
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
||||||
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
||||||
@ -57,19 +75,76 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineHover hover(
|
public BaselineCompletion completion(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
final String documentUri,
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
final int line,
|
final int line,
|
||||||
final int character) {
|
final int character) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
return new BaselineHover("""
|
return editorialDocument(context, documentUri, text)
|
||||||
**Prometeu Studio LSP**
|
.map(document -> {
|
||||||
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
Compiler-backed baseline.
|
final List<PbsEditorialCompletionCandidate> candidates = editorialSupportService.completion(
|
||||||
|
document.text(),
|
||||||
Semantic enrichment will be layered later.
|
document.ast(),
|
||||||
""");
|
document.supplementalTopDecls(),
|
||||||
|
document.feSurfaceContext(),
|
||||||
|
offset);
|
||||||
|
return new BaselineCompletion(
|
||||||
|
false,
|
||||||
|
candidates.stream().map(this::mapCompletionItem).toList());
|
||||||
|
})
|
||||||
|
.orElseGet(() -> new BaselineCompletion(false, List.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaselineHover hover(
|
||||||
|
final LspProjectContext context,
|
||||||
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
|
Objects.requireNonNull(context, "context");
|
||||||
|
return editorialDocument(context, documentUri, text)
|
||||||
|
.flatMap(document -> {
|
||||||
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
|
return editorialSupportService.hover(
|
||||||
|
document.text(),
|
||||||
|
document.ast(),
|
||||||
|
document.supplementalTopDecls(),
|
||||||
|
document.feSurfaceContext(),
|
||||||
|
offset);
|
||||||
|
})
|
||||||
|
.map(symbol -> new BaselineHover(formatHoverMarkdown(symbol)))
|
||||||
|
.orElseGet(() -> new BaselineHover("No symbol information."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaselineSignatureHelp signatureHelp(
|
||||||
|
final LspProjectContext context,
|
||||||
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
|
Objects.requireNonNull(context, "context");
|
||||||
|
return editorialDocument(context, documentUri, text)
|
||||||
|
.flatMap(document -> {
|
||||||
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
|
return editorialSupportService.signatureHelp(
|
||||||
|
document.text(),
|
||||||
|
document.ast(),
|
||||||
|
document.supplementalTopDecls(),
|
||||||
|
document.feSurfaceContext(),
|
||||||
|
offset);
|
||||||
|
})
|
||||||
|
.map(signatureHelp -> new BaselineSignatureHelp(
|
||||||
|
signatureHelp.signatures().stream()
|
||||||
|
.map(signature -> new BaselineSignature(signature.label(), signature.parameterLabels(), ""))
|
||||||
|
.toList(),
|
||||||
|
signatureHelp.activeSignature(),
|
||||||
|
signatureHelp.activeParameter()))
|
||||||
|
.orElseGet(() -> new BaselineSignatureHelp(List.of(), 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -119,6 +194,71 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return BuilderPipelineService.INSTANCE.analyze(pipelineContext, LogAggregator.empty());
|
return BuilderPipelineService.INSTANCE.analyze(pipelineContext, LogAggregator.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<EditorialDocument> editorialDocument(
|
||||||
|
final LspProjectContext context,
|
||||||
|
final String documentUri,
|
||||||
|
final String text) {
|
||||||
|
if (!"pbs".equals(context.languageId())) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final Path documentPath = normalizeDocumentPath(documentUri);
|
||||||
|
final BuilderPipelineContext pipelineContext = BuilderPipelineContext.fromConfig(
|
||||||
|
new BuilderPipelineConfig(
|
||||||
|
false,
|
||||||
|
context.projectRoot().toString(),
|
||||||
|
"core-v1",
|
||||||
|
SourceProviderFactory.overlayUtf8(text == null ? Map.of() : Map.of(documentPath, text))));
|
||||||
|
BuilderPipelineService.INSTANCE.analyze(pipelineContext, LogAggregator.empty());
|
||||||
|
final FileId fileId = findFileId(pipelineContext, documentPath);
|
||||||
|
if (fileId == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final FrontendPhaseContext frontendPhaseContext = new FrontendPhaseContext(
|
||||||
|
pipelineContext.resolvedWorkspace.graph().projectTable(),
|
||||||
|
pipelineContext.fileTable,
|
||||||
|
pipelineContext.resolvedWorkspace.stack(),
|
||||||
|
pipelineContext.resolvedWorkspace.stdlib(),
|
||||||
|
HostAdmissionContext.permissiveDefault(),
|
||||||
|
assetSurfaceContextLoader.load(pipelineContext.resolvedWorkspace.mainProject().getRootPath()));
|
||||||
|
final var semanticReadSurface = PBSFrontendPhaseService.semanticReadSurface(
|
||||||
|
frontendPhaseContext,
|
||||||
|
p.studio.compiler.source.diagnostics.DiagnosticSink.empty(),
|
||||||
|
BuildingIssueSink.empty());
|
||||||
|
final var ast = semanticReadSurface.astByFile().get(fileId);
|
||||||
|
if (ast == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new EditorialDocument(
|
||||||
|
text == null ? readCurrentText(pipelineContext, fileId) : text,
|
||||||
|
ast,
|
||||||
|
semanticReadSurface.supplementalTopDeclsByFile().getOrDefault(fileId, ReadOnlyList.empty()),
|
||||||
|
frontendPhaseContext.feSurfaceContext()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileId findFileId(
|
||||||
|
final BuilderPipelineContext pipelineContext,
|
||||||
|
final Path documentPath) {
|
||||||
|
for (int fileIndex = 0; fileIndex < pipelineContext.fileTable.size(); fileIndex += 1) {
|
||||||
|
final FileId candidate = new FileId(fileIndex);
|
||||||
|
final var sourceHandle = pipelineContext.fileTable.get(candidate);
|
||||||
|
if (sourceHandle != null
|
||||||
|
&& sourceHandle.getCanonPath().toAbsolutePath().normalize().equals(documentPath)) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readCurrentText(
|
||||||
|
final BuilderPipelineContext pipelineContext,
|
||||||
|
final FileId fileId) {
|
||||||
|
final var sourceHandle = pipelineContext.fileTable.get(fileId);
|
||||||
|
if (sourceHandle == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return sourceHandle.readUtf8().orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
private List<BaselineDocumentIssue> mapDiagnostics(
|
private List<BaselineDocumentIssue> mapDiagnostics(
|
||||||
final AnalysisSnapshot snapshot,
|
final AnalysisSnapshot snapshot,
|
||||||
final Path documentPath,
|
final Path documentPath,
|
||||||
@ -163,6 +303,14 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return Path.of(URI.create(Objects.requireNonNull(documentUri, "documentUri"))).toAbsolutePath().normalize();
|
return Path.of(URI.create(Objects.requireNonNull(documentUri, "documentUri"))).toAbsolutePath().normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaselineCompletionItem mapCompletionItem(final PbsEditorialCompletionCandidate candidate) {
|
||||||
|
return new BaselineCompletionItem(
|
||||||
|
candidate.label(),
|
||||||
|
mapCompletionKind(candidate.kind()),
|
||||||
|
candidate.detail(),
|
||||||
|
candidate.origin());
|
||||||
|
}
|
||||||
|
|
||||||
private BaselineVisualTheme mapVisualTheme(final FrontendVisualThemeSpec theme) {
|
private BaselineVisualTheme mapVisualTheme(final FrontendVisualThemeSpec theme) {
|
||||||
return new BaselineVisualTheme(
|
return new BaselineVisualTheme(
|
||||||
theme.themeId(),
|
theme.themeId(),
|
||||||
@ -210,4 +358,65 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return FrontendRegistryService.getFrontendSpec(context.languageId())
|
return FrontendRegistryService.getFrontendSpec(context.languageId())
|
||||||
.orElseThrow(() -> new IllegalArgumentException("no frontend registered for languageId: " + context.languageId()));
|
.orElseThrow(() -> new IllegalArgumentException("no frontend registered for languageId: " + context.languageId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaselineCompletionItemKind mapCompletionKind(final PbsEditorialSymbolKind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case KEYWORD -> BaselineCompletionItemKind.KEYWORD;
|
||||||
|
case LOCAL, PARAMETER -> BaselineCompletionItemKind.VARIABLE;
|
||||||
|
case FIELD -> BaselineCompletionItemKind.FIELD;
|
||||||
|
case FUNCTION -> BaselineCompletionItemKind.FUNCTION;
|
||||||
|
case METHOD -> BaselineCompletionItemKind.METHOD;
|
||||||
|
case CONSTRUCTOR -> BaselineCompletionItemKind.CONSTRUCTOR;
|
||||||
|
case STRUCT -> BaselineCompletionItemKind.STRUCT;
|
||||||
|
case BUILTIN_TYPE -> BaselineCompletionItemKind.BUILTIN_TYPE;
|
||||||
|
case SERVICE -> BaselineCompletionItemKind.SERVICE;
|
||||||
|
case HOST -> BaselineCompletionItemKind.HOST;
|
||||||
|
case CONTRACT -> BaselineCompletionItemKind.CONTRACT;
|
||||||
|
case CALLBACK -> BaselineCompletionItemKind.CALLBACK;
|
||||||
|
case ENUM -> BaselineCompletionItemKind.ENUM;
|
||||||
|
case ERROR -> BaselineCompletionItemKind.ERROR;
|
||||||
|
case GLOBAL -> BaselineCompletionItemKind.GLOBAL;
|
||||||
|
case CONST -> BaselineCompletionItemKind.CONST;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatHoverMarkdown(final PbsEditorialResolvedSymbol symbol) {
|
||||||
|
final StringBuilder markdown = new StringBuilder();
|
||||||
|
final List<PbsEditorialSignature> signatures = symbol.signatures();
|
||||||
|
if (!signatures.isEmpty()) {
|
||||||
|
markdown.append("```pbs\n");
|
||||||
|
for (int index = 0; index < signatures.size(); index += 1) {
|
||||||
|
if (index > 0) {
|
||||||
|
markdown.append('\n');
|
||||||
|
}
|
||||||
|
markdown.append(signatures.get(index).label());
|
||||||
|
}
|
||||||
|
markdown.append("\n```");
|
||||||
|
} else if (!symbol.detail().isBlank()) {
|
||||||
|
markdown.append("```pbs\n").append(symbol.displayName());
|
||||||
|
if (!symbol.detail().isBlank()) {
|
||||||
|
markdown.append(" : ").append(symbol.detail());
|
||||||
|
}
|
||||||
|
markdown.append("\n```");
|
||||||
|
}
|
||||||
|
final String kindLabel = symbol.kind().name().toLowerCase(Locale.ROOT).replace('_', ' ');
|
||||||
|
if (markdown.length() > 0) {
|
||||||
|
markdown.append("\n\n");
|
||||||
|
}
|
||||||
|
markdown.append("**").append(kindLabel).append("**");
|
||||||
|
if (!symbol.origin().isBlank()) {
|
||||||
|
markdown.append(" from `").append(symbol.origin()).append('`');
|
||||||
|
}
|
||||||
|
if (!symbol.documentation().isBlank()) {
|
||||||
|
markdown.append("\n\n").append(symbol.documentation());
|
||||||
|
}
|
||||||
|
return markdown.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record EditorialDocument(
|
||||||
|
String text,
|
||||||
|
p.studio.compiler.pbs.ast.PbsAst.File ast,
|
||||||
|
ReadOnlyList<p.studio.compiler.pbs.ast.PbsAst.TopDecl> supplementalTopDecls,
|
||||||
|
p.studio.compiler.messages.FESurfaceContext feSurfaceContext) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,40 @@ final class DocumentPositionMapper {
|
|||||||
return new DocumentPosition(line, character);
|
return new DocumentPosition(line, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int byteOffsetOf(
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
|
if (line <= 0 && character <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int currentLine = 0;
|
||||||
|
int currentCharacter = 0;
|
||||||
|
int utf8Offset = 0;
|
||||||
|
for (int index = 0; index < text.length(); ) {
|
||||||
|
if (currentLine == line && currentCharacter >= character) {
|
||||||
|
return utf8Offset;
|
||||||
|
}
|
||||||
|
final int codePoint = text.codePointAt(index);
|
||||||
|
final int utf16Width = Character.charCount(codePoint);
|
||||||
|
final int utf8Width = utf8Length(codePoint);
|
||||||
|
if (currentLine == line && currentCharacter + utf16Width > character) {
|
||||||
|
return utf8Offset;
|
||||||
|
}
|
||||||
|
utf8Offset += utf8Width;
|
||||||
|
if (codePoint == '\n') {
|
||||||
|
if (currentLine == line) {
|
||||||
|
return utf8Offset - utf8Width;
|
||||||
|
}
|
||||||
|
currentLine += 1;
|
||||||
|
currentCharacter = 0;
|
||||||
|
} else {
|
||||||
|
currentCharacter += utf16Width;
|
||||||
|
}
|
||||||
|
index += utf16Width;
|
||||||
|
}
|
||||||
|
return utf8Offset;
|
||||||
|
}
|
||||||
|
|
||||||
private int utf8Length(final int codePoint) {
|
private int utf8Length(final int codePoint) {
|
||||||
if (codePoint <= 0x7F) {
|
if (codePoint <= 0x7F) {
|
||||||
return 1;
|
return 1;
|
||||||
@ -54,4 +88,4 @@ final class DocumentPositionMapper {
|
|||||||
}
|
}
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.lsp.services.protocol;
|
package p.studio.lsp.services.protocol;
|
||||||
|
|
||||||
import org.eclipse.lsp4j.*;
|
import org.eclipse.lsp4j.*;
|
||||||
|
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||||
import org.eclipse.lsp4j.services.LanguageClient;
|
import org.eclipse.lsp4j.services.LanguageClient;
|
||||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||||
import p.studio.lsp.messages.LspProjectContext;
|
import p.studio.lsp.messages.LspProjectContext;
|
||||||
@ -11,6 +12,7 @@ import java.util.Objects;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public final class PrometeuTextDocumentService implements TextDocumentService {
|
public final class PrometeuTextDocumentService implements TextDocumentService {
|
||||||
private final LspProjectContext project;
|
private final LspProjectContext project;
|
||||||
@ -70,15 +72,44 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
|
|||||||
languageServiceBridge.onSave(project, params.getTextDocument().getUri())));
|
languageServiceBridge.onSave(project, params.getTextDocument().getUri())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(final CompletionParams params) {
|
||||||
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
final String text = documentTextByUri.get(uri);
|
||||||
|
return CompletableFuture.completedFuture(Either.forRight(protocolMessageMapper.mapCompletion(
|
||||||
|
languageServiceBridge.completion(
|
||||||
|
project,
|
||||||
|
uri,
|
||||||
|
text,
|
||||||
|
params.getPosition().getLine(),
|
||||||
|
params.getPosition().getCharacter()))));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Hover> hover(final HoverParams params) {
|
public CompletableFuture<Hover> hover(final HoverParams params) {
|
||||||
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
final String text = documentTextByUri.get(uri);
|
||||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapHover(languageServiceBridge.hover(
|
return CompletableFuture.completedFuture(protocolMessageMapper.mapHover(languageServiceBridge.hover(
|
||||||
project,
|
project,
|
||||||
params.getTextDocument().getUri(),
|
uri,
|
||||||
|
text,
|
||||||
params.getPosition().getLine(),
|
params.getPosition().getLine(),
|
||||||
params.getPosition().getCharacter())));
|
params.getPosition().getCharacter())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<SignatureHelp> signatureHelp(final SignatureHelpParams params) {
|
||||||
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
final String text = documentTextByUri.get(uri);
|
||||||
|
return CompletableFuture.completedFuture(protocolMessageMapper.mapSignatureHelp(
|
||||||
|
languageServiceBridge.signatureHelp(
|
||||||
|
project,
|
||||||
|
uri,
|
||||||
|
text,
|
||||||
|
params.getPosition().getLine(),
|
||||||
|
params.getPosition().getCharacter())));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<SemanticTokens> semanticTokensFull(final SemanticTokensParams params) {
|
public CompletableFuture<SemanticTokens> semanticTokensFull(final SemanticTokensParams params) {
|
||||||
final String uri = params.getTextDocument().getUri();
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
|||||||
@ -24,6 +24,18 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
|
|
||||||
capabilities.setTextDocumentSync(syncOptions);
|
capabilities.setTextDocumentSync(syncOptions);
|
||||||
capabilities.setHoverProvider(description.hoverSupported());
|
capabilities.setHoverProvider(description.hoverSupported());
|
||||||
|
if (description.completionSupported()) {
|
||||||
|
final CompletionOptions completionOptions = new CompletionOptions();
|
||||||
|
completionOptions.setResolveProvider(false);
|
||||||
|
completionOptions.setTriggerCharacters(List.of("."));
|
||||||
|
capabilities.setCompletionProvider(completionOptions);
|
||||||
|
}
|
||||||
|
if (description.signatureHelpSupported()) {
|
||||||
|
final SignatureHelpOptions signatureHelpOptions = new SignatureHelpOptions();
|
||||||
|
signatureHelpOptions.setTriggerCharacters(List.of("(", ","));
|
||||||
|
signatureHelpOptions.setRetriggerCharacters(List.of(","));
|
||||||
|
capabilities.setSignatureHelpProvider(signatureHelpOptions);
|
||||||
|
}
|
||||||
final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions();
|
final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions();
|
||||||
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
||||||
semanticTokens.setFull(true);
|
semanticTokens.setFull(true);
|
||||||
@ -71,6 +83,38 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
|
final CompletionList list = new CompletionList();
|
||||||
|
list.setIsIncomplete(completion.incomplete());
|
||||||
|
list.setItems(completion.items().stream().map(this::mapCompletionItem).toList());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignatureHelp mapSignatureHelp(final BaselineSignatureHelp signatureHelp) {
|
||||||
|
final SignatureHelp result = new SignatureHelp();
|
||||||
|
result.setActiveSignature(signatureHelp.activeSignature());
|
||||||
|
result.setActiveParameter(signatureHelp.activeParameter());
|
||||||
|
result.setSignatures(signatureHelp.signatures().stream().map(signature -> {
|
||||||
|
final SignatureInformation information = new SignatureInformation();
|
||||||
|
information.setLabel(signature.label());
|
||||||
|
if (!signature.documentation().isBlank()) {
|
||||||
|
final MarkupContent content = new MarkupContent();
|
||||||
|
content.setKind(MarkupKind.MARKDOWN);
|
||||||
|
content.setValue(signature.documentation());
|
||||||
|
information.setDocumentation(content);
|
||||||
|
}
|
||||||
|
information.setParameters(signature.parameters().stream().map(parameter -> {
|
||||||
|
final ParameterInformation parameterInformation = new ParameterInformation();
|
||||||
|
parameterInformation.setLabel(parameter);
|
||||||
|
return parameterInformation;
|
||||||
|
}).toList());
|
||||||
|
return information;
|
||||||
|
}).toList());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SemanticTokens mapSemanticTokens(final BaselineSemanticTokens semanticTokens) {
|
public SemanticTokens mapSemanticTokens(final BaselineSemanticTokens semanticTokens) {
|
||||||
final List<BaselineSemanticToken> orderedTokens = new ArrayList<>(semanticTokens.tokens());
|
final List<BaselineSemanticToken> orderedTokens = new ArrayList<>(semanticTokens.tokens());
|
||||||
@ -127,6 +171,39 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
return diagnostic;
|
return diagnostic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CompletionItem mapCompletionItem(final BaselineCompletionItem item) {
|
||||||
|
final CompletionItem completionItem = new CompletionItem();
|
||||||
|
completionItem.setLabel(item.label());
|
||||||
|
completionItem.setKind(mapCompletionItemKind(item.kind()));
|
||||||
|
if (!item.detail().isBlank()) {
|
||||||
|
completionItem.setDetail(item.detail());
|
||||||
|
}
|
||||||
|
if (!item.documentation().isBlank()) {
|
||||||
|
final MarkupContent content = new MarkupContent();
|
||||||
|
content.setKind(MarkupKind.MARKDOWN);
|
||||||
|
content.setValue(item.documentation());
|
||||||
|
completionItem.setDocumentation(content);
|
||||||
|
}
|
||||||
|
return completionItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompletionItemKind mapCompletionItemKind(final BaselineCompletionItemKind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case KEYWORD -> CompletionItemKind.Keyword;
|
||||||
|
case VARIABLE -> CompletionItemKind.Variable;
|
||||||
|
case FIELD -> CompletionItemKind.Field;
|
||||||
|
case FUNCTION -> CompletionItemKind.Function;
|
||||||
|
case METHOD -> CompletionItemKind.Method;
|
||||||
|
case CONSTRUCTOR -> CompletionItemKind.Constructor;
|
||||||
|
case STRUCT, BUILTIN_TYPE -> CompletionItemKind.Struct;
|
||||||
|
case SERVICE, HOST, CONTRACT -> CompletionItemKind.Interface;
|
||||||
|
case CALLBACK -> CompletionItemKind.Function;
|
||||||
|
case ENUM -> CompletionItemKind.Enum;
|
||||||
|
case ERROR -> CompletionItemKind.EnumMember;
|
||||||
|
case GLOBAL, CONST -> CompletionItemKind.Constant;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private DiagnosticSeverity mapSeverity(final BaselineIssueSeverity severity) {
|
private DiagnosticSeverity mapSeverity(final BaselineIssueSeverity severity) {
|
||||||
return switch (severity) {
|
return switch (severity) {
|
||||||
|
|||||||
@ -4,11 +4,15 @@ import org.eclipse.lsp4j.Hover;
|
|||||||
import org.eclipse.lsp4j.InitializeResult;
|
import org.eclipse.lsp4j.InitializeResult;
|
||||||
import org.eclipse.lsp4j.MessageParams;
|
import org.eclipse.lsp4j.MessageParams;
|
||||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||||
|
import org.eclipse.lsp4j.SignatureHelp;
|
||||||
import org.eclipse.lsp4j.SemanticTokens;
|
import org.eclipse.lsp4j.SemanticTokens;
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
|
import org.eclipse.lsp4j.CompletionList;
|
||||||
|
|
||||||
public interface ProtocolMessageMapper {
|
public interface ProtocolMessageMapper {
|
||||||
InitializeResult mapInitializeResult(BaselineServerDescription description);
|
InitializeResult mapInitializeResult(BaselineServerDescription description);
|
||||||
@ -19,6 +23,10 @@ public interface ProtocolMessageMapper {
|
|||||||
|
|
||||||
Hover mapHover(BaselineHover hover);
|
Hover mapHover(BaselineHover hover);
|
||||||
|
|
||||||
|
CompletionList mapCompletion(BaselineCompletion completion);
|
||||||
|
|
||||||
|
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
||||||
|
|
||||||
SemanticTokens mapSemanticTokens(BaselineSemanticTokens semanticTokens);
|
SemanticTokens mapSemanticTokens(BaselineSemanticTokens semanticTokens);
|
||||||
|
|
||||||
MessageParams mapInfoMessage(String message);
|
MessageParams mapInfoMessage(String message);
|
||||||
|
|||||||
@ -98,6 +98,71 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertTrue(semanticTokens.tokens().stream().anyMatch(token -> token.semanticKey().equals("pbs-builtin-type")));
|
assertTrue(semanticTokens.tokens().stream().anyMatch(token -> token.semanticKey().equals("pbs-builtin-type")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void completionHoverAndSignatureHelpUseCompilerBackedEditorialResolution() {
|
||||||
|
final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize())
|
||||||
|
.resolve("test-projects")
|
||||||
|
.resolve("main")
|
||||||
|
.toAbsolutePath()
|
||||||
|
.normalize();
|
||||||
|
final Path documentPath = projectRoot.resolve("src").resolve("main.pbs");
|
||||||
|
final String overlay = """
|
||||||
|
declare struct Vec() {
|
||||||
|
fn blend(dx: int, dy: int) -> int { return dx; }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mix(left: int, right: int) -> int { return left; }
|
||||||
|
|
||||||
|
fn frame(vec: Vec) -> void {
|
||||||
|
mix(1, 2);
|
||||||
|
vec.blend(1, 2);
|
||||||
|
new Vec();
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
|
final LspProjectContext context = new LspProjectContext("main", "pbs", projectRoot);
|
||||||
|
final DocumentPositionMapper mapper = new DocumentPositionMapper(overlay);
|
||||||
|
final var completionPosition = mapper.positionOf(overlay.indexOf("mix(1, 2);"));
|
||||||
|
final var hoverPosition = mapper.positionOf(overlay.indexOf("blend(1, 2);"));
|
||||||
|
final var functionSignaturePosition = mapper.positionOf(overlay.indexOf("2);"));
|
||||||
|
final var constructorSignaturePosition = mapper.positionOf(overlay.indexOf("Vec();") + 4);
|
||||||
|
|
||||||
|
final var completion = bridge.completion(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
completionPosition.line(),
|
||||||
|
completionPosition.character());
|
||||||
|
assertTrue(completion.items().stream().anyMatch(item -> item.label().equals("mix")));
|
||||||
|
assertTrue(completion.items().stream().anyMatch(item -> item.label().equals("vec")));
|
||||||
|
|
||||||
|
final var hover = bridge.hover(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
hoverPosition.line(),
|
||||||
|
hoverPosition.character());
|
||||||
|
assertTrue(hover.markdown().contains("blend(arg0: int, arg1: int) -> int"));
|
||||||
|
assertTrue(hover.markdown().contains("**method**"));
|
||||||
|
|
||||||
|
final var functionSignatureHelp = bridge.signatureHelp(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
functionSignaturePosition.line(),
|
||||||
|
functionSignaturePosition.character());
|
||||||
|
assertEquals(1, functionSignatureHelp.activeParameter());
|
||||||
|
assertEquals("mix(left: int, right: int) -> int", functionSignatureHelp.signatures().getFirst().label());
|
||||||
|
|
||||||
|
final var constructorSignatureHelp = bridge.signatureHelp(
|
||||||
|
context,
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
overlay,
|
||||||
|
constructorSignaturePosition.line(),
|
||||||
|
constructorSignaturePosition.character());
|
||||||
|
assertEquals("Vec()", constructorSignatureHelp.signatures().getFirst().label());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void describeServerPublishesFrontendVisualThemes() {
|
void describeServerPublishesFrontendVisualThemes() {
|
||||||
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
@ -106,6 +171,8 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
|
|
||||||
assertEquals("pbs", description.frontendLanguageId());
|
assertEquals("pbs", description.frontendLanguageId());
|
||||||
assertEquals("pbs-default", description.activeVisualThemeId());
|
assertEquals("pbs-default", description.activeVisualThemeId());
|
||||||
|
assertTrue(description.completionSupported());
|
||||||
|
assertTrue(description.signatureHelpSupported());
|
||||||
assertEquals(1, description.semanticHostProjections().size());
|
assertEquals(1, description.semanticHostProjections().size());
|
||||||
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
||||||
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
||||||
|
|||||||
@ -6,16 +6,22 @@ import org.eclipse.lsp4j.InitializeResult;
|
|||||||
import org.eclipse.lsp4j.InitializedParams;
|
import org.eclipse.lsp4j.InitializedParams;
|
||||||
import org.eclipse.lsp4j.MessageParams;
|
import org.eclipse.lsp4j.MessageParams;
|
||||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||||
|
import org.eclipse.lsp4j.SignatureHelp;
|
||||||
import org.eclipse.lsp4j.SemanticTokens;
|
import org.eclipse.lsp4j.SemanticTokens;
|
||||||
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
|
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
|
||||||
import org.eclipse.lsp4j.services.LanguageClient;
|
import org.eclipse.lsp4j.services.LanguageClient;
|
||||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineEditorPalette;
|
import p.studio.lsp.messages.BaselineEditorPalette;
|
||||||
import p.studio.lsp.messages.BaselineHover;
|
import p.studio.lsp.messages.BaselineHover;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
||||||
|
import p.studio.lsp.messages.BaselineSignature;
|
||||||
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
import p.studio.lsp.messages.BaselineTokenStyle;
|
import p.studio.lsp.messages.BaselineTokenStyle;
|
||||||
@ -90,6 +96,8 @@ class PrometeuLanguageServerTest {
|
|||||||
"1",
|
"1",
|
||||||
"pbs",
|
"pbs",
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -118,10 +126,35 @@ class PrometeuLanguageServerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineHover hover(final LspProjectContext project, final String documentUri, final int line, final int character) {
|
public BaselineCompletion completion(
|
||||||
|
final LspProjectContext project,
|
||||||
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
|
return new BaselineCompletion(false, List.of(new BaselineCompletionItem("demo", BaselineCompletionItemKind.FUNCTION, "", "")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaselineHover hover(
|
||||||
|
final LspProjectContext project,
|
||||||
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
return new BaselineHover("hover");
|
return new BaselineHover("hover");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaselineSignatureHelp signatureHelp(
|
||||||
|
final LspProjectContext project,
|
||||||
|
final String documentUri,
|
||||||
|
final String text,
|
||||||
|
final int line,
|
||||||
|
final int character) {
|
||||||
|
return new BaselineSignatureHelp(List.of(new BaselineSignature("demo()", List.of(), "")), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineSemanticTokens semanticTokens(final LspProjectContext context, final String documentUri, final String text) {
|
public BaselineSemanticTokens semanticTokens(final LspProjectContext context, final String documentUri, final String text) {
|
||||||
return new BaselineSemanticTokens(List.of(), List.of());
|
return new BaselineSemanticTokens(List.of(), List.of());
|
||||||
@ -158,6 +191,16 @@ class PrometeuLanguageServerTest {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.lsp4j.CompletionList mapCompletion(final BaselineCompletion completion) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SignatureHelp mapSignatureHelp(final BaselineSignatureHelp signatureHelp) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SemanticTokens mapSemanticTokens(final BaselineSemanticTokens semanticTokens) {
|
public SemanticTokens mapSemanticTokens(final BaselineSemanticTokens semanticTokens) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
package p.studio.lsp.services.protocol.mapping;
|
package p.studio.lsp.services.protocol.mapping;
|
||||||
|
|
||||||
import org.eclipse.lsp4j.InitializeResult;
|
import org.eclipse.lsp4j.InitializeResult;
|
||||||
|
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
import p.studio.lsp.messages.BaselineEditorPalette;
|
import p.studio.lsp.messages.BaselineEditorPalette;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
import p.studio.lsp.messages.BaselineSemanticHostProjection;
|
||||||
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
import p.studio.lsp.messages.BaselineSemanticHostProjectionEntry;
|
||||||
|
import p.studio.lsp.messages.BaselineSignature;
|
||||||
|
import p.studio.lsp.messages.BaselineSignatureHelp;
|
||||||
import p.studio.lsp.messages.BaselineServerDescription;
|
import p.studio.lsp.messages.BaselineServerDescription;
|
||||||
import p.studio.lsp.messages.BaselineTokenStyle;
|
import p.studio.lsp.messages.BaselineTokenStyle;
|
||||||
import p.studio.lsp.messages.BaselineVisualTheme;
|
import p.studio.lsp.messages.BaselineVisualTheme;
|
||||||
@ -14,6 +20,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
final class Lsp4jProtocolMessageMapperTest {
|
final class Lsp4jProtocolMessageMapperTest {
|
||||||
|
|
||||||
@ -25,6 +32,8 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
"1",
|
"1",
|
||||||
"pbs",
|
"pbs",
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
List.of("demo-keyword"),
|
List.of("demo-keyword"),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -43,6 +52,9 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
|
|
||||||
final InitializeResult result = mapper.mapInitializeResult(description);
|
final InitializeResult result = mapper.mapInitializeResult(description);
|
||||||
|
|
||||||
|
assertEquals(List.of("."), result.getCapabilities().getCompletionProvider().getTriggerCharacters());
|
||||||
|
assertEquals(List.of("(", ","), result.getCapabilities().getSignatureHelpProvider().getTriggerCharacters());
|
||||||
|
|
||||||
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
||||||
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
||||||
assertEquals("pbs", semanticPayload.get("frontendLanguageId"));
|
assertEquals("pbs", semanticPayload.get("frontendLanguageId"));
|
||||||
@ -59,4 +71,33 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
assertEquals(List.of("declaration"), firstTokenProjection.get("hostTokenModifiers"));
|
assertEquals(List.of("declaration"), firstTokenProjection.get("hostTokenModifiers"));
|
||||||
assertEquals("variable", firstTokenProjection.get("fallbackTokenType"));
|
assertEquals("variable", firstTokenProjection.get("fallbackTokenType"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void completionAndSignatureHelpMapToLspPayloads() {
|
||||||
|
final var mapper = new Lsp4jProtocolMessageMapper();
|
||||||
|
|
||||||
|
final var completion = mapper.mapCompletion(new BaselineCompletion(
|
||||||
|
false,
|
||||||
|
List.of(new BaselineCompletionItem(
|
||||||
|
"blend",
|
||||||
|
BaselineCompletionItemKind.METHOD,
|
||||||
|
"blend(dx: int, dy: int) -> int",
|
||||||
|
"method docs"))));
|
||||||
|
assertEquals(1, completion.getItems().size());
|
||||||
|
assertEquals("blend", completion.getItems().getFirst().getLabel());
|
||||||
|
assertEquals("blend(dx: int, dy: int) -> int", completion.getItems().getFirst().getDetail());
|
||||||
|
final var documentation = assertInstanceOf(Either.class, completion.getItems().getFirst().getDocumentation());
|
||||||
|
assertTrue(documentation.isRight());
|
||||||
|
|
||||||
|
final var signatureHelp = mapper.mapSignatureHelp(new BaselineSignatureHelp(
|
||||||
|
List.of(new BaselineSignature(
|
||||||
|
"mix(left: int, right: int) -> int",
|
||||||
|
List.of("left: int", "right: int"),
|
||||||
|
"")),
|
||||||
|
0,
|
||||||
|
1));
|
||||||
|
assertEquals(1, signatureHelp.getSignatures().size());
|
||||||
|
assertEquals("mix(left: int, right: int) -> int", signatureHelp.getSignatures().getFirst().getLabel());
|
||||||
|
assertEquals(1, signatureHelp.getActiveParameter());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user