diff --git a/docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md b/docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md index ab924de8..13ebaece 100644 --- a/docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md +++ b/docs/specs/compiler/23. Compiler Pipeline Entry Points Specification.md @@ -294,6 +294,8 @@ When a frontend exposes folding ranges, the ranges MUST come from recovered synt 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. +When a frontend exposes formatting, it MUST be full-document formatting only. Range formatting and on-type formatting MUST NOT be advertised. The formatter MUST reprint the existing token stream. It MUST NOT reorder declarations, join lines, or split lines. Indentation MUST be four spaces and MUST change only where brace or parenthesis nesting changes. The interior of a `Doc` text block MUST be copied unchanged. A comment MUST stay on the line of the token it already follows. A missing formatting capability MUST NOT be advertised and MUST produce an empty edit 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. diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSFrontendLanguageService.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSFrontendLanguageService.java index 14439297..b8d1ca1e 100644 --- a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSFrontendLanguageService.java +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/PBSFrontendLanguageService.java @@ -16,6 +16,7 @@ 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.PbsDocumentFormatter; import p.studio.compiler.pbs.PbsStructureRanges; import p.studio.compiler.pbs.semantics.PbsQuickFix; import p.studio.compiler.pbs.semantics.PbsQuickFixCollector; @@ -475,6 +476,17 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService offsets); } + @Override + public boolean formattingSupported() { + return true; + } + + @Override + public Optional formatDocument(final FrontendDocumentRequest request) { + return Optional.of(PbsDocumentFormatter.format( + request == null || request.documentText() == null ? "" : request.documentText())); + } + @Override public boolean codeActionsSupported() { return true; diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentFormatter.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentFormatter.java new file mode 100644 index 00000000..502f360b --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/main/java/p/studio/compiler/pbs/PbsDocumentFormatter.java @@ -0,0 +1,179 @@ +package p.studio.compiler.pbs; + +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.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; + +public final class PbsDocumentFormatter { + private static final int INDENT_SPACES = 4; + + private PbsDocumentFormatter() { + } + + public static String format(final String documentText) { + final String text = documentText == null ? "" : documentText; + if (text.isEmpty()) { + return text; + } + final ReadOnlyList tokens = PbsLexer.lex(text, FileId.none(), DiagnosticSink.empty()); + final ArrayList lines = splitLines(text); + final StringBuilder formatted = new StringBuilder(text.length()); + int depth = 0; + for (final SourceLine line : lines) { + if (isDocInterior(line, tokens)) { + formatted.append(line.content()).append(line.ending()); + continue; + } + final int leadingClosers = leadingClosers(line, tokens); + final int indent = Math.max(0, depth - leadingClosers); + formatted.append(reindent(line.content(), indent)).append(line.ending()); + depth = depthAfter(line, tokens, depth); + } + return formatted.toString(); + } + + private static boolean isDocInterior(final SourceLine line, final ReadOnlyList tokens) { + for (final PbsToken token : tokens) { + if (token.kind() != PbsTokenKind.DOC_TEXT_BLOCK) { + continue; + } + if (line.startByte() > token.start() && line.endByte() <= token.end()) { + return true; + } + } + return false; + } + + private static int leadingClosers(final SourceLine line, final ReadOnlyList tokens) { + int closers = 0; + boolean seenCode = false; + for (final PbsToken token : tokens) { + if (token.start() < line.startByte() || token.start() >= line.endByte()) { + continue; + } + if (token.kind() == PbsTokenKind.COMMENT) { + continue; + } + if (!seenCode && isCloser(token.kind())) { + closers += 1; + continue; + } + seenCode = true; + } + return closers; + } + + private static int depthAfter( + final SourceLine line, + final ReadOnlyList tokens, + final int depth) { + int next = depth; + for (final PbsToken token : tokens) { + if (token.start() < line.startByte() || token.start() >= line.endByte()) { + continue; + } + if (isOpener(token.kind())) { + next += 1; + } else if (isCloser(token.kind())) { + next = Math.max(0, next - 1); + } + } + return next; + } + + private static boolean isOpener(final PbsTokenKind kind) { + return kind == PbsTokenKind.LEFT_BRACE || kind == PbsTokenKind.LEFT_PAREN; + } + + private static boolean isCloser(final PbsTokenKind kind) { + return kind == PbsTokenKind.RIGHT_BRACE || kind == PbsTokenKind.RIGHT_PAREN; + } + + private static String reindent(final String content, final int indent) { + int index = 0; + while (index < content.length()) { + final char character = content.charAt(index); + if (character != ' ' && character != '\t') { + break; + } + index += 1; + } + if (index == content.length()) { + return content; + } + return " ".repeat(indent * INDENT_SPACES) + content.substring(index); + } + + private static ArrayList splitLines(final String text) { + final ArrayList lines = new ArrayList<>(); + final byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + int charIndex = 0; + int byteIndex = 0; + int lineCharStart = 0; + int lineByteStart = 0; + while (charIndex < text.length()) { + final int codePoint = text.codePointAt(charIndex); + final int charWidth = Character.charCount(codePoint); + final int byteWidth = utf8Length(codePoint); + if (codePoint == '\n') { + lines.add(new SourceLine( + text.substring(lineCharStart, charIndex), + "\n", + lineByteStart, + byteIndex)); + charIndex += charWidth; + byteIndex += byteWidth; + lineCharStart = charIndex; + lineByteStart = byteIndex; + continue; + } + if (codePoint == '\r' && charIndex + charWidth < text.length() && text.codePointAt(charIndex + charWidth) == '\n') { + lines.add(new SourceLine( + text.substring(lineCharStart, charIndex), + "\r\n", + lineByteStart, + byteIndex)); + charIndex += charWidth + 1; + byteIndex += byteWidth + 1; + lineCharStart = charIndex; + lineByteStart = byteIndex; + continue; + } + charIndex += charWidth; + byteIndex += byteWidth; + } + if (lineCharStart < text.length()) { + lines.add(new SourceLine( + text.substring(lineCharStart), + "", + lineByteStart, + bytes.length)); + } + if (lines.isEmpty()) { + lines.add(new SourceLine(text, "", 0, bytes.length)); + } + return lines; + } + + private static int utf8Length(final int codePoint) { + if (codePoint <= 0x7F) { + return 1; + } + if (codePoint <= 0x7FF) { + return 2; + } + if (codePoint <= 0xFFFF) { + return 3; + } + return 4; + } + + private record SourceLine(String content, String ending, int startByte, int endByte) { + } +} diff --git a/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentFormatterTest.java b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentFormatterTest.java new file mode 100644 index 00000000..a74768fc --- /dev/null +++ b/prometeu-compiler/frontends/prometeu-frontend-pbs/src/test/java/p/studio/compiler/pbs/PbsDocumentFormatterTest.java @@ -0,0 +1,53 @@ +package p.studio.compiler.pbs; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PbsDocumentFormatterTest { + + @Test + void indentsOnlyWhereBraceOrParenNestingChanges() { + final String source = """ + fn length(value: int) -> int { + return value; + } + fn other() -> int { + return 1; + } + """; + + final String formatted = PbsDocumentFormatter.format(source); + + assertEquals(""" + fn length(value: int) -> int { + return value; + } + fn other() -> int { + return 1; + } + """, formatted); + assertTrue(formatted.indexOf("fn length") < formatted.indexOf("fn other")); + assertFalse(formatted.contains("\nreturn value;\nreturn 1;")); + } + + @Test + void leavesDocTextBlockInteriorAndTrailingCommentUntouched() { + final String source = """ + [Doc(markdown = \"\"\" + hello + \"\"\")] + fn length() -> int { // keep + return 1; + } + """; + + final String formatted = PbsDocumentFormatter.format(source); + + assertTrue(formatted.contains(" hello\n")); + assertTrue(formatted.contains("fn length() -> int { // keep")); + assertTrue(formatted.contains("\n return 1;\n")); + } +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java index 48a33e00..cc10e7f8 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java @@ -184,6 +184,14 @@ public interface FrontendLanguageService { return selectionRanges(request, offsets); } + default boolean formattingSupported() { + return false; + } + + default java.util.Optional formatDocument(final FrontendDocumentRequest request) { + return java.util.Optional.empty(); + } + default boolean codeActionsSupported() { return false; } diff --git a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/services/FrontendLanguageServiceTest.java b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/services/FrontendLanguageServiceTest.java index dfb2d63d..52cf4c0a 100644 --- a/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/services/FrontendLanguageServiceTest.java +++ b/prometeu-compiler/prometeu-frontend-api/src/test/java/p/studio/compiler/services/FrontendLanguageServiceTest.java @@ -36,6 +36,8 @@ class FrontendLanguageServiceTest { assertFalse(service.foldingRangesSupported()); assertTrue(service.foldingRanges(request).isEmpty()); assertTrue(service.foldingRanges(request, null).isEmpty()); + assertFalse(service.formattingSupported()); + assertTrue(service.formatDocument(request).isEmpty()); assertFalse(service.selectionRangesSupported()); assertTrue(service.selectionRanges(request, List.of(0)).isEmpty()); assertTrue(service.selectionRanges(request, null, List.of(0)).isEmpty()); diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineFormatting.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineFormatting.java new file mode 100644 index 00000000..8a0ef588 --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineFormatting.java @@ -0,0 +1,13 @@ +package p.studio.lsp.messages; + +import java.util.List; + +public record BaselineFormatting(List edits) { + public BaselineFormatting { + edits = edits == null ? List.of() : List.copyOf(edits); + } + + public static BaselineFormatting empty() { + return new BaselineFormatting(List.of()); + } +} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java index ef9eba20..93fab3a1 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineServerDescription.java @@ -19,6 +19,7 @@ public record BaselineServerDescription( boolean documentLinksSupported, boolean foldingRangesSupported, boolean selectionRangesSupported, + boolean formattingSupported, List semanticTokenTypes, List semanticHostProjections, List visualThemes, diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTextEdit.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTextEdit.java new file mode 100644 index 00000000..194cae4a --- /dev/null +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/BaselineTextEdit.java @@ -0,0 +1,18 @@ +package p.studio.lsp.messages; + +import java.util.Objects; + +public record BaselineTextEdit( + int startLine, + int startCharacter, + int endLine, + int endCharacter, + String newText) { + + public BaselineTextEdit { + if (startLine < 0 || startCharacter < 0 || endLine < 0 || endCharacter < 0) { + throw new IllegalArgumentException("text edit coordinates must not be negative"); + } + newText = Objects.requireNonNull(newText, "newText"); + } +} diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/LanguageServiceBridge.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/LanguageServiceBridge.java index f704f8df..0f6bd3ad 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/LanguageServiceBridge.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/LanguageServiceBridge.java @@ -11,6 +11,7 @@ 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.BaselineFormatting; import p.studio.lsp.messages.BaselineSelectionRanges; import p.studio.lsp.messages.DocumentPosition; import p.studio.lsp.messages.BaselinePrepareRename; @@ -72,6 +73,10 @@ public interface LanguageServiceBridge { return BaselineFoldingRanges.empty(); } + default BaselineFormatting formatDocument(LspProjectContext context, String documentUri, String text) { + return BaselineFormatting.empty(); + } + default BaselineSelectionRanges selectionRanges( LspProjectContext context, String documentUri, diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java index cf85b739..9815b19d 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java @@ -69,6 +69,9 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg final boolean selectionRangesSupported = languageService .map(FrontendLanguageService::selectionRangesSupported) .orElse(false); + final boolean formattingSupported = languageService + .map(FrontendLanguageService::formattingSupported) + .orElse(false); return new BaselineServerDescription( "Prometeu Studio LSP", "0.1.0", @@ -85,6 +88,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg documentLinksSupported, foldingRangesSupported, selectionRangesSupported, + formattingSupported, presentation.semanticKeys(), presentation.hostProjections().stream().map(this::mapSemanticHostProjection).toList(), presentation.themes().stream().map(this::mapVisualTheme).toList(), @@ -403,6 +407,34 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg .orElseGet(BaselineDocumentLinks::empty); } + @Override + public BaselineFormatting formatDocument( + final LspProjectContext context, + final String documentUri, + final String text) { + Objects.requireNonNull(context, "context"); + final var languageService = frontendProvider(context).languageService(); + if (languageService.isEmpty() || !languageService.orElseThrow().formattingSupported()) { + return BaselineFormatting.empty(); + } + final String effectiveText = text == null ? "" : text; + final String formatted = languageService.orElseThrow().formatDocument(new FrontendDocumentRequest( + context.projectRoot(), + normalizeDocumentPath(documentUri), + effectiveText)).orElse(effectiveText); + if (formatted.equals(effectiveText)) { + return BaselineFormatting.empty(); + } + final DocumentPosition end = new DocumentPositionMapper(effectiveText) + .positionOf(effectiveText.getBytes(StandardCharsets.UTF_8).length); + return new BaselineFormatting(List.of(new BaselineTextEdit( + 0, + 0, + end.line(), + end.character(), + formatted))); + } + @Override public BaselineFoldingRanges foldingRanges( final LspProjectContext context, diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/PrometeuTextDocumentService.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/PrometeuTextDocumentService.java index a8b8ea89..5f56dafa 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/PrometeuTextDocumentService.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/PrometeuTextDocumentService.java @@ -169,6 +169,14 @@ public final class PrometeuTextDocumentService implements TextDocumentService { return CompletableFuture.completedFuture(protocolMessageMapper.mapRename(result)); } + @Override + public CompletableFuture> formatting(final DocumentFormattingParams params) { + final String uri = params.getTextDocument().getUri(); + final String text = documentTextByUri.get(uri); + return CompletableFuture.completedFuture(protocolMessageMapper.mapFormatting( + languageServiceBridge.formatDocument(project, uri, text))); + } + @Override public CompletableFuture> foldingRange(final FoldingRangeRequestParams params) { final String uri = params.getTextDocument().getUri(); diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapper.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapper.java index e1bdf41c..95dd0311 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapper.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapper.java @@ -64,6 +64,9 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper { if (description.selectionRangesSupported()) { capabilities.setSelectionRangeProvider(true); } + if (description.formattingSupported()) { + capabilities.setDocumentFormattingProvider(true); + } final SemanticTokensWithRegistrationOptions semanticTokens = new SemanticTokensWithRegistrationOptions(); semanticTokens.setLegend(new SemanticTokensLegend(description.semanticTokenTypes(), List.of())); semanticTokens.setFull(true); @@ -187,6 +190,22 @@ public final class Lsp4jProtocolMessageMapper implements ProtocolMessageMapper { return List.copyOf(mapped); } + @Override + public List mapFormatting(final BaselineFormatting formatting) { + if (formatting == null || formatting.edits().isEmpty()) { + return List.of(); + } + final ArrayList mapped = new ArrayList<>(); + for (final BaselineTextEdit edit : formatting.edits()) { + mapped.add(new TextEdit( + new Range( + new Position(edit.startLine(), edit.startCharacter()), + new Position(edit.endLine(), edit.endCharacter())), + edit.newText())); + } + return List.copyOf(mapped); + } + @Override public List mapFoldingRanges(final BaselineFoldingRanges ranges) { if (ranges == null || ranges.ranges().isEmpty()) { diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/ProtocolMessageMapper.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/ProtocolMessageMapper.java index b7de3e24..3e530bf4 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/ProtocolMessageMapper.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/protocol/mapping/ProtocolMessageMapper.java @@ -12,6 +12,7 @@ import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DocumentLink; import org.eclipse.lsp4j.DocumentSymbol; import org.eclipse.lsp4j.FoldingRange; +import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.SelectionRange; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Range; @@ -24,6 +25,7 @@ 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.BaselineFormatting; import p.studio.lsp.messages.BaselineSelectionRanges; import p.studio.lsp.messages.BaselineDocumentSymbols; import p.studio.lsp.messages.BaselineWorkspaceSymbols; @@ -73,6 +75,10 @@ public interface ProtocolMessageMapper { return List.of(); } + default List mapFormatting(BaselineFormatting formatting) { + return List.of(); + } + default List mapSelectionRanges(BaselineSelectionRanges ranges) { return List.of(); } diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java index a9983601..285103fe 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java @@ -78,6 +78,7 @@ 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.formatDocument(context, documentUri, "fn main() {}").edits().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()); @@ -778,6 +779,7 @@ class CompilerLanguageServiceBridgeTest { assertTrue(description.documentLinksSupported()); assertTrue(description.foldingRangesSupported()); assertTrue(description.selectionRangesSupported()); + assertTrue(description.formattingSupported()); assertEquals(1, description.semanticHostProjections().size()); assertEquals("vscode", description.semanticHostProjections().getFirst().hostId()); assertTrue(description.semanticHostProjections().getFirst().tokenProjections().stream() diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java index 97d4aaee..68be0806 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/PrometeuLanguageServerTest.java @@ -108,6 +108,7 @@ class PrometeuLanguageServerTest { false, false, false, + false, List.of(), List.of(new BaselineSemanticHostProjection( "vscode", diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapperTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapperTest.java index 284cee4b..8238ea69 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapperTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/protocol/mapping/Lsp4jProtocolMessageMapperTest.java @@ -60,6 +60,7 @@ final class Lsp4jProtocolMessageMapperTest { true, true, true, + true, List.of("demo-keyword"), List.of(new BaselineSemanticHostProjection( "vscode", @@ -90,6 +91,9 @@ final class Lsp4jProtocolMessageMapperTest { assertEquals(Boolean.FALSE, result.getCapabilities().getDocumentLinkProvider().getResolveProvider()); assertEquals(Boolean.TRUE, result.getCapabilities().getFoldingRangeProvider().getLeft()); assertEquals(Boolean.TRUE, result.getCapabilities().getSelectionRangeProvider().getLeft()); + assertEquals(Boolean.TRUE, result.getCapabilities().getDocumentFormattingProvider().getLeft()); + assertNull(result.getCapabilities().getDocumentRangeFormattingProvider()); + assertNull(result.getCapabilities().getDocumentOnTypeFormattingProvider()); final var experimental = assertInstanceOf(Map.class, result.getCapabilities().getExperimental()); final var semanticPayload = assertInstanceOf(Map.class, experimental.get("prometeuSemanticHostProjections")); @@ -343,6 +347,7 @@ final class Lsp4jProtocolMessageMapperTest { documentLinksSupported, documentLinksSupported, documentLinksSupported, + documentLinksSupported, List.of("demo-keyword"), List.of(new BaselineSemanticHostProjection( "vscode",