implements PLN-0027 frontend semantic presentation contract and lsp descriptor

This commit is contained in:
bQUARKz 2026-04-02 15:11:40 +01:00
parent 78758c1023
commit de9782c16e
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
14 changed files with 253 additions and 32 deletions

View File

@ -1,6 +1,8 @@
package p.studio.compiler; package p.studio.compiler;
import p.studio.compiler.models.FrontendSpec; import p.studio.compiler.models.FrontendSpec;
import p.studio.compiler.models.FrontendSemanticPresentationSpec;
import p.studio.compiler.pbs.PbsSemanticKind;
import p.studio.utilities.structures.ReadOnlySet; import p.studio.utilities.structures.ReadOnlySet;
import java.util.List; import java.util.List;
@ -12,5 +14,8 @@ public class PBSDefinitions {
.allowedExtensions(ReadOnlySet.from("pbs", "barrel")) .allowedExtensions(ReadOnlySet.from("pbs", "barrel"))
.sourceRoots(ReadOnlySet.from("src")) .sourceRoots(ReadOnlySet.from("src"))
.stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1))) .stdlibVersions(List.of(FrontendSpec.Stdlib.asDefault(1)))
.semanticPresentation(new FrontendSemanticPresentationSpec(
PbsSemanticKind.semanticKeys(),
List.of("/themes/pbs/semantic-highlighting.css")))
.build(); .build();
} }

View File

@ -0,0 +1,56 @@
package p.studio.compiler.pbs;
import p.studio.compiler.pbs.lexer.PbsToken;
import java.util.Arrays;
import java.util.List;
public enum PbsSemanticKind {
COMMENT("pbs-comment"),
STRING("pbs-string"),
NUMBER("pbs-number"),
LITERAL("pbs-literal"),
KEYWORD("pbs-keyword"),
OPERATOR("pbs-operator"),
PUNCTUATION("pbs-punctuation"),
FUNCTION("pbs-function"),
TYPE("pbs-type"),
BINDING("pbs-binding"),
IDENTIFIER("pbs-identifier");
private final String semanticKey;
PbsSemanticKind(final String semanticKey) {
this.semanticKey = semanticKey;
}
public String semanticKey() {
return semanticKey;
}
public static List<String> semanticKeys() {
return Arrays.stream(values())
.map(PbsSemanticKind::semanticKey)
.toList();
}
public static PbsSemanticKind forToken(final PbsToken token) {
return switch (token.kind()) {
case COMMENT -> COMMENT;
case STRING_LITERAL -> STRING;
case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> NUMBER;
case TRUE, FALSE, NONE -> LITERAL;
case IMPORT, FROM, AS, SERVICE, HOST, FN, APPLY, BIND, NEW, IMPLEMENTS, USING, CTOR,
DECLARE, LET, CONST, GLOBAL, STRUCT, CONTRACT, ERROR, ENUM, CALLBACK, BUILTIN,
SELF, THIS, PUB, MUT, MOD, TYPE, IF, ELSE, SWITCH, DEFAULT, FOR, UNTIL, STEP,
WHILE, BREAK, CONTINUE, RETURN, VOID, OPTIONAL, RESULT, SOME, OK, ERR, HANDLE,
AND, OR, NOT, SPAWN, YIELD, SLEEP, MATCH -> KEYWORD;
case PLUS, MINUS, STAR, SLASH, PERCENT, BANG, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL,
SLASH_EQUAL, PERCENT_EQUAL, BANG_EQUAL, EQUAL, EQUAL_EQUAL, LESS, LESS_EQUAL,
GREATER, GREATER_EQUAL, AND_AND, OR_OR, ARROW, QUESTION -> OPERATOR;
case LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, LEFT_BRACKET, RIGHT_BRACKET,
COMMA, COLON, SEMICOLON, AT, DOT, DOT_DOT -> PUNCTUATION;
case IDENTIFIER, EOF -> null;
};
}
}

View File

@ -0,0 +1,61 @@
.editor-workspace-code-area-type-pbs {
-fx-highlight-fill: #1b3244;
}
.editor-workspace-code-area-type-pbs .text {
-fx-fill: #edf4fb;
}
.editor-workspace-code-area-type-pbs .lineno {
-fx-text-fill: #71859a;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-keyword {
-fx-fill: #8dc7ff;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-function {
-fx-fill: #f0cb79;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-type {
-fx-fill: #9ddba8;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-binding {
-fx-fill: #ffb1c8;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-string {
-fx-fill: #e2c48c;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-number {
-fx-fill: #c4e58a;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-comment {
-fx-fill: #6f8192;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-literal {
-fx-fill: #c8a2ff;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-operator {
-fx-fill: #dbe6f1;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-punctuation {
-fx-fill: #adc1d4;
}
.editor-workspace-code-area-type-pbs .text.editor-syntax-pbs-identifier {
-fx-fill: #edf4fb;
}
.editor-workspace-status-chip-type-pbs {
-fx-background-color: #152432;
-fx-border-color: #4d8db9;
-fx-text-fill: #e8f5ff;
}

View File

@ -0,0 +1,33 @@
package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test;
import p.studio.compiler.PBSDefinitions;
import java.net.URL;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
final class PbsSemanticPresentationContractTest {
@Test
void shouldPublishSemanticPresentationContractThroughFrontendSpec() {
final var presentation = PBSDefinitions.PBS.getSemanticPresentation();
assertFalse(presentation.semanticKeys().isEmpty());
assertEquals(PbsSemanticKind.semanticKeys(), presentation.semanticKeys());
assertEquals(1, presentation.resources().size());
assertEquals("/themes/pbs/semantic-highlighting.css", presentation.resources().getFirst());
}
@Test
void shouldPublishResolvableSemanticPresentationResources() {
final var resourcePath = PBSDefinitions.PBS.getSemanticPresentation().resources().getFirst();
final URL resource = PBSDefinitions.class.getResource(resourcePath);
assertNotNull(resource, resourcePath);
assertTrue(resource.toExternalForm().endsWith("themes/pbs/semantic-highlighting.css"));
}
}

View File

@ -0,0 +1,18 @@
package p.studio.compiler.models;
import java.util.List;
import java.util.Objects;
public record FrontendSemanticPresentationSpec(
List<String> semanticKeys,
List<String> resources) {
public FrontendSemanticPresentationSpec {
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
resources = List.copyOf(Objects.requireNonNull(resources, "resources"));
}
public static FrontendSemanticPresentationSpec empty() {
return new FrontendSemanticPresentationSpec(List.of(), List.of());
}
}

View File

@ -15,6 +15,8 @@ public class FrontendSpec {
private final boolean caseSensitive; private final boolean caseSensitive;
@Builder.Default @Builder.Default
private final List<Stdlib> stdlibVersions = List.of(); private final List<Stdlib> stdlibVersions = List.of();
@Builder.Default
private final FrontendSemanticPresentationSpec semanticPresentation = FrontendSemanticPresentationSpec.empty();
public String toString() { public String toString() {
return String.format("FrontendSpec(language=%s)", languageId); return String.format("FrontendSpec(language=%s)", languageId);

View File

@ -0,0 +1,14 @@
package p.studio.lsp.dtos;
import java.util.List;
import java.util.Objects;
public record LspSemanticPresentationDTO(
List<String> semanticKeys,
List<String> resources) {
public LspSemanticPresentationDTO {
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
resources = List.copyOf(Objects.requireNonNull(resources, "resources"));
}
}

View File

@ -2,6 +2,7 @@ package p.studio.lsp.messages;
import p.studio.lsp.dtos.LspDiagnosticDTO; import p.studio.lsp.dtos.LspDiagnosticDTO;
import p.studio.lsp.dtos.LspHighlightSpanDTO; import p.studio.lsp.dtos.LspHighlightSpanDTO;
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
import p.studio.lsp.dtos.LspSessionStateDTO; import p.studio.lsp.dtos.LspSessionStateDTO;
import p.studio.lsp.dtos.LspSymbolDTO; import p.studio.lsp.dtos.LspSymbolDTO;
@ -10,6 +11,7 @@ import java.util.Objects;
public record LspAnalyzeDocumentResult( public record LspAnalyzeDocumentResult(
LspSessionStateDTO sessionState, LspSessionStateDTO sessionState,
LspSemanticPresentationDTO semanticPresentation,
List<LspDiagnosticDTO> diagnostics, List<LspDiagnosticDTO> diagnostics,
List<LspHighlightSpanDTO> semanticHighlights, List<LspHighlightSpanDTO> semanticHighlights,
List<LspSymbolDTO> documentSymbols, List<LspSymbolDTO> documentSymbols,
@ -17,6 +19,7 @@ public record LspAnalyzeDocumentResult(
public LspAnalyzeDocumentResult { public LspAnalyzeDocumentResult {
Objects.requireNonNull(sessionState, "sessionState"); Objects.requireNonNull(sessionState, "sessionState");
Objects.requireNonNull(semanticPresentation, "semanticPresentation");
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics")); diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights")); semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights"));
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols")); documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));

View File

@ -16,6 +16,7 @@ class LspSemanticAnalyseService {
final var normalizedRequestedDocument = normalize(request.documentPath()); final var normalizedRequestedDocument = normalize(request.documentPath());
return new LspAnalyzeDocumentResult( return new LspAnalyzeDocumentResult(
new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")), new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")),
session.semanticPresentation(),
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()), session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),

View File

@ -5,6 +5,7 @@ import p.studio.compiler.messages.*;
import p.studio.compiler.models.AnalysisSnapshot; import p.studio.compiler.models.AnalysisSnapshot;
import p.studio.compiler.models.BuilderPipelineContext; import p.studio.compiler.models.BuilderPipelineContext;
import p.studio.compiler.models.FrontendSpec; import p.studio.compiler.models.FrontendSpec;
import p.studio.compiler.models.FrontendSemanticPresentationSpec;
import p.studio.compiler.models.SourceHandle; import p.studio.compiler.models.SourceHandle;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsLexer; import p.studio.compiler.pbs.lexer.PbsLexer;
@ -18,6 +19,7 @@ import p.studio.compiler.workspaces.stages.LoadSourcesPipelineStage;
import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage; import p.studio.compiler.workspaces.stages.ResolveDepsPipelineStage;
import p.studio.lsp.dtos.LspDiagnosticDTO; import p.studio.lsp.dtos.LspDiagnosticDTO;
import p.studio.lsp.dtos.LspRangeDTO; import p.studio.lsp.dtos.LspRangeDTO;
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
import p.studio.lsp.messages.*; import p.studio.lsp.messages.*;
import p.studio.lsp.models.AnalysisRuntimeSnapshot; import p.studio.lsp.models.AnalysisRuntimeSnapshot;
import p.studio.lsp.models.SemanticIndex; import p.studio.lsp.models.SemanticIndex;
@ -137,6 +139,7 @@ final class LspSemanticReadPhase {
if (snapshot.fileTable() == null) { if (snapshot.fileTable() == null) {
return new SemanticSession( return new SemanticSession(
normalize(requestedDocumentPath), normalize(requestedDocumentPath),
semanticPresentation(snapshot.frontendSpec()),
diagnosticsByDocument, diagnosticsByDocument,
Map.of(), Map.of(),
Map.of(), Map.of(),
@ -157,6 +160,7 @@ final class LspSemanticReadPhase {
} }
return new SemanticSession( return new SemanticSession(
normalize(requestedDocumentPath), normalize(requestedDocumentPath),
semanticPresentation(snapshot.frontendSpec()),
diagnosticsByDocument, diagnosticsByDocument,
semanticIndex.semanticHighlightsByDocument(), semanticIndex.semanticHighlightsByDocument(),
semanticIndex.documentSymbolsByDocument(), semanticIndex.documentSymbolsByDocument(),
@ -169,6 +173,11 @@ final class LspSemanticReadPhase {
return frontendSpec.getAllowedExtensions().contains(sourceHandle.getExtension()); return frontendSpec.getAllowedExtensions().contains(sourceHandle.getExtension());
} }
private static LspSemanticPresentationDTO semanticPresentation(final FrontendSpec frontendSpec) {
final FrontendSemanticPresentationSpec presentation = frontendSpec.getSemanticPresentation();
return new LspSemanticPresentationDTO(presentation.semanticKeys(), presentation.resources());
}
private static Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument( private static Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument(
final List<BuildingIssue> issues, final List<BuildingIssue> issues,
final AnalysisSnapshot snapshot, final AnalysisSnapshot snapshot,

View File

@ -3,6 +3,7 @@ package p.studio.lsp.messages;
import p.studio.compiler.pbs.lexer.PbsToken; import p.studio.compiler.pbs.lexer.PbsToken;
import p.studio.lsp.dtos.LspDiagnosticDTO; import p.studio.lsp.dtos.LspDiagnosticDTO;
import p.studio.lsp.dtos.LspHighlightSpanDTO; import p.studio.lsp.dtos.LspHighlightSpanDTO;
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
import p.studio.lsp.dtos.LspSymbolDTO; import p.studio.lsp.dtos.LspSymbolDTO;
import java.nio.file.Path; import java.nio.file.Path;
@ -11,6 +12,7 @@ import java.util.Map;
public record SemanticSession( public record SemanticSession(
Path requestedDocumentPath, Path requestedDocumentPath,
LspSemanticPresentationDTO semanticPresentation,
Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument, Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument,
Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument, Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument,
Map<Path, List<LspSymbolDTO>> documentSymbolsByDocument, Map<Path, List<LspSymbolDTO>> documentSymbolsByDocument,

View File

@ -1,5 +1,6 @@
package p.studio.lsp.models; package p.studio.lsp.models;
import p.studio.compiler.pbs.PbsSemanticKind;
import p.studio.compiler.pbs.ast.PbsAst; import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.compiler.pbs.lexer.PbsToken; import p.studio.compiler.pbs.lexer.PbsToken;
import p.studio.compiler.pbs.lexer.PbsTokenKind; import p.studio.compiler.pbs.lexer.PbsTokenKind;
@ -71,39 +72,25 @@ public final class SemanticIndex {
private String semanticKey( private String semanticKey(
final PbsToken token, final PbsToken token,
final Map<String, List<LspSymbolDTO>> indexedSymbolsByName) { final Map<String, List<LspSymbolDTO>> indexedSymbolsByName) {
return switch (token.kind()) { final PbsSemanticKind semanticKind = token.kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.IDENTIFIER
case COMMENT -> "fe-comment"; ? semanticKindForIdentifier(token.lexeme(), indexedSymbolsByName)
case STRING_LITERAL -> "fe-string"; : PbsSemanticKind.forToken(token);
case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> "fe-number"; return semanticKind == null ? null : semanticKind.semanticKey();
case TRUE, FALSE, NONE -> "fe-literal";
case IDENTIFIER -> semanticKeyForIdentifier(token.lexeme(), indexedSymbolsByName);
case IMPORT, FROM, AS, SERVICE, HOST, FN, APPLY, BIND, NEW, IMPLEMENTS, USING, CTOR,
DECLARE, LET, CONST, GLOBAL, STRUCT, CONTRACT, ERROR, ENUM, CALLBACK, BUILTIN,
SELF, THIS, PUB, MUT, MOD, TYPE, IF, ELSE, SWITCH, DEFAULT, FOR, UNTIL, STEP,
WHILE, BREAK, CONTINUE, RETURN, VOID, OPTIONAL, RESULT, SOME, OK, ERR, HANDLE,
AND, OR, NOT -> "fe-keyword";
case PLUS, MINUS, STAR, SLASH, PERCENT, BANG, PLUS_EQUAL, MINUS_EQUAL, STAR_EQUAL,
SLASH_EQUAL, PERCENT_EQUAL, BANG_EQUAL, EQUAL, EQUAL_EQUAL, LESS, LESS_EQUAL,
GREATER, GREATER_EQUAL, AND_AND, OR_OR, ARROW, QUESTION -> "fe-operator";
case LEFT_PAREN, RIGHT_PAREN, LEFT_BRACE, RIGHT_BRACE, LEFT_BRACKET, RIGHT_BRACKET,
COMMA, COLON, SEMICOLON, AT, DOT, DOT_DOT -> "fe-punctuation";
default -> null;
};
} }
private String semanticKeyForIdentifier( private PbsSemanticKind semanticKindForIdentifier(
final String lexeme, final String lexeme,
final Map<String, List<LspSymbolDTO>> indexedSymbolsByName) { final Map<String, List<LspSymbolDTO>> indexedSymbolsByName) {
final List<LspSymbolDTO> symbols = indexedSymbolsByName.getOrDefault(lexeme, List.of()); final List<LspSymbolDTO> symbols = indexedSymbolsByName.getOrDefault(lexeme, List.of());
if (symbols.isEmpty()) { if (symbols.isEmpty()) {
return "fe-identifier"; return PbsSemanticKind.IDENTIFIER;
} }
final LspSymbolKind kind = symbols.getFirst().kind(); final LspSymbolKind kind = symbols.getFirst().kind();
return switch (kind) { return switch (kind) {
case FUNCTION, METHOD, CALLBACK -> "fe-callable"; case FUNCTION, METHOD, CALLBACK -> PbsSemanticKind.FUNCTION;
case STRUCT, CONTRACT, HOST, BUILTIN_TYPE, SERVICE, ERROR, ENUM -> "fe-type"; case STRUCT, CONTRACT, HOST, BUILTIN_TYPE, SERVICE, ERROR, ENUM -> PbsSemanticKind.TYPE;
case GLOBAL, CONST -> "fe-binding"; case GLOBAL, CONST -> PbsSemanticKind.BINDING;
default -> "fe-identifier"; default -> PbsSemanticKind.IDENTIFIER;
}; };
} }

View File

@ -22,6 +22,13 @@ final class LspServiceImplTest {
@TempDir @TempDir
Path tempDir; Path tempDir;
private static final String OVERLAY_SOURCE = """
fn helper_call() -> void
{
helper();
}
""";
@Test @Test
void analyzeDocumentUsesVfsOverlayForRequestedDocumentAndFilesystemFallbackForClosedFiles() throws Exception { void analyzeDocumentUsesVfsOverlayForRequestedDocumentAndFilesystemFallbackForClosedFiles() throws Exception {
final Path projectRoot = createProject(); final Path projectRoot = createProject();
@ -31,18 +38,17 @@ final class LspServiceImplTest {
Files.writeString(helperFile, "fn helper() -> void {}\n"); Files.writeString(helperFile, "fn helper() -> void {}\n");
final VfsProjectDocument delegate = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot)); final VfsProjectDocument delegate = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot));
final String overlaySource = """
fn helper_call() -> void
{
helper();
}
""";
final LspService service = new LspServiceImpl( final LspService service = new LspServiceImpl(
new LspProjectContext("Example", "pbs", projectRoot), new LspProjectContext("Example", "pbs", projectRoot),
new OverlayVfsProjectDocument(delegate, mainFile, overlaySource)); new OverlayVfsProjectDocument(delegate, mainFile, OVERLAY_SOURCE));
final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile)); final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile));
assertEquals("pbs-function", semanticKeyForLexeme(analysis, OVERLAY_SOURCE, "helper_call"));
assertEquals("pbs-function", semanticKeyForLexeme(analysis, OVERLAY_SOURCE, "helper"));
assertEquals(List.of("/themes/pbs/semantic-highlighting.css"), analysis.semanticPresentation().resources());
assertTrue(analysis.semanticPresentation().semanticKeys().contains("pbs-function"));
assertTrue( assertTrue(
analysis.documentSymbols().stream().anyMatch(symbol -> symbol.name().equals("helper_call")), analysis.documentSymbols().stream().anyMatch(symbol -> symbol.name().equals("helper_call")),
analysis.toString()); analysis.toString());
@ -51,7 +57,7 @@ final class LspServiceImplTest {
symbol.name().equals("helper") && symbol.documentPath().equals(normalize(helperFile))), symbol.name().equals("helper") && symbol.documentPath().equals(normalize(helperFile))),
analysis.toString()); analysis.toString());
final int offset = overlaySource.indexOf("helper();"); final int offset = OVERLAY_SOURCE.indexOf("helper();");
final var definition = service.definition(new LspDefinitionRequest(mainFile, offset)); final var definition = service.definition(new LspDefinitionRequest(mainFile, offset));
final List<LspDefinitionTargetDTO> targets = definition.targets(); final List<LspDefinitionTargetDTO> targets = definition.targets();
@ -110,6 +116,27 @@ final class LspServiceImplTest {
} }
} }
private static String semanticKeyForLexeme(
final p.studio.lsp.messages.LspAnalyzeDocumentResult analysis,
final String source,
final String lexeme) {
return analysis.semanticHighlights().stream()
.filter(highlight -> lexeme.equals(spanContent(source, highlight.range().startOffset(), highlight.range().endOffset())))
.map(p.studio.lsp.dtos.LspHighlightSpanDTO::semanticKey)
.findFirst()
.orElseThrow();
}
private static String spanContent(
final String source,
final int start,
final int end) {
if (start < 0 || end > source.length() || start >= end) {
return "";
}
return source.substring(start, end);
}
private static final class OverlayVfsProjectDocument implements VfsProjectDocument { private static final class OverlayVfsProjectDocument implements VfsProjectDocument {
private final VfsProjectDocument delegate; private final VfsProjectDocument delegate;
private final Path overlayPath; private final Path overlayPath;

View File

@ -2,6 +2,7 @@ package p.studio.workspaces.editor;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import p.studio.lsp.dtos.LspHighlightSpanDTO; import p.studio.lsp.dtos.LspHighlightSpanDTO;
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
import p.studio.lsp.dtos.LspRangeDTO; import p.studio.lsp.dtos.LspRangeDTO;
import p.studio.lsp.dtos.LspSessionStateDTO; import p.studio.lsp.dtos.LspSessionStateDTO;
import p.studio.lsp.messages.LspAnalyzeDocumentResult; import p.studio.lsp.messages.LspAnalyzeDocumentResult;
@ -28,6 +29,7 @@ final class EditorDocumentHighlightingRouterTest {
final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult( final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult(
new LspSessionStateDTO(true, List.of("highlight")), new LspSessionStateDTO(true, List.of("highlight")),
new LspSemanticPresentationDTO(List.of(), List.of()),
List.of(), List.of(),
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "fe-keyword")), List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "fe-keyword")),
List.of(), List.of(),
@ -56,6 +58,7 @@ final class EditorDocumentHighlightingRouterTest {
final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult( final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult(
new LspSessionStateDTO(true, List.of("highlight")), new LspSessionStateDTO(true, List.of("highlight")),
new LspSemanticPresentationDTO(List.of(), List.of()),
List.of(), List.of(),
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 1), "fe-punctuation")), List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 1), "fe-punctuation")),
List.of(), List.of(),