dev/pbs-lsp-remaining-editor-surface #31
@ -288,6 +288,8 @@ Compiler diagnostics and editor diagnostics are distinct ownership surfaces. Dia
|
|||||||
|
|
||||||
When tooling publishes an editor diagnostic, `source` MUST be the `languageId` of the bound frontend project. `code` MUST be the stable compiler diagnostic code when one exists, and MUST be empty when the compiler diagnostic has none. Tooling MUST NOT copy that code into `source`. Tooling MUST NOT publish related locations, compiler phase, or a repair payload on the editor diagnostic.
|
When tooling publishes an editor diagnostic, `source` MUST be the `languageId` of the bound frontend project. `code` MUST be the stable compiler diagnostic code when one exists, and MUST be empty when the compiler diagnostic has none. Tooling MUST NOT copy that code into `source`. Tooling MUST NOT publish related locations, compiler phase, or a repair payload on the editor diagnostic.
|
||||||
|
|
||||||
|
When a frontend exposes document links, each link MUST cover the span of a module reference whose resolved destination is a compiler-known regular file. A module reference that does not resolve to a regular file MUST produce no link. Tooling MUST NOT target virtual, untitled, or synthetic URIs, including virtual stdlib documents. Document links MUST NOT replace definition. A missing document-link capability MUST NOT be advertised and MUST produce an empty list rather than a protocol error.
|
||||||
|
|
||||||
`FrontendSpec` remains the source of static frontend-owned presentation metadata such as semantic vocabularies, host projections, and visual themes. Producing semantic tokens for a live document is an optional editor-facing capability; the existence of static presentation metadata MUST NOT imply that every frontend can provide live semantic-token results.
|
`FrontendSpec` remains the source of static frontend-owned presentation metadata such as semantic vocabularies, host projections, and visual themes. Producing semantic tokens for a live document is an optional editor-facing capability; the existence of static presentation metadata MUST NOT imply that every frontend can provide live semantic-token results.
|
||||||
|
|
||||||
The frontend registry MUST resolve providers by `languageId`. A lookup for an unknown `languageId` MUST fail explicitly with a diagnostic-friendly error. Unknown languages MUST NOT silently fall back to PBS or to any other frontend.
|
The frontend registry MUST resolve providers by `languageId`. A lookup for an unknown `languageId` MUST fail explicitly with a diagnostic-friendly error. Unknown languages MUST NOT silently fall back to PBS or to any other frontend.
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import p.studio.compiler.pbs.semantics.PbsQuickFixCollector;
|
|||||||
import p.studio.compiler.services.PBSFrontendPhaseService.PbsSemanticReadSurface;
|
import p.studio.compiler.services.PBSFrontendPhaseService.PbsSemanticReadSurface;
|
||||||
import p.studio.compiler.services.FrontendCodeAction;
|
import p.studio.compiler.services.FrontendCodeAction;
|
||||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentLink;
|
||||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
import p.studio.compiler.services.FrontendDefinitionLocation;
|
||||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
import p.studio.compiler.services.FrontendDocumentSymbol;
|
import p.studio.compiler.services.FrontendDocumentSymbol;
|
||||||
@ -40,6 +41,7 @@ import p.studio.compiler.source.Span;
|
|||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.source.tables.FileTableReader;
|
import p.studio.compiler.source.tables.FileTableReader;
|
||||||
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -401,6 +403,46 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
return FrontendRenameResult.applied(List.copyOf(edits.values()));
|
return FrontendRenameResult.applied(List.copyOf(edits.values()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean documentLinksSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FrontendDocumentLink> documentLinks(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext) {
|
||||||
|
if (request == null
|
||||||
|
|| editorialContext == null
|
||||||
|
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
||||||
|
|| !(editorialContext.projectSurface() instanceof PbsSemanticReadSurface surface)
|
||||||
|
|| surface.physicalModuleFileByReference() == null
|
||||||
|
|| ast.imports() == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
final ArrayList<FrontendDocumentLink> links = new ArrayList<>();
|
||||||
|
for (final PbsAst.ImportDecl importDecl : ast.imports()) {
|
||||||
|
if (importDecl == null || importDecl.moduleRef() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final PbsAst.ModuleRef moduleRef = importDecl.moduleRef();
|
||||||
|
final Span span = moduleRef.span();
|
||||||
|
if (span == null || span.isNone() || moduleRef.project() == null || moduleRef.project().isBlank()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Path target = surface.physicalModuleFileByReference().get(
|
||||||
|
new ModuleReference(moduleRef.project(), moduleRef.pathSegments()));
|
||||||
|
if (target == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
links.add(new FrontendDocumentLink(
|
||||||
|
target,
|
||||||
|
toOffset(span.getStart()),
|
||||||
|
toOffset(span.getEnd())));
|
||||||
|
}
|
||||||
|
return List.copyOf(links);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean codeActionsSupported() {
|
public boolean codeActionsSupported() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -15,9 +15,12 @@ import p.studio.compiler.pbs.stdlib.StdlibEnvironmentResolver;
|
|||||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.source.identifiers.ModuleId;
|
import p.studio.compiler.source.identifiers.ModuleId;
|
||||||
|
import p.studio.compiler.source.tables.ModuleReference;
|
||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -153,14 +156,46 @@ public class PBSFrontendPhaseService implements FrontendPhaseService {
|
|||||||
Map.copyOf(astByFile),
|
Map.copyOf(astByFile),
|
||||||
Map.copyOf(supplementalTopDeclsByFile),
|
Map.copyOf(supplementalTopDeclsByFile),
|
||||||
Map.copyOf(inlineHintsByFile),
|
Map.copyOf(inlineHintsByFile),
|
||||||
Map.copyOf(sourceKindByFile));
|
Map.copyOf(sourceKindByFile),
|
||||||
|
physicalModuleFiles(ctx, assembly));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<ModuleReference, Path> physicalModuleFiles(
|
||||||
|
final FrontendPhaseContext ctx,
|
||||||
|
final PbsModuleAssembly assembly) {
|
||||||
|
final Map<ModuleReference, Path> files = new LinkedHashMap<>();
|
||||||
|
if (ctx == null || ctx.fileTable == null || assembly == null || assembly.parsedSourceFiles() == null) {
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
for (final PbsParsedSourceFile parsed : assembly.parsedSourceFiles()) {
|
||||||
|
if (parsed.fileId() == null || parsed.fileId().isNone()
|
||||||
|
|| parsed.moduleId() == null || parsed.moduleId().isNone()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final var handle = ctx.fileTable.get(parsed.fileId());
|
||||||
|
if (handle == null || handle.getCanonPath() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Path path = handle.getCanonPath().toAbsolutePath().normalize();
|
||||||
|
if (!Files.isRegularFile(path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final ModuleReference reference = assembly.moduleTable().get(parsed.moduleId());
|
||||||
|
if (reference == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
files.merge(reference, path, (left, right) ->
|
||||||
|
left.toString().compareTo(right.toString()) <= 0 ? left : right);
|
||||||
|
}
|
||||||
|
return Map.copyOf(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record PbsSemanticReadSurface(
|
public record PbsSemanticReadSurface(
|
||||||
Map<FileId, p.studio.compiler.pbs.ast.PbsAst.File> astByFile,
|
Map<FileId, p.studio.compiler.pbs.ast.PbsAst.File> astByFile,
|
||||||
Map<FileId, ReadOnlyList<p.studio.compiler.pbs.ast.PbsAst.TopDecl>> supplementalTopDeclsByFile,
|
Map<FileId, ReadOnlyList<p.studio.compiler.pbs.ast.PbsAst.TopDecl>> supplementalTopDeclsByFile,
|
||||||
Map<FileId, ReadOnlyList<PbsInlineHintSurface>> inlineHintsByFile,
|
Map<FileId, ReadOnlyList<PbsInlineHintSurface>> inlineHintsByFile,
|
||||||
Map<FileId, SourceKind> sourceKindByFile) {
|
Map<FileId, SourceKind> sourceKindByFile,
|
||||||
|
Map<ModuleReference, Path> physicalModuleFileByReference) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRBackend mergeCompiledSources(
|
private IRBackend mergeCompiledSources(
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
package p.studio.compiler;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import p.studio.AppMode;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
|
import p.studio.compiler.models.BuildStack;
|
||||||
|
import p.studio.compiler.models.ProjectDescriptor;
|
||||||
|
import p.studio.compiler.models.SourceHandle;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
import p.studio.compiler.source.tables.FileTable;
|
||||||
|
import p.studio.compiler.source.tables.ProjectTable;
|
||||||
|
import p.studio.compiler.utilities.SourceProviderFactory;
|
||||||
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class PBSFrontendDocumentLinkTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void documentLinksUseTheAssembledPhysicalModuleFile() throws IOException {
|
||||||
|
final Path projectRoot = tempDir.resolve("app");
|
||||||
|
final Path sourceRoot = projectRoot.resolve("src");
|
||||||
|
final Path sourceA = sourceRoot.resolve("a").resolve("source.pbs");
|
||||||
|
final Path sourceB = sourceRoot.resolve("b").resolve("source.pbs");
|
||||||
|
Files.createDirectories(sourceA.getParent());
|
||||||
|
Files.createDirectories(sourceB.getParent());
|
||||||
|
Files.writeString(sourceA, """
|
||||||
|
fn target() -> int {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
Files.writeString(sourceA.getParent().resolve("mod.barrel"), "pub fn target() -> int;\n");
|
||||||
|
final String caller = """
|
||||||
|
import { target } from @app:a;
|
||||||
|
import { Log } from @sdk:log;
|
||||||
|
import { Missing } from @app:missing;
|
||||||
|
|
||||||
|
fn caller() -> int {
|
||||||
|
return target();
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(sourceB, caller);
|
||||||
|
Files.writeString(sourceB.getParent().resolve("mod.barrel"), "pub fn caller() -> int;\n");
|
||||||
|
|
||||||
|
final var projectTable = new ProjectTable();
|
||||||
|
final var fileTable = new FileTable(1);
|
||||||
|
final var projectId = projectTable.register(ProjectDescriptor.builder()
|
||||||
|
.rootPath(projectRoot)
|
||||||
|
.name("app")
|
||||||
|
.version("1.0.0")
|
||||||
|
.sourceRoots(ReadOnlyList.wrap(List.of(sourceRoot)))
|
||||||
|
.build());
|
||||||
|
register(projectId, projectRoot, sourceA, fileTable);
|
||||||
|
register(projectId, projectRoot, sourceA.getParent().resolve("mod.barrel"), fileTable);
|
||||||
|
final FileId callerId = register(projectId, projectRoot, sourceB, fileTable);
|
||||||
|
register(projectId, projectRoot, sourceB.getParent().resolve("mod.barrel"), fileTable);
|
||||||
|
|
||||||
|
final var phaseContext = new FrontendPhaseContext(
|
||||||
|
projectTable,
|
||||||
|
fileTable,
|
||||||
|
new BuildStack(ReadOnlyList.wrap(List.of(projectId))),
|
||||||
|
AppMode.Game);
|
||||||
|
final var service = new PBSFrontendLanguageService();
|
||||||
|
final var editorialContext = service.prepareEditorialContext(phaseContext, callerId).orElseThrow();
|
||||||
|
final var links = service.documentLinks(
|
||||||
|
new FrontendDocumentRequest(projectRoot, sourceB, caller),
|
||||||
|
editorialContext);
|
||||||
|
|
||||||
|
assertEquals(1, links.size());
|
||||||
|
assertEquals(sourceA.toAbsolutePath().normalize(), links.getFirst().path());
|
||||||
|
final byte[] bytes = caller.getBytes(StandardCharsets.UTF_8);
|
||||||
|
assertEquals("@app:a", new String(
|
||||||
|
bytes,
|
||||||
|
links.getFirst().startOffset(),
|
||||||
|
links.getFirst().endOffset() - links.getFirst().startOffset(),
|
||||||
|
StandardCharsets.UTF_8));
|
||||||
|
assertTrue(service.documentLinksSupported());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileId register(
|
||||||
|
final p.studio.compiler.source.identifiers.ProjectId projectId,
|
||||||
|
final Path projectRoot,
|
||||||
|
final Path file,
|
||||||
|
final FileTable fileTable) throws IOException {
|
||||||
|
final BasicFileAttributes attributes = Files.readAttributes(file, BasicFileAttributes.class);
|
||||||
|
return fileTable.register(new SourceHandle(
|
||||||
|
projectId,
|
||||||
|
projectRoot.relativize(file),
|
||||||
|
file,
|
||||||
|
attributes.size(),
|
||||||
|
attributes.lastModifiedTime().toMillis(),
|
||||||
|
SourceProviderFactory.filesystem()));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendDocumentLink(
|
||||||
|
Path path,
|
||||||
|
int startOffset,
|
||||||
|
int endOffset) {
|
||||||
|
|
||||||
|
public FrontendDocumentLink {
|
||||||
|
path = Objects.requireNonNull(path, "path").toAbsolutePath().normalize();
|
||||||
|
if (startOffset < 0) {
|
||||||
|
throw new IllegalArgumentException("startOffset must not be negative");
|
||||||
|
}
|
||||||
|
if (endOffset < startOffset) {
|
||||||
|
throw new IllegalArgumentException("endOffset must not be before startOffset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -139,6 +139,20 @@ public interface FrontendLanguageService {
|
|||||||
return rename(request, offset, newName);
|
return rename(request, offset, newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean documentLinksSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<FrontendDocumentLink> documentLinks(final FrontendDocumentRequest request) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<FrontendDocumentLink> documentLinks(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext) {
|
||||||
|
return documentLinks(request);
|
||||||
|
}
|
||||||
|
|
||||||
default boolean codeActionsSupported() {
|
default boolean codeActionsSupported() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,9 @@ class FrontendLanguageServiceTest {
|
|||||||
assertTrue(service.prepareRename(request, 0).isEmpty());
|
assertTrue(service.prepareRename(request, 0).isEmpty());
|
||||||
assertEquals(FrontendRenameResult.Status.UNSUPPORTED, service.rename(request, 0, "renamed").status());
|
assertEquals(FrontendRenameResult.Status.UNSUPPORTED, service.rename(request, 0, "renamed").status());
|
||||||
assertTrue(service.rename(request, 0, "renamed").edits().isEmpty());
|
assertTrue(service.rename(request, 0, "renamed").edits().isEmpty());
|
||||||
|
assertFalse(service.documentLinksSupported());
|
||||||
|
assertTrue(service.documentLinks(request).isEmpty());
|
||||||
|
assertTrue(service.documentLinks(request, null).isEmpty());
|
||||||
assertFalse(service.codeActionsSupported());
|
assertFalse(service.codeActionsSupported());
|
||||||
assertTrue(service.codeActions(request).isEmpty());
|
assertTrue(service.codeActions(request).isEmpty());
|
||||||
assertTrue(service.codeActions(request, null).isEmpty());
|
assertTrue(service.codeActions(request, null).isEmpty());
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record BaselineDocumentLink(
|
||||||
|
String targetUri,
|
||||||
|
int startLine,
|
||||||
|
int startCharacter,
|
||||||
|
int endLine,
|
||||||
|
int endCharacter) {
|
||||||
|
|
||||||
|
public BaselineDocumentLink {
|
||||||
|
targetUri = requireText(targetUri, "targetUri");
|
||||||
|
if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0) {
|
||||||
|
throw new IllegalArgumentException("document link range coordinates must not be negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,13 @@
|
|||||||
|
package p.studio.lsp.messages;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record BaselineDocumentLinks(List<BaselineDocumentLink> links) {
|
||||||
|
public BaselineDocumentLinks {
|
||||||
|
links = links == null ? List.of() : List.copyOf(links);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BaselineDocumentLinks empty() {
|
||||||
|
return new BaselineDocumentLinks(List.of());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ public record BaselineServerDescription(
|
|||||||
boolean workspaceSymbolsSupported,
|
boolean workspaceSymbolsSupported,
|
||||||
boolean renameSupported,
|
boolean renameSupported,
|
||||||
boolean codeActionsSupported,
|
boolean codeActionsSupported,
|
||||||
|
boolean documentLinksSupported,
|
||||||
List<String> semanticTokenTypes,
|
List<String> semanticTokenTypes,
|
||||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||||
List<BaselineVisualTheme> visualThemes,
|
List<BaselineVisualTheme> visualThemes,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import p.studio.lsp.messages.BaselineReferences;
|
|||||||
import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
||||||
import p.studio.lsp.messages.BaselineCodeActionDiagnostic;
|
import p.studio.lsp.messages.BaselineCodeActionDiagnostic;
|
||||||
import p.studio.lsp.messages.BaselineCodeActions;
|
import p.studio.lsp.messages.BaselineCodeActions;
|
||||||
|
import p.studio.lsp.messages.BaselineDocumentLinks;
|
||||||
import p.studio.lsp.messages.BaselinePrepareRename;
|
import p.studio.lsp.messages.BaselinePrepareRename;
|
||||||
import p.studio.lsp.messages.BaselineRename;
|
import p.studio.lsp.messages.BaselineRename;
|
||||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||||
@ -60,6 +61,10 @@ public interface LanguageServiceBridge {
|
|||||||
|
|
||||||
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
BaselineSemanticTokens semanticTokens(LspProjectContext context, String documentUri, String text);
|
||||||
|
|
||||||
|
default BaselineDocumentLinks documentLinks(LspProjectContext context, String documentUri, String text) {
|
||||||
|
return BaselineDocumentLinks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
BaselineCodeActions codeActions(
|
BaselineCodeActions codeActions(
|
||||||
LspProjectContext context,
|
LspProjectContext context,
|
||||||
String documentUri,
|
String documentUri,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import p.studio.compiler.messages.*;
|
|||||||
import p.studio.compiler.models.*;
|
import p.studio.compiler.models.*;
|
||||||
import p.studio.compiler.services.FrontendCodeAction;
|
import p.studio.compiler.services.FrontendCodeAction;
|
||||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentLink;
|
||||||
import p.studio.compiler.services.FrontendLanguageService;
|
import p.studio.compiler.services.FrontendLanguageService;
|
||||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
import p.studio.compiler.services.FrontendDefinitionLocation;
|
||||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
@ -57,6 +58,9 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final boolean codeActionsSupported = languageService
|
final boolean codeActionsSupported = languageService
|
||||||
.map(FrontendLanguageService::codeActionsSupported)
|
.map(FrontendLanguageService::codeActionsSupported)
|
||||||
.orElse(false);
|
.orElse(false);
|
||||||
|
final boolean documentLinksSupported = languageService
|
||||||
|
.map(FrontendLanguageService::documentLinksSupported)
|
||||||
|
.orElse(false);
|
||||||
return new BaselineServerDescription(
|
return new BaselineServerDescription(
|
||||||
"Prometeu Studio LSP",
|
"Prometeu Studio LSP",
|
||||||
"0.1.0",
|
"0.1.0",
|
||||||
@ -70,6 +74,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
languageServicePresent,
|
languageServicePresent,
|
||||||
languageServicePresent,
|
languageServicePresent,
|
||||||
codeActionsSupported,
|
codeActionsSupported,
|
||||||
|
documentLinksSupported,
|
||||||
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(),
|
||||||
@ -365,6 +370,29 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
.orElseGet(() -> BaselineRename.refused("not renameable"));
|
.orElseGet(() -> BaselineRename.refused("not renameable"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaselineDocumentLinks documentLinks(
|
||||||
|
final LspProjectContext context,
|
||||||
|
final String documentUri,
|
||||||
|
final String text) {
|
||||||
|
Objects.requireNonNull(context, "context");
|
||||||
|
final var languageService = frontendProvider(context).languageService();
|
||||||
|
if (languageService.isEmpty() || !languageService.orElseThrow().documentLinksSupported()) {
|
||||||
|
return BaselineDocumentLinks.empty();
|
||||||
|
}
|
||||||
|
return editorialDocument(context, documentUri, text)
|
||||||
|
.map(document -> {
|
||||||
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
|
context.projectRoot(),
|
||||||
|
normalizeDocumentPath(documentUri),
|
||||||
|
document.text());
|
||||||
|
return new BaselineDocumentLinks(mapDocumentLinks(
|
||||||
|
languageService.orElseThrow().documentLinks(request, document.editorialContext()),
|
||||||
|
document.text()));
|
||||||
|
})
|
||||||
|
.orElseGet(BaselineDocumentLinks::empty);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BaselineCodeActions codeActions(
|
public BaselineCodeActions codeActions(
|
||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
@ -830,6 +858,28 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return List.copyOf(mapped);
|
return List.copyOf(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<BaselineDocumentLink> mapDocumentLinks(
|
||||||
|
final List<FrontendDocumentLink> links,
|
||||||
|
final String currentDocumentText) {
|
||||||
|
if (links == null || links.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
final DocumentPositionMapper mapper = new DocumentPositionMapper(
|
||||||
|
currentDocumentText == null ? "" : currentDocumentText);
|
||||||
|
final ArrayList<BaselineDocumentLink> mapped = new ArrayList<>();
|
||||||
|
for (final FrontendDocumentLink link : links) {
|
||||||
|
final DocumentPosition start = mapper.positionOf(link.startOffset());
|
||||||
|
final DocumentPosition end = mapper.positionOf(link.endOffset());
|
||||||
|
mapped.add(new BaselineDocumentLink(
|
||||||
|
link.path().toUri().toString(),
|
||||||
|
start.line(),
|
||||||
|
start.character(),
|
||||||
|
end.line(),
|
||||||
|
end.character()));
|
||||||
|
}
|
||||||
|
return List.copyOf(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
private List<BaselineDefinitionLocation> mapDefinitionLocations(
|
private List<BaselineDefinitionLocation> mapDefinitionLocations(
|
||||||
final List<FrontendDefinitionLocation> locations,
|
final List<FrontendDefinitionLocation> locations,
|
||||||
final Path currentDocumentPath,
|
final Path currentDocumentPath,
|
||||||
|
|||||||
@ -168,6 +168,14 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
|
|||||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapRename(result));
|
return CompletableFuture.completedFuture(protocolMessageMapper.mapRename(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletableFuture<List<DocumentLink>> documentLink(final DocumentLinkParams params) {
|
||||||
|
final String uri = params.getTextDocument().getUri();
|
||||||
|
final String text = documentTextByUri.get(uri);
|
||||||
|
return CompletableFuture.completedFuture(protocolMessageMapper.mapDocumentLinks(
|
||||||
|
languageServiceBridge.documentLinks(project, uri, text)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(final CodeActionParams params) {
|
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(final CodeActionParams params) {
|
||||||
if (!quickFixRequested(params.getContext())) {
|
if (!quickFixRequested(params.getContext())) {
|
||||||
|
|||||||
@ -53,6 +53,11 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
codeActionOptions.setCodeActionKinds(List.of(CodeActionKind.QuickFix));
|
codeActionOptions.setCodeActionKinds(List.of(CodeActionKind.QuickFix));
|
||||||
capabilities.setCodeActionProvider(codeActionOptions);
|
capabilities.setCodeActionProvider(codeActionOptions);
|
||||||
}
|
}
|
||||||
|
if (description.documentLinksSupported()) {
|
||||||
|
final DocumentLinkOptions documentLinkOptions = new DocumentLinkOptions();
|
||||||
|
documentLinkOptions.setResolveProvider(false);
|
||||||
|
capabilities.setDocumentLinkProvider(documentLinkOptions);
|
||||||
|
}
|
||||||
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);
|
||||||
@ -159,6 +164,23 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
|||||||
return edit;
|
return edit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DocumentLink> mapDocumentLinks(final BaselineDocumentLinks links) {
|
||||||
|
if (links == null || links.links().isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
final ArrayList<DocumentLink> mapped = new ArrayList<>();
|
||||||
|
for (final BaselineDocumentLink link : links.links()) {
|
||||||
|
final DocumentLink documentLink = new DocumentLink();
|
||||||
|
documentLink.setRange(new Range(
|
||||||
|
new Position(link.startLine(), link.startCharacter()),
|
||||||
|
new Position(link.endLine(), link.endCharacter())));
|
||||||
|
documentLink.setTarget(link.targetUri());
|
||||||
|
mapped.add(documentLink);
|
||||||
|
}
|
||||||
|
return List.copyOf(mapped);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Either<Command, CodeAction>> mapCodeActions(
|
public List<Either<Command, CodeAction>> mapCodeActions(
|
||||||
final BaselineCodeActions actions,
|
final BaselineCodeActions actions,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
|||||||
import org.eclipse.lsp4j.SignatureHelp;
|
import org.eclipse.lsp4j.SignatureHelp;
|
||||||
import org.eclipse.lsp4j.SemanticTokens;
|
import org.eclipse.lsp4j.SemanticTokens;
|
||||||
import org.eclipse.lsp4j.Diagnostic;
|
import org.eclipse.lsp4j.Diagnostic;
|
||||||
|
import org.eclipse.lsp4j.DocumentLink;
|
||||||
import org.eclipse.lsp4j.DocumentSymbol;
|
import org.eclipse.lsp4j.DocumentSymbol;
|
||||||
import org.eclipse.lsp4j.Location;
|
import org.eclipse.lsp4j.Location;
|
||||||
import org.eclipse.lsp4j.Range;
|
import org.eclipse.lsp4j.Range;
|
||||||
@ -19,6 +20,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either3;
|
|||||||
import org.eclipse.lsp4j.PrepareRenameDefaultBehavior;
|
import org.eclipse.lsp4j.PrepareRenameDefaultBehavior;
|
||||||
import org.eclipse.lsp4j.PrepareRenameResult;
|
import org.eclipse.lsp4j.PrepareRenameResult;
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
|
import p.studio.lsp.messages.BaselineDocumentLinks;
|
||||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
||||||
import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
||||||
import p.studio.lsp.messages.BaselineCodeActions;
|
import p.studio.lsp.messages.BaselineCodeActions;
|
||||||
@ -59,6 +61,10 @@ public interface ProtocolMessageMapper {
|
|||||||
|
|
||||||
List<Either<Command, CodeAction>> mapCodeActions(BaselineCodeActions actions, List<Diagnostic> contextDiagnostics);
|
List<Either<Command, CodeAction>> mapCodeActions(BaselineCodeActions actions, List<Diagnostic> contextDiagnostics);
|
||||||
|
|
||||||
|
default List<DocumentLink> mapDocumentLinks(BaselineDocumentLinks links) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
CompletionList mapCompletion(BaselineCompletion completion);
|
CompletionList mapCompletion(BaselineCompletion completion);
|
||||||
|
|
||||||
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package p.studio.lsp.services.compiler;
|
package p.studio.lsp.services.compiler;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import p.studio.compiler.messages.BuildingIssueSink;
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
import p.studio.compiler.messages.FrontendPhaseContext;
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
import p.studio.compiler.models.FrontendSpec;
|
import p.studio.compiler.models.FrontendSpec;
|
||||||
@ -14,6 +15,7 @@ import p.studio.lsp.messages.LspProjectContext;
|
|||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
import p.studio.utilities.structures.ReadOnlySet;
|
import p.studio.utilities.structures.ReadOnlySet;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -74,6 +76,7 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertTrue(bridge.codeActions(context, documentUri, "fn main() {}", List.of(
|
assertTrue(bridge.codeActions(context, documentUri, "fn main() {}", List.of(
|
||||||
new BaselineCodeActionDiagnostic("E_SEM_DUPLICATE_RESERVED_ATTRIBUTE", 0, 0, 0, 1)))
|
new BaselineCodeActionDiagnostic("E_SEM_DUPLICATE_RESERVED_ATTRIBUTE", 0, 0, 0, 1)))
|
||||||
.actions().isEmpty());
|
.actions().isEmpty());
|
||||||
|
assertTrue(bridge.documentLinks(context, documentUri, "fn main() {}").links().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -768,6 +771,7 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
assertTrue(description.workspaceSymbolsSupported());
|
assertTrue(description.workspaceSymbolsSupported());
|
||||||
assertTrue(description.renameSupported());
|
assertTrue(description.renameSupported());
|
||||||
assertTrue(description.codeActionsSupported());
|
assertTrue(description.codeActionsSupported());
|
||||||
|
assertTrue(description.documentLinksSupported());
|
||||||
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()
|
||||||
@ -781,6 +785,77 @@ class CompilerLanguageServiceBridgeTest {
|
|||||||
&& tokenStyle.foreground().equals("#7fb8ff")));
|
&& tokenStyle.foreground().equals("#7fb8ff")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void documentLinksSkipVirtualStdlibImports() throws IOException {
|
||||||
|
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 text = Files.readString(documentPath);
|
||||||
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
|
|
||||||
|
final var links = bridge.documentLinks(
|
||||||
|
new LspProjectContext("main", "pbs", projectRoot),
|
||||||
|
documentPath.toUri().toString(),
|
||||||
|
text);
|
||||||
|
|
||||||
|
assertTrue(links.links().isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void documentLinksPointAtThePhysicalModuleFile(@TempDir final Path tempDir) throws IOException {
|
||||||
|
final Path projectRoot = tempDir.resolve("app");
|
||||||
|
final Path sourceA = projectRoot.resolve("src").resolve("a").resolve("source.pbs");
|
||||||
|
final Path sourceB = projectRoot.resolve("src").resolve("b").resolve("source.pbs");
|
||||||
|
Files.createDirectories(sourceA.getParent());
|
||||||
|
Files.createDirectories(sourceB.getParent());
|
||||||
|
Files.writeString(projectRoot.resolve("prometeu.json"), """
|
||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "pbs",
|
||||||
|
"stdlib": "1",
|
||||||
|
"target": "Game",
|
||||||
|
"dependencies": []
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
Files.writeString(sourceA, """
|
||||||
|
fn target() -> int {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
Files.writeString(sourceA.getParent().resolve("mod.barrel"), "pub fn target() -> int;\n");
|
||||||
|
final String caller = """
|
||||||
|
import { target } from @app:a;
|
||||||
|
import { Log } from @sdk:log;
|
||||||
|
import { Missing } from @app:missing;
|
||||||
|
|
||||||
|
fn caller() -> int {
|
||||||
|
return target();
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(sourceB, caller);
|
||||||
|
Files.writeString(sourceB.getParent().resolve("mod.barrel"), "pub fn caller() -> int;\n");
|
||||||
|
final Path realRoot = projectRoot.toRealPath();
|
||||||
|
final Path realA = sourceA.toRealPath();
|
||||||
|
final Path realB = sourceB.toRealPath();
|
||||||
|
final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge();
|
||||||
|
|
||||||
|
final var links = bridge.documentLinks(
|
||||||
|
new LspProjectContext("app", "pbs", realRoot),
|
||||||
|
realB.toUri().toString(),
|
||||||
|
caller);
|
||||||
|
|
||||||
|
assertEquals(1, links.links().size());
|
||||||
|
final var link = links.links().getFirst();
|
||||||
|
assertEquals(realA.toUri().toString(), link.targetUri());
|
||||||
|
final int start = new DocumentPositionMapper(caller).byteOffsetOf(link.startLine(), link.startCharacter());
|
||||||
|
final int end = new DocumentPositionMapper(caller).byteOffsetOf(link.endLine(), link.endCharacter());
|
||||||
|
assertEquals("@app:a", new String(caller.getBytes(StandardCharsets.UTF_8), start, end - start, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
private Path findRepoRoot(final Path start) {
|
private Path findRepoRoot(final Path start) {
|
||||||
var current = start;
|
var current = start;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
|
|||||||
@ -105,6 +105,7 @@ class PrometeuLanguageServerTest {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
List.of(),
|
List.of(),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import p.studio.lsp.messages.BaselineCodeActions;
|
|||||||
import p.studio.lsp.messages.BaselineCompletion;
|
import p.studio.lsp.messages.BaselineCompletion;
|
||||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||||
import p.studio.lsp.messages.BaselineDocumentIssue;
|
import p.studio.lsp.messages.BaselineDocumentIssue;
|
||||||
|
import p.studio.lsp.messages.BaselineDocumentLink;
|
||||||
|
import p.studio.lsp.messages.BaselineDocumentLinks;
|
||||||
import p.studio.lsp.messages.BaselineIssueSeverity;
|
import p.studio.lsp.messages.BaselineIssueSeverity;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItem;
|
import p.studio.lsp.messages.BaselineCompletionItem;
|
||||||
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
import p.studio.lsp.messages.BaselineCompletionItemKind;
|
||||||
@ -55,6 +57,7 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
List.of("demo-keyword"),
|
List.of("demo-keyword"),
|
||||||
List.of(new BaselineSemanticHostProjection(
|
List.of(new BaselineSemanticHostProjection(
|
||||||
"vscode",
|
"vscode",
|
||||||
@ -82,6 +85,7 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
assertEquals(Boolean.TRUE, result.getCapabilities().getRenameProvider().getRight().getPrepareProvider());
|
assertEquals(Boolean.TRUE, result.getCapabilities().getRenameProvider().getRight().getPrepareProvider());
|
||||||
assertEquals(List.of(CodeActionKind.QuickFix), result.getCapabilities().getCodeActionProvider().getRight().getCodeActionKinds());
|
assertEquals(List.of(CodeActionKind.QuickFix), result.getCapabilities().getCodeActionProvider().getRight().getCodeActionKinds());
|
||||||
assertEquals(Boolean.FALSE, result.getCapabilities().getCodeActionProvider().getRight().getResolveProvider());
|
assertEquals(Boolean.FALSE, result.getCapabilities().getCodeActionProvider().getRight().getResolveProvider());
|
||||||
|
assertEquals(Boolean.FALSE, result.getCapabilities().getDocumentLinkProvider().getResolveProvider());
|
||||||
|
|
||||||
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"));
|
||||||
@ -298,4 +302,53 @@ final class Lsp4jProtocolMessageMapperTest {
|
|||||||
assertEquals(1, action.getDiagnostics().size());
|
assertEquals(1, action.getDiagnostics().size());
|
||||||
assertEquals(diagnostic, action.getDiagnostics().getFirst());
|
assertEquals(diagnostic, action.getDiagnostics().getFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void documentLinksMapRangeAndTargetWithoutResolve() {
|
||||||
|
final var mapper = new Lsp4jProtocolMessageMapper();
|
||||||
|
final var unsupported = mapper.mapInitializeResult(serverDescription(false));
|
||||||
|
assertNull(unsupported.getCapabilities().getDocumentLinkProvider());
|
||||||
|
|
||||||
|
final var mapped = mapper.mapDocumentLinks(new BaselineDocumentLinks(List.of(
|
||||||
|
new BaselineDocumentLink("file:///tmp/a/source.pbs", 0, 19, 0, 25))));
|
||||||
|
assertEquals(1, mapped.size());
|
||||||
|
assertEquals("file:///tmp/a/source.pbs", mapped.getFirst().getTarget());
|
||||||
|
assertEquals(0, mapped.getFirst().getRange().getStart().getLine());
|
||||||
|
assertEquals(19, mapped.getFirst().getRange().getStart().getCharacter());
|
||||||
|
assertEquals(25, mapped.getFirst().getRange().getEnd().getCharacter());
|
||||||
|
assertNull(mapped.getFirst().getData());
|
||||||
|
assertTrue(mapper.mapDocumentLinks(BaselineDocumentLinks.empty()).isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BaselineServerDescription serverDescription(final boolean documentLinksSupported) {
|
||||||
|
return new BaselineServerDescription(
|
||||||
|
"demo",
|
||||||
|
"1",
|
||||||
|
"pbs",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
documentLinksSupported,
|
||||||
|
List.of("demo-keyword"),
|
||||||
|
List.of(new BaselineSemanticHostProjection(
|
||||||
|
"vscode",
|
||||||
|
List.of(new BaselineSemanticHostProjectionEntry(
|
||||||
|
"demo-keyword",
|
||||||
|
"keyword",
|
||||||
|
List.of(),
|
||||||
|
"variable",
|
||||||
|
List.of())))),
|
||||||
|
List.of(new BaselineVisualTheme(
|
||||||
|
"demo-default",
|
||||||
|
"Demo Default",
|
||||||
|
new BaselineEditorPalette("#111111", "#222222", "#333333", "#444444", "#555555", "#666666", "#777777"),
|
||||||
|
List.of(new BaselineTokenStyle("demo-keyword", "#888888", false, false, false)))),
|
||||||
|
"demo-default");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user