dev/pbs-lsp-remaining-editor-surface #31
@ -290,6 +290,10 @@ When tooling publishes an editor diagnostic, `source` MUST be the `languageId` o
|
||||
|
||||
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.
|
||||
|
||||
When a frontend exposes folding ranges, the ranges MUST come from recovered syntax spans for top-level `fn`, `struct`, `service`, `contract`, `enum`, `error`, `callback`, and `host` declarations, their bodies, and `Doc` text blocks. A parameter list MUST fold from the parenthesis pair. A brace, parenthesis, or text-block delimiter that the recovered tree does not cover MUST still produce a token range, including an unmatched opener through the end of the text. Folding ranges MUST NOT be document symbols.
|
||||
|
||||
When a frontend exposes selection ranges, the chain at a cursor MUST run from the inside outward in this order: identifier, argument or parameter list, block or `Doc` text block, declaration. A layer that does not contain the cursor MUST be omitted. A missing folding or selection 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.
|
||||
|
||||
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.
|
||||
|
||||
@ -16,12 +16,15 @@ import p.studio.compiler.pbs.semantics.PbsEditorialSupportService.EditorialDocum
|
||||
import p.studio.compiler.models.SourceKind;
|
||||
import p.studio.compiler.pbs.semantics.PbsEditorialRename;
|
||||
import p.studio.compiler.pbs.semantics.PbsEditorialWorkspaceSymbol;
|
||||
import p.studio.compiler.pbs.PbsStructureRanges;
|
||||
import p.studio.compiler.pbs.semantics.PbsQuickFix;
|
||||
import p.studio.compiler.pbs.semantics.PbsQuickFixCollector;
|
||||
import p.studio.compiler.services.PBSFrontendPhaseService.PbsSemanticReadSurface;
|
||||
import p.studio.compiler.services.FrontendCodeAction;
|
||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||
import p.studio.compiler.services.FrontendDocumentLink;
|
||||
import p.studio.compiler.services.FrontendFoldingRange;
|
||||
import p.studio.compiler.services.FrontendSelectionRange;
|
||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||
import p.studio.compiler.services.FrontendDocumentSymbol;
|
||||
@ -443,6 +446,35 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
||||
return List.copyOf(links);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean foldingRangesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FrontendFoldingRange> foldingRanges(
|
||||
final FrontendDocumentRequest request,
|
||||
final FrontendEditorialContext editorialContext) {
|
||||
return PbsStructureRanges.foldingRanges(request == null || request.documentText() == null
|
||||
? ""
|
||||
: request.documentText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean selectionRangesSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FrontendSelectionRange> selectionRanges(
|
||||
final FrontendDocumentRequest request,
|
||||
final FrontendEditorialContext editorialContext,
|
||||
final List<Integer> offsets) {
|
||||
return PbsStructureRanges.selectionRanges(
|
||||
request == null || request.documentText() == null ? "" : request.documentText(),
|
||||
offsets);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean codeActionsSupported() {
|
||||
return true;
|
||||
|
||||
@ -0,0 +1,380 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
import p.studio.compiler.pbs.ast.PbsAst;
|
||||
import p.studio.compiler.pbs.lexer.PbsLexer;
|
||||
import p.studio.compiler.pbs.lexer.PbsToken;
|
||||
import p.studio.compiler.pbs.lexer.PbsTokenKind;
|
||||
import p.studio.compiler.pbs.parser.PbsParser;
|
||||
import p.studio.compiler.services.FrontendFoldingRange;
|
||||
import p.studio.compiler.services.FrontendSelectionRange;
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.compiler.source.identifiers.FileId;
|
||||
import p.studio.utilities.structures.ReadOnlyList;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public final class PbsStructureRanges {
|
||||
private PbsStructureRanges() {
|
||||
}
|
||||
|
||||
public static List<FrontendFoldingRange> foldingRanges(final String documentText) {
|
||||
final String text = documentText == null ? "" : documentText;
|
||||
final ReadOnlyList<PbsToken> tokens = PbsLexer.lex(text, FileId.none(), DiagnosticSink.empty());
|
||||
final PbsAst.File ast = PbsParser.parse(tokens, FileId.none(), DiagnosticSink.empty());
|
||||
return foldingRanges(text, ast, tokens);
|
||||
}
|
||||
|
||||
public static List<FrontendSelectionRange> selectionRanges(
|
||||
final String documentText,
|
||||
final List<Integer> offsets) {
|
||||
final String text = documentText == null ? "" : documentText;
|
||||
final ReadOnlyList<PbsToken> tokens = PbsLexer.lex(text, FileId.none(), DiagnosticSink.empty());
|
||||
final PbsAst.File ast = PbsParser.parse(tokens, FileId.none(), DiagnosticSink.empty());
|
||||
if (offsets == null || offsets.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
final Collected collected = collect(ast);
|
||||
final List<SpanPair> delimiters = delimiters(tokens, textEnd(text));
|
||||
final ArrayList<FrontendSelectionRange> ranges = new ArrayList<>();
|
||||
for (final Integer offset : offsets) {
|
||||
if (offset == null || offset < 0) {
|
||||
continue;
|
||||
}
|
||||
final FrontendSelectionRange range = selectionAt(offset, tokens, collected, delimiters);
|
||||
if (range != null) {
|
||||
ranges.add(range);
|
||||
}
|
||||
}
|
||||
return List.copyOf(ranges);
|
||||
}
|
||||
|
||||
private static List<FrontendFoldingRange> foldingRanges(
|
||||
final String text,
|
||||
final PbsAst.File ast,
|
||||
final ReadOnlyList<PbsToken> tokens) {
|
||||
final Collected collected = collect(ast);
|
||||
final LinkedHashSet<String> emitted = new LinkedHashSet<>();
|
||||
final ArrayList<SpanPair> ranges = new ArrayList<>();
|
||||
for (final SpanPair span : collected.folding()) {
|
||||
addRange(ranges, emitted, span);
|
||||
}
|
||||
for (final SpanPair delimiter : delimiters(tokens, textEnd(text))) {
|
||||
if (collected.coveredKeys().contains(delimiter.key())) {
|
||||
continue;
|
||||
}
|
||||
addRange(ranges, emitted, delimiter);
|
||||
}
|
||||
ranges.sort(Comparator.comparingInt(SpanPair::start).thenComparingInt(SpanPair::end));
|
||||
return ranges.stream()
|
||||
.map(span -> new FrontendFoldingRange(span.start(), span.end()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static FrontendSelectionRange selectionAt(
|
||||
final int offset,
|
||||
final ReadOnlyList<PbsToken> tokens,
|
||||
final Collected collected,
|
||||
final List<SpanPair> delimiters) {
|
||||
final SpanPair identifier = smallest(identifierSpans(tokens), offset);
|
||||
final SpanPair parameters = smallest(delimiters, offset);
|
||||
final SpanPair block = smallest(collected.blocks(), offset);
|
||||
final SpanPair declaration = smallest(collected.declarations(), offset);
|
||||
FrontendSelectionRange node = null;
|
||||
SpanPair previous = null;
|
||||
for (final SpanPair layer : new SpanPair[] {declaration, block, parameters, identifier}) {
|
||||
if (layer == null) {
|
||||
continue;
|
||||
}
|
||||
if (previous != null && previous.start() == layer.start() && previous.end() == layer.end()) {
|
||||
continue;
|
||||
}
|
||||
node = new FrontendSelectionRange(layer.start(), layer.end(), node);
|
||||
previous = layer;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private static Collected collect(final PbsAst.File ast) {
|
||||
final Collected collected = new Collected();
|
||||
if (ast == null || ast.topDecls() == null) {
|
||||
return collected;
|
||||
}
|
||||
for (final PbsAst.TopDecl topDecl : ast.topDecls()) {
|
||||
visitTop(topDecl, true, collected);
|
||||
}
|
||||
return collected;
|
||||
}
|
||||
|
||||
private static void visitTop(
|
||||
final PbsAst.TopDecl topDecl,
|
||||
final boolean topLevel,
|
||||
final Collected collected) {
|
||||
if (topDecl == null) {
|
||||
return;
|
||||
}
|
||||
if (topLevel && isListedDeclaration(topDecl)) {
|
||||
addSpan(collected.folding(), topDecl.span());
|
||||
}
|
||||
if (isListedDeclaration(topDecl) || topDecl instanceof PbsAst.FunctionDecl) {
|
||||
addSpan(collected.declarations(), topDecl.span());
|
||||
}
|
||||
switch (topDecl) {
|
||||
case PbsAst.FunctionDecl functionDecl -> visitFunction(functionDecl, collected);
|
||||
case PbsAst.StructDecl structDecl -> {
|
||||
visitAttributes(structDecl.attributes(), collected);
|
||||
if (structDecl.methods() != null) {
|
||||
for (final PbsAst.FunctionDecl method : structDecl.methods()) {
|
||||
visitTop(method, false, collected);
|
||||
}
|
||||
}
|
||||
if (structDecl.ctors() != null) {
|
||||
for (final PbsAst.CtorDecl ctor : structDecl.ctors()) {
|
||||
visitCtor(ctor, collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
case PbsAst.ServiceDecl serviceDecl -> {
|
||||
visitAttributes(serviceDecl.attributes(), collected);
|
||||
if (serviceDecl.methods() != null) {
|
||||
for (final PbsAst.FunctionDecl method : serviceDecl.methods()) {
|
||||
visitTop(method, false, collected);
|
||||
}
|
||||
}
|
||||
}
|
||||
case PbsAst.ContractDecl contractDecl -> visitAttributes(contractDecl.attributes(), collected);
|
||||
case PbsAst.HostDecl hostDecl -> visitAttributes(hostDecl.attributes(), collected);
|
||||
case PbsAst.EnumDecl enumDecl -> visitAttributes(enumDecl.attributes(), collected);
|
||||
case PbsAst.ErrorDecl errorDecl -> visitAttributes(errorDecl.attributes(), collected);
|
||||
case PbsAst.CallbackDecl callbackDecl -> visitAttributes(callbackDecl.attributes(), collected);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void visitFunction(
|
||||
final PbsAst.FunctionDecl functionDecl,
|
||||
final Collected collected) {
|
||||
visitAttributes(functionDecl.attributes(), collected);
|
||||
coverBlock(functionDecl.body(), true, collected);
|
||||
}
|
||||
|
||||
private static void visitCtor(
|
||||
final PbsAst.CtorDecl ctor,
|
||||
final Collected collected) {
|
||||
if (ctor == null) {
|
||||
return;
|
||||
}
|
||||
addSpan(collected.declarations(), ctor.span());
|
||||
coverBlock(ctor.body(), true, collected);
|
||||
}
|
||||
|
||||
private static void visitAttributes(
|
||||
final ReadOnlyList<PbsAst.Attribute> attributes,
|
||||
final Collected collected) {
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
for (final PbsAst.Attribute attribute : attributes) {
|
||||
if (attribute == null || attribute.arguments() == null) {
|
||||
continue;
|
||||
}
|
||||
for (final PbsAst.AttributeArgument argument : attribute.arguments()) {
|
||||
if (argument != null && argument.value() instanceof PbsAst.AttributeDocTextBlockValue doc) {
|
||||
addSpan(collected.folding(), doc.span());
|
||||
addSpan(collected.blocks(), doc.span());
|
||||
collected.cover(doc.span());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void coverBlock(
|
||||
final PbsAst.Block block,
|
||||
final boolean emit,
|
||||
final Collected collected) {
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
if (emit) {
|
||||
addSpan(collected.folding(), block.span());
|
||||
}
|
||||
addSpan(collected.blocks(), block.span());
|
||||
collected.cover(block.span());
|
||||
if (block.statements() == null) {
|
||||
return;
|
||||
}
|
||||
for (final PbsAst.Statement statement : block.statements()) {
|
||||
coverStatement(statement, collected);
|
||||
}
|
||||
}
|
||||
|
||||
private static void coverStatement(
|
||||
final PbsAst.Statement statement,
|
||||
final Collected collected) {
|
||||
switch (statement) {
|
||||
case null -> {
|
||||
}
|
||||
case PbsAst.IfStatement ifStatement -> {
|
||||
coverBlock(ifStatement.thenBlock(), false, collected);
|
||||
coverBlock(ifStatement.elseBlock(), false, collected);
|
||||
coverStatement(ifStatement.elseIf(), collected);
|
||||
}
|
||||
case PbsAst.ForStatement forStatement -> coverBlock(forStatement.body(), false, collected);
|
||||
case PbsAst.WhileStatement whileStatement -> coverBlock(whileStatement.body(), false, collected);
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isListedDeclaration(final PbsAst.TopDecl topDecl) {
|
||||
return topDecl instanceof PbsAst.FunctionDecl
|
||||
|| topDecl instanceof PbsAst.StructDecl
|
||||
|| topDecl instanceof PbsAst.ServiceDecl
|
||||
|| topDecl instanceof PbsAst.ContractDecl
|
||||
|| topDecl instanceof PbsAst.HostDecl
|
||||
|| topDecl instanceof PbsAst.EnumDecl
|
||||
|| topDecl instanceof PbsAst.ErrorDecl
|
||||
|| topDecl instanceof PbsAst.CallbackDecl;
|
||||
}
|
||||
|
||||
private static List<SpanPair> delimiters(
|
||||
final ReadOnlyList<PbsToken> tokens,
|
||||
final int textEnd) {
|
||||
final ArrayList<PbsToken> stack = new ArrayList<>();
|
||||
final ArrayList<SpanPair> pairs = new ArrayList<>();
|
||||
if (tokens == null) {
|
||||
return pairs;
|
||||
}
|
||||
for (final PbsToken token : tokens) {
|
||||
if (token == null) {
|
||||
continue;
|
||||
}
|
||||
if (token.kind() == PbsTokenKind.LEFT_BRACE || token.kind() == PbsTokenKind.LEFT_PAREN) {
|
||||
stack.add(token);
|
||||
continue;
|
||||
}
|
||||
if (token.kind() == PbsTokenKind.DOC_TEXT_BLOCK) {
|
||||
addPair(pairs, token.start(), token.end());
|
||||
continue;
|
||||
}
|
||||
if (token.kind() == PbsTokenKind.RIGHT_BRACE || token.kind() == PbsTokenKind.RIGHT_PAREN) {
|
||||
final PbsTokenKind openKind = token.kind() == PbsTokenKind.RIGHT_BRACE
|
||||
? PbsTokenKind.LEFT_BRACE
|
||||
: PbsTokenKind.LEFT_PAREN;
|
||||
if (!stack.isEmpty() && stack.getLast().kind() == openKind) {
|
||||
final PbsToken open = stack.removeLast();
|
||||
addPair(pairs, open.start(), token.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (final PbsToken open : stack) {
|
||||
addPair(pairs, open.start(), textEnd);
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
private static List<SpanPair> identifierSpans(final ReadOnlyList<PbsToken> tokens) {
|
||||
final ArrayList<SpanPair> spans = new ArrayList<>();
|
||||
if (tokens == null) {
|
||||
return spans;
|
||||
}
|
||||
for (final PbsToken token : tokens) {
|
||||
if (token != null && token.kind() == PbsTokenKind.IDENTIFIER) {
|
||||
addPair(spans, token.start(), token.end());
|
||||
}
|
||||
}
|
||||
return spans;
|
||||
}
|
||||
|
||||
private static SpanPair smallest(final List<SpanPair> spans, final int offset) {
|
||||
SpanPair best = null;
|
||||
for (final SpanPair span : spans) {
|
||||
if (span.start() > offset || offset >= span.end()) {
|
||||
continue;
|
||||
}
|
||||
if (best == null
|
||||
|| span.end() - span.start() < best.end() - best.start()
|
||||
|| (span.end() - span.start() == best.end() - best.start() && span.start() > best.start())) {
|
||||
best = span;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private static void addSpan(final List<SpanPair> spans, final Span span) {
|
||||
if (span == null) {
|
||||
return;
|
||||
}
|
||||
addPair(spans, toOffset(span.getStart()), toOffset(span.getEnd()));
|
||||
}
|
||||
|
||||
private static void addPair(final List<SpanPair> spans, final int start, final int end) {
|
||||
if (start < 0 || end <= start) {
|
||||
return;
|
||||
}
|
||||
spans.add(new SpanPair(start, end));
|
||||
}
|
||||
|
||||
private static void addRange(
|
||||
final List<SpanPair> ranges,
|
||||
final LinkedHashSet<String> emitted,
|
||||
final SpanPair span) {
|
||||
if (span == null || !emitted.add(span.key())) {
|
||||
return;
|
||||
}
|
||||
ranges.add(span);
|
||||
}
|
||||
|
||||
private static int textEnd(final String text) {
|
||||
return text.getBytes(StandardCharsets.UTF_8).length;
|
||||
}
|
||||
|
||||
private static int toOffset(final long value) {
|
||||
if (value <= 0L) {
|
||||
return 0;
|
||||
}
|
||||
return (int) Math.min(Integer.MAX_VALUE, value);
|
||||
}
|
||||
|
||||
private record SpanPair(int start, int end) {
|
||||
private String key() {
|
||||
return start + ":" + end;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Collected {
|
||||
private final ArrayList<SpanPair> folding = new ArrayList<>();
|
||||
private final ArrayList<SpanPair> covered = new ArrayList<>();
|
||||
private final ArrayList<SpanPair> blocks = new ArrayList<>();
|
||||
private final ArrayList<SpanPair> declarations = new ArrayList<>();
|
||||
|
||||
private List<SpanPair> folding() {
|
||||
return folding;
|
||||
}
|
||||
|
||||
private void cover(final Span span) {
|
||||
addSpan(covered, span);
|
||||
}
|
||||
|
||||
private LinkedHashSet<String> coveredKeys() {
|
||||
final LinkedHashSet<String> keys = new LinkedHashSet<>();
|
||||
for (final SpanPair span : covered) {
|
||||
keys.add(span.key());
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
private List<SpanPair> blocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private List<SpanPair> declarations() {
|
||||
return declarations;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package p.studio.compiler.pbs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.compiler.services.FrontendFoldingRange;
|
||||
import p.studio.compiler.services.FrontendSelectionRange;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PbsStructureRangesTest {
|
||||
private static final String SOURCE = """
|
||||
[Doc(markdown = \"\"\"
|
||||
hello
|
||||
\"\"\")]
|
||||
fn length(value: int) -> int {
|
||||
return value;
|
||||
}
|
||||
""";
|
||||
|
||||
@Test
|
||||
void foldingUsesDeclarationBodyParameterListAndDocText() {
|
||||
final var ranges = PbsStructureRanges.foldingRanges(SOURCE);
|
||||
final var slices = ranges.stream().map(range -> slice(SOURCE, range)).toList();
|
||||
|
||||
assertTrue(slices.stream().anyMatch(slice -> slice.contains("hello")), slices.toString());
|
||||
assertTrue(slices.stream().anyMatch(slice -> slice.equals("(value: int)")), slices.toString());
|
||||
assertTrue(slices.stream().anyMatch(slice -> slice.startsWith("{")), slices.toString());
|
||||
assertTrue(slices.stream().anyMatch(slice -> slice.contains("fn length")), slices.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectionGrowsFromIdentifierToBlockToDeclaration() {
|
||||
final int marker = SOURCE.indexOf("return value");
|
||||
assertTrue(marker >= 0, SOURCE);
|
||||
final int offset = marker + "return ".length();
|
||||
final var selections = PbsStructureRanges.selectionRanges(SOURCE, List.of(offset));
|
||||
assertEquals(1, selections.size(), "offset " + offset);
|
||||
final FrontendSelectionRange selection = selections.getFirst();
|
||||
|
||||
assertEquals("value", slice(SOURCE, selection.startOffset(), selection.endOffset()));
|
||||
assertNotNull(selection.parent());
|
||||
assertTrue(slice(SOURCE, selection.parent().startOffset(), selection.parent().endOffset()).startsWith("{"));
|
||||
assertNotNull(selection.parent().parent());
|
||||
assertTrue(slice(SOURCE, selection.parent().parent().startOffset(), selection.parent().parent().endOffset())
|
||||
.contains("fn length"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmatchedBraceFoldsThroughTheEndOfTheText() {
|
||||
final String broken = """
|
||||
fn broken() -> void {
|
||||
let x: int = 1;
|
||||
""";
|
||||
final int textEnd = broken.getBytes(StandardCharsets.UTF_8).length;
|
||||
final var ranges = PbsStructureRanges.foldingRanges(broken);
|
||||
|
||||
assertTrue(ranges.stream().anyMatch(range ->
|
||||
range.endOffset() == textEnd && slice(broken, range).startsWith("{")));
|
||||
}
|
||||
|
||||
private static String slice(final String text, final FrontendFoldingRange range) {
|
||||
return slice(text, range.startOffset(), range.endOffset());
|
||||
}
|
||||
|
||||
private static String slice(final String text, final int start, final int end) {
|
||||
final byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
|
||||
return new String(bytes, start, end - start, StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package p.studio.compiler.services;
|
||||
|
||||
public record FrontendFoldingRange(
|
||||
int startOffset,
|
||||
int endOffset) {
|
||||
|
||||
public FrontendFoldingRange {
|
||||
if (startOffset < 0) {
|
||||
throw new IllegalArgumentException("startOffset must not be negative");
|
||||
}
|
||||
if (endOffset < startOffset) {
|
||||
throw new IllegalArgumentException("endOffset must not be before startOffset");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,6 +153,37 @@ public interface FrontendLanguageService {
|
||||
return documentLinks(request);
|
||||
}
|
||||
|
||||
default boolean foldingRangesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default List<FrontendFoldingRange> foldingRanges(final FrontendDocumentRequest request) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default List<FrontendFoldingRange> foldingRanges(
|
||||
final FrontendDocumentRequest request,
|
||||
final FrontendEditorialContext editorialContext) {
|
||||
return foldingRanges(request);
|
||||
}
|
||||
|
||||
default boolean selectionRangesSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default List<FrontendSelectionRange> selectionRanges(
|
||||
final FrontendDocumentRequest request,
|
||||
final List<Integer> offsets) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default List<FrontendSelectionRange> selectionRanges(
|
||||
final FrontendDocumentRequest request,
|
||||
final FrontendEditorialContext editorialContext,
|
||||
final List<Integer> offsets) {
|
||||
return selectionRanges(request, offsets);
|
||||
}
|
||||
|
||||
default boolean codeActionsSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package p.studio.compiler.services;
|
||||
|
||||
public record FrontendSelectionRange(
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
FrontendSelectionRange parent) {
|
||||
|
||||
public FrontendSelectionRange {
|
||||
if (startOffset < 0) {
|
||||
throw new IllegalArgumentException("startOffset must not be negative");
|
||||
}
|
||||
if (endOffset < startOffset) {
|
||||
throw new IllegalArgumentException("endOffset must not be before startOffset");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -33,6 +33,12 @@ class FrontendLanguageServiceTest {
|
||||
assertFalse(service.documentLinksSupported());
|
||||
assertTrue(service.documentLinks(request).isEmpty());
|
||||
assertTrue(service.documentLinks(request, null).isEmpty());
|
||||
assertFalse(service.foldingRangesSupported());
|
||||
assertTrue(service.foldingRanges(request).isEmpty());
|
||||
assertTrue(service.foldingRanges(request, null).isEmpty());
|
||||
assertFalse(service.selectionRangesSupported());
|
||||
assertTrue(service.selectionRanges(request, List.of(0)).isEmpty());
|
||||
assertTrue(service.selectionRanges(request, null, List.of(0)).isEmpty());
|
||||
assertFalse(service.codeActionsSupported());
|
||||
assertTrue(service.codeActions(request).isEmpty());
|
||||
assertTrue(service.codeActions(request, null).isEmpty());
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
public record BaselineFoldingRange(
|
||||
int startLine,
|
||||
int startCharacter,
|
||||
int endLine,
|
||||
int endCharacter) {
|
||||
|
||||
public BaselineFoldingRange {
|
||||
if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0) {
|
||||
throw new IllegalArgumentException("folding range coordinates must not be negative");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BaselineFoldingRanges(List<BaselineFoldingRange> ranges) {
|
||||
public BaselineFoldingRanges {
|
||||
ranges = ranges == null ? List.of() : List.copyOf(ranges);
|
||||
}
|
||||
|
||||
public static BaselineFoldingRanges empty() {
|
||||
return new BaselineFoldingRanges(List.of());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
public record BaselineSelectionRange(
|
||||
int startLine,
|
||||
int startCharacter,
|
||||
int endLine,
|
||||
int endCharacter,
|
||||
BaselineSelectionRange parent) {
|
||||
|
||||
public BaselineSelectionRange {
|
||||
if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0) {
|
||||
throw new IllegalArgumentException("selection range coordinates must not be negative");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package p.studio.lsp.messages;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record BaselineSelectionRanges(List<BaselineSelectionRange> ranges) {
|
||||
public BaselineSelectionRanges {
|
||||
ranges = ranges == null ? List.of() : List.copyOf(ranges);
|
||||
}
|
||||
|
||||
public static BaselineSelectionRanges empty() {
|
||||
return new BaselineSelectionRanges(List.of());
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,8 @@ public record BaselineServerDescription(
|
||||
boolean renameSupported,
|
||||
boolean codeActionsSupported,
|
||||
boolean documentLinksSupported,
|
||||
boolean foldingRangesSupported,
|
||||
boolean selectionRangesSupported,
|
||||
List<String> semanticTokenTypes,
|
||||
List<BaselineSemanticHostProjection> semanticHostProjections,
|
||||
List<BaselineVisualTheme> visualThemes,
|
||||
|
||||
@ -10,6 +10,9 @@ import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
||||
import p.studio.lsp.messages.BaselineCodeActionDiagnostic;
|
||||
import p.studio.lsp.messages.BaselineCodeActions;
|
||||
import p.studio.lsp.messages.BaselineDocumentLinks;
|
||||
import p.studio.lsp.messages.BaselineFoldingRanges;
|
||||
import p.studio.lsp.messages.BaselineSelectionRanges;
|
||||
import p.studio.lsp.messages.DocumentPosition;
|
||||
import p.studio.lsp.messages.BaselinePrepareRename;
|
||||
import p.studio.lsp.messages.BaselineRename;
|
||||
import p.studio.lsp.messages.BaselineSemanticTokens;
|
||||
@ -65,6 +68,18 @@ public interface LanguageServiceBridge {
|
||||
return BaselineDocumentLinks.empty();
|
||||
}
|
||||
|
||||
default BaselineFoldingRanges foldingRanges(LspProjectContext context, String documentUri, String text) {
|
||||
return BaselineFoldingRanges.empty();
|
||||
}
|
||||
|
||||
default BaselineSelectionRanges selectionRanges(
|
||||
LspProjectContext context,
|
||||
String documentUri,
|
||||
String text,
|
||||
List<DocumentPosition> positions) {
|
||||
return BaselineSelectionRanges.empty();
|
||||
}
|
||||
|
||||
BaselineCodeActions codeActions(
|
||||
LspProjectContext context,
|
||||
String documentUri,
|
||||
|
||||
@ -7,6 +7,8 @@ import p.studio.compiler.models.*;
|
||||
import p.studio.compiler.services.FrontendCodeAction;
|
||||
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||
import p.studio.compiler.services.FrontendDocumentLink;
|
||||
import p.studio.compiler.services.FrontendFoldingRange;
|
||||
import p.studio.compiler.services.FrontendSelectionRange;
|
||||
import p.studio.compiler.services.FrontendLanguageService;
|
||||
import p.studio.compiler.services.FrontendDefinitionLocation;
|
||||
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||
@ -61,6 +63,12 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
||||
final boolean documentLinksSupported = languageService
|
||||
.map(FrontendLanguageService::documentLinksSupported)
|
||||
.orElse(false);
|
||||
final boolean foldingRangesSupported = languageService
|
||||
.map(FrontendLanguageService::foldingRangesSupported)
|
||||
.orElse(false);
|
||||
final boolean selectionRangesSupported = languageService
|
||||
.map(FrontendLanguageService::selectionRangesSupported)
|
||||
.orElse(false);
|
||||
return new BaselineServerDescription(
|
||||
"Prometeu Studio LSP",
|
||||
"0.1.0",
|
||||
@ -75,6 +83,8 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
||||
languageServicePresent,
|
||||
codeActionsSupported,
|
||||
documentLinksSupported,
|
||||
foldingRangesSupported,
|
||||
selectionRangesSupported,
|
||||
presentation.semanticKeys(),
|
||||
presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(),
|
||||
presentation.themes().stream().map(this::mapVisualTheme).toList(),
|
||||
@ -393,6 +403,120 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
||||
.orElseGet(BaselineDocumentLinks::empty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaselineFoldingRanges foldingRanges(
|
||||
final LspProjectContext context,
|
||||
final String documentUri,
|
||||
final String text) {
|
||||
return structureRanges(context, documentUri, text, true, List.of()).folding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaselineSelectionRanges selectionRanges(
|
||||
final LspProjectContext context,
|
||||
final String documentUri,
|
||||
final String text,
|
||||
final List<DocumentPosition> positions) {
|
||||
return structureRanges(context, documentUri, text, false, positions == null ? List.of() : positions).selection();
|
||||
}
|
||||
|
||||
private StructureRanges structureRanges(
|
||||
final LspProjectContext context,
|
||||
final String documentUri,
|
||||
final String text,
|
||||
final boolean folding,
|
||||
final List<DocumentPosition> positions) {
|
||||
Objects.requireNonNull(context, "context");
|
||||
final var languageService = frontendProvider(context).languageService();
|
||||
if (languageService.isEmpty()) {
|
||||
return StructureRanges.empty();
|
||||
}
|
||||
final boolean supported = folding
|
||||
? languageService.orElseThrow().foldingRangesSupported()
|
||||
: languageService.orElseThrow().selectionRangesSupported();
|
||||
if (!supported) {
|
||||
return StructureRanges.empty();
|
||||
}
|
||||
final String effectiveText = text == null ? "" : text;
|
||||
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||
context.projectRoot(),
|
||||
normalizeDocumentPath(documentUri),
|
||||
effectiveText);
|
||||
final DocumentPositionMapper mapper = new DocumentPositionMapper(effectiveText);
|
||||
if (folding) {
|
||||
return new StructureRanges(new BaselineFoldingRanges(mapFoldingRanges(
|
||||
languageService.orElseThrow().foldingRanges(request, null),
|
||||
mapper)), BaselineSelectionRanges.empty());
|
||||
}
|
||||
final ArrayList<Integer> offsets = new ArrayList<>();
|
||||
for (final DocumentPosition position : positions) {
|
||||
if (position == null) {
|
||||
offsets.add(0);
|
||||
continue;
|
||||
}
|
||||
offsets.add(mapper.byteOffsetOf(position.line(), position.character()));
|
||||
}
|
||||
return new StructureRanges(
|
||||
BaselineFoldingRanges.empty(),
|
||||
new BaselineSelectionRanges(mapSelectionRanges(
|
||||
languageService.orElseThrow().selectionRanges(request, null, offsets),
|
||||
mapper)));
|
||||
}
|
||||
|
||||
private List<BaselineFoldingRange> mapFoldingRanges(
|
||||
final List<FrontendFoldingRange> ranges,
|
||||
final DocumentPositionMapper mapper) {
|
||||
if (ranges == null || ranges.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
final ArrayList<BaselineFoldingRange> mapped = new ArrayList<>();
|
||||
for (final FrontendFoldingRange range : ranges) {
|
||||
final DocumentPosition start = mapper.positionOf(range.startOffset());
|
||||
final DocumentPosition end = mapper.positionOf(range.endOffset());
|
||||
mapped.add(new BaselineFoldingRange(
|
||||
start.line(),
|
||||
start.character(),
|
||||
end.line(),
|
||||
end.character()));
|
||||
}
|
||||
return List.copyOf(mapped);
|
||||
}
|
||||
|
||||
private List<BaselineSelectionRange> mapSelectionRanges(
|
||||
final List<FrontendSelectionRange> ranges,
|
||||
final DocumentPositionMapper mapper) {
|
||||
if (ranges == null || ranges.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
final ArrayList<BaselineSelectionRange> mapped = new ArrayList<>();
|
||||
for (final FrontendSelectionRange range : ranges) {
|
||||
mapped.add(mapSelectionRange(range, mapper));
|
||||
}
|
||||
return List.copyOf(mapped);
|
||||
}
|
||||
|
||||
private BaselineSelectionRange mapSelectionRange(
|
||||
final FrontendSelectionRange range,
|
||||
final DocumentPositionMapper mapper) {
|
||||
if (range == null) {
|
||||
return null;
|
||||
}
|
||||
final DocumentPosition start = mapper.positionOf(range.startOffset());
|
||||
final DocumentPosition end = mapper.positionOf(range.endOffset());
|
||||
return new BaselineSelectionRange(
|
||||
start.line(),
|
||||
start.character(),
|
||||
end.line(),
|
||||
end.character(),
|
||||
mapSelectionRange(range.parent(), mapper));
|
||||
}
|
||||
|
||||
private record StructureRanges(BaselineFoldingRanges folding, BaselineSelectionRanges selection) {
|
||||
private static StructureRanges empty() {
|
||||
return new StructureRanges(BaselineFoldingRanges.empty(), BaselineSelectionRanges.empty());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaselineCodeActions codeActions(
|
||||
final LspProjectContext context,
|
||||
|
||||
@ -9,6 +9,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import p.studio.lsp.messages.BaselineCodeActionDiagnostic;
|
||||
import p.studio.lsp.messages.DocumentPosition;
|
||||
import p.studio.lsp.messages.LspProjectContext;
|
||||
import p.studio.lsp.services.LanguageServiceBridge;
|
||||
import p.studio.lsp.services.protocol.mapping.ProtocolMessageMapper;
|
||||
@ -168,6 +169,31 @@ public final class PrometeuTextDocumentService implements TextDocumentService {
|
||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapRename(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<FoldingRange>> foldingRange(final FoldingRangeRequestParams params) {
|
||||
final String uri = params.getTextDocument().getUri();
|
||||
final String text = documentTextByUri.get(uri);
|
||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapFoldingRanges(
|
||||
languageServiceBridge.foldingRanges(project, uri, text)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<SelectionRange>> selectionRange(final SelectionRangeParams params) {
|
||||
final String uri = params.getTextDocument().getUri();
|
||||
final String text = documentTextByUri.get(uri);
|
||||
final ArrayList<DocumentPosition> positions = new ArrayList<>();
|
||||
if (params.getPositions() != null) {
|
||||
for (final Position position : params.getPositions()) {
|
||||
if (position == null) {
|
||||
continue;
|
||||
}
|
||||
positions.add(new DocumentPosition(position.getLine(), position.getCharacter()));
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(protocolMessageMapper.mapSelectionRanges(
|
||||
languageServiceBridge.selectionRanges(project, uri, text, positions)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<DocumentLink>> documentLink(final DocumentLinkParams params) {
|
||||
final String uri = params.getTextDocument().getUri();
|
||||
|
||||
@ -58,6 +58,12 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
documentLinkOptions.setResolveProvider(false);
|
||||
capabilities.setDocumentLinkProvider(documentLinkOptions);
|
||||
}
|
||||
if (description.foldingRangesSupported()) {
|
||||
capabilities.setFoldingRangeProvider(true);
|
||||
}
|
||||
if (description.selectionRangesSupported()) {
|
||||
capabilities.setSelectionRangeProvider(true);
|
||||
}
|
||||
final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions();
|
||||
semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of()));
|
||||
semanticTokens.setFull(true);
|
||||
@ -181,6 +187,47 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper {
|
||||
return List.copyOf(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FoldingRange> mapFoldingRanges(final BaselineFoldingRanges ranges) {
|
||||
if (ranges == null || ranges.ranges().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
final ArrayList<FoldingRange> mapped = new ArrayList<>();
|
||||
for (final BaselineFoldingRange range : ranges.ranges()) {
|
||||
final FoldingRange foldingRange = new FoldingRange();
|
||||
foldingRange.setStartLine(range.startLine());
|
||||
foldingRange.setStartCharacter(range.startCharacter());
|
||||
foldingRange.setEndLine(range.endLine());
|
||||
foldingRange.setEndCharacter(range.endCharacter());
|
||||
mapped.add(foldingRange);
|
||||
}
|
||||
return List.copyOf(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SelectionRange> mapSelectionRanges(final BaselineSelectionRanges ranges) {
|
||||
if (ranges == null || ranges.ranges().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
final ArrayList<SelectionRange> mapped = new ArrayList<>();
|
||||
for (final BaselineSelectionRange range : ranges.ranges()) {
|
||||
mapped.add(mapSelectionRange(range));
|
||||
}
|
||||
return List.copyOf(mapped);
|
||||
}
|
||||
|
||||
private SelectionRange mapSelectionRange(final BaselineSelectionRange range) {
|
||||
if (range == null) {
|
||||
return null;
|
||||
}
|
||||
final SelectionRange selectionRange = new SelectionRange();
|
||||
selectionRange.setRange(new Range(
|
||||
new Position(range.startLine(), range.startCharacter()),
|
||||
new Position(range.endLine(), range.endCharacter())));
|
||||
selectionRange.setParent(mapSelectionRange(range.parent()));
|
||||
return selectionRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Either<Command, CodeAction>> mapCodeActions(
|
||||
final BaselineCodeActions actions,
|
||||
|
||||
@ -11,6 +11,8 @@ import org.eclipse.lsp4j.SemanticTokens;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DocumentLink;
|
||||
import org.eclipse.lsp4j.DocumentSymbol;
|
||||
import org.eclipse.lsp4j.FoldingRange;
|
||||
import org.eclipse.lsp4j.SelectionRange;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
@ -21,6 +23,8 @@ import org.eclipse.lsp4j.PrepareRenameDefaultBehavior;
|
||||
import org.eclipse.lsp4j.PrepareRenameResult;
|
||||
import p.studio.lsp.messages.BaselineDocumentAnalysis;
|
||||
import p.studio.lsp.messages.BaselineDocumentLinks;
|
||||
import p.studio.lsp.messages.BaselineFoldingRanges;
|
||||
import p.studio.lsp.messages.BaselineSelectionRanges;
|
||||
import p.studio.lsp.messages.BaselineDocumentSymbols;
|
||||
import p.studio.lsp.messages.BaselineWorkspaceSymbols;
|
||||
import p.studio.lsp.messages.BaselineCodeActions;
|
||||
@ -65,6 +69,14 @@ public interface ProtocolMessageMapper {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default List<FoldingRange> mapFoldingRanges(BaselineFoldingRanges ranges) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
default List<SelectionRange> mapSelectionRanges(BaselineSelectionRanges ranges) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
CompletionList mapCompletion(BaselineCompletion completion);
|
||||
|
||||
SignatureHelp mapSignatureHelp(BaselineSignatureHelp signatureHelp);
|
||||
|
||||
@ -11,6 +11,7 @@ import p.studio.compiler.services.FrontendProvider;
|
||||
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||
import p.studio.lsp.messages.BaselineCodeActionDiagnostic;
|
||||
import p.studio.lsp.messages.BaselineIssueSeverity;
|
||||
import p.studio.lsp.messages.DocumentPosition;
|
||||
import p.studio.lsp.messages.LspProjectContext;
|
||||
import p.studio.utilities.logs.LogAggregator;
|
||||
import p.studio.utilities.structures.ReadOnlySet;
|
||||
@ -77,6 +78,9 @@ class CompilerLanguageServiceBridgeTest {
|
||||
new BaselineCodeActionDiagnostic("E_SEM_DUPLICATE_RESERVED_ATTRIBUTE", 0, 0, 0, 1)))
|
||||
.actions().isEmpty());
|
||||
assertTrue(bridge.documentLinks(context, documentUri, "fn main() {}").links().isEmpty());
|
||||
assertTrue(bridge.foldingRanges(context, documentUri, "fn main() {}").ranges().isEmpty());
|
||||
assertTrue(bridge.selectionRanges(context, documentUri, "fn main() {}", List.of(new DocumentPosition(0, 0)))
|
||||
.ranges().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -772,6 +776,8 @@ class CompilerLanguageServiceBridgeTest {
|
||||
assertTrue(description.renameSupported());
|
||||
assertTrue(description.codeActionsSupported());
|
||||
assertTrue(description.documentLinksSupported());
|
||||
assertTrue(description.foldingRangesSupported());
|
||||
assertTrue(description.selectionRangesSupported());
|
||||
assertEquals(1, description.semanticHostProjections().size());
|
||||
assertEquals("vscode", description.semanticHostProjections().getFirst().hostId());
|
||||
assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream()
|
||||
|
||||
@ -106,6 +106,8 @@ class PrometeuLanguageServerTest {
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
List.of(),
|
||||
List.of(new BaselineSemanticHostProjection(
|
||||
"vscode",
|
||||
|
||||
@ -58,6 +58,8 @@ final class Lsp4jProtocolMessageMapperTest {
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
List.of("demo-keyword"),
|
||||
List.of(new BaselineSemanticHostProjection(
|
||||
"vscode",
|
||||
@ -86,6 +88,8 @@ final class Lsp4jProtocolMessageMapperTest {
|
||||
assertEquals(List.of(CodeActionKind.QuickFix), result.getCapabilities().getCodeActionProvider().getRight().getCodeActionKinds());
|
||||
assertEquals(Boolean.FALSE, result.getCapabilities().getCodeActionProvider().getRight().getResolveProvider());
|
||||
assertEquals(Boolean.FALSE, result.getCapabilities().getDocumentLinkProvider().getResolveProvider());
|
||||
assertEquals(Boolean.TRUE, result.getCapabilities().getFoldingRangeProvider().getLeft());
|
||||
assertEquals(Boolean.TRUE, result.getCapabilities().getSelectionRangeProvider().getLeft());
|
||||
|
||||
final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental());
|
||||
final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections"));
|
||||
@ -308,6 +312,8 @@ final class Lsp4jProtocolMessageMapperTest {
|
||||
final var mapper = new Lsp4jProtocolMessageMapper();
|
||||
final var unsupported = mapper.mapInitializeResult(serverDescription(false));
|
||||
assertNull(unsupported.getCapabilities().getDocumentLinkProvider());
|
||||
assertNull(unsupported.getCapabilities().getFoldingRangeProvider());
|
||||
assertNull(unsupported.getCapabilities().getSelectionRangeProvider());
|
||||
|
||||
final var mapped = mapper.mapDocumentLinks(new BaselineDocumentLinks(List.of(
|
||||
new BaselineDocumentLink("file:///tmp/a/source.pbs", 0, 19, 0, 25))));
|
||||
@ -335,6 +341,8 @@ final class Lsp4jProtocolMessageMapperTest {
|
||||
true,
|
||||
true,
|
||||
documentLinksSupported,
|
||||
documentLinksSupported,
|
||||
documentLinksSupported,
|
||||
List.of("demo-keyword"),
|
||||
List.of(new BaselineSemanticHostProjection(
|
||||
"vscode",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user