PBS highlight colors
This commit is contained in:
parent
5bbd753ba0
commit
f368ed94d2
@ -574,7 +574,7 @@ final class PbsDeclarationParser {
|
||||
}
|
||||
|
||||
private PbsToken consumeCallableName() {
|
||||
if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) {
|
||||
if (cursor.check(PbsTokenKind.IDENTIFIER)) {
|
||||
return cursor.advance();
|
||||
}
|
||||
final var token = cursor.peek();
|
||||
|
||||
@ -39,7 +39,7 @@ final class PbsExprParserContext {
|
||||
}
|
||||
|
||||
PbsToken consumeMemberName(final String message) {
|
||||
if (cursor.check(PbsTokenKind.IDENTIFIER) || cursor.check(PbsTokenKind.ERROR)) {
|
||||
if (cursor.check(PbsTokenKind.IDENTIFIER)) {
|
||||
return cursor.advance();
|
||||
}
|
||||
final var token = cursor.peek();
|
||||
|
||||
@ -30,7 +30,7 @@ declare service Log
|
||||
LowLog.write(3, msg);
|
||||
}
|
||||
|
||||
fn error(msg: str) -> void
|
||||
fn failure(msg: str) -> void
|
||||
{
|
||||
LowLog.write(4, msg);
|
||||
}
|
||||
@ -55,7 +55,7 @@ declare service Log
|
||||
LowLog.write_tag(3, tag, msg);
|
||||
}
|
||||
|
||||
fn error(tag: int, msg: str) -> void
|
||||
fn failure(tag: int, msg: str) -> void
|
||||
{
|
||||
LowLog.write_tag(4, tag, msg);
|
||||
}
|
||||
|
||||
@ -173,7 +173,7 @@ class PbsParserTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldParseServiceAndMemberNamesUsingErrorKeyword() {
|
||||
void shouldRejectErrorKeywordAsCallableAndMemberName() {
|
||||
final var source = """
|
||||
declare service Log {
|
||||
fn error(msg: str) -> void { return; }
|
||||
@ -181,20 +181,17 @@ class PbsParserTest {
|
||||
}
|
||||
|
||||
fn run() -> void {
|
||||
Log.error("oops");
|
||||
Log.error(7, "oops");
|
||||
Log.failure("oops");
|
||||
Log.failure(7, "oops");
|
||||
return;
|
||||
}
|
||||
""";
|
||||
final var diagnostics = DiagnosticSink.empty();
|
||||
final var fileId = new FileId(0);
|
||||
|
||||
final PbsAst.File ast = PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
PbsParser.parse(PbsLexer.lex(source, fileId, diagnostics), fileId, diagnostics);
|
||||
|
||||
assertTrue(diagnostics.isEmpty(), "Parser should accept 'error' as callable/member name");
|
||||
final var service = assertInstanceOf(PbsAst.ServiceDecl.class, ast.topDecls().getFirst());
|
||||
assertEquals(2, service.methods().size());
|
||||
assertEquals("error", service.methods().getFirst().name());
|
||||
assertFalse(diagnostics.isEmpty(), "Parser should reject reserved keyword 'error' as callable/member name");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -219,6 +219,7 @@ final class LspServiceImplTest {
|
||||
pub fn helper() -> void;
|
||||
pub fn helper_result() -> result<MyError> int;
|
||||
pub error MyError;
|
||||
pub service Log;
|
||||
""");
|
||||
Files.writeString(src.resolve("helper.pbs"), "fn helper() -> void {}\n");
|
||||
Files.writeString(src.resolve("helper_result.pbs"), """
|
||||
@ -230,6 +231,11 @@ final class LspServiceImplTest {
|
||||
return ok(1);
|
||||
}
|
||||
""");
|
||||
Files.writeString(src.resolve("log.pbs"), """
|
||||
declare service Log {
|
||||
fn failure(message: string) -> void;
|
||||
}
|
||||
""");
|
||||
return tempDir;
|
||||
}
|
||||
|
||||
|
||||
@ -11,16 +11,19 @@ final class EditorDocumentHighlightingRouter {
|
||||
final EditorOpenFileBuffer fileBuffer,
|
||||
final EditorDocumentPresentation presentation,
|
||||
final LspAnalyzeDocumentResult analysis) {
|
||||
final var localHighlighting = presentation.highlight(fileBuffer.content());
|
||||
if (fileBuffer.frontendDocument()
|
||||
&& analysis != null
|
||||
&& presentation.supportsSemanticHighlighting()
|
||||
&& !analysis.semanticHighlights().isEmpty()) {
|
||||
return new EditorDocumentHighlightingResult(
|
||||
EditorDocumentHighlightOwner.LSP,
|
||||
EditorDocumentSemanticHighlighting.highlight(fileBuffer.content(), analysis.semanticHighlights()));
|
||||
EditorDocumentSemanticHighlighting.overlay(
|
||||
localHighlighting,
|
||||
EditorDocumentSemanticHighlighting.highlight(fileBuffer.content(), analysis.semanticHighlights())));
|
||||
}
|
||||
return new EditorDocumentHighlightingResult(
|
||||
EditorDocumentHighlightOwner.LOCAL,
|
||||
presentation.highlight(fileBuffer.content()));
|
||||
localHighlighting);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
import p.studio.compiler.FrontendRegistryService;
|
||||
import p.studio.compiler.models.FrontendSemanticPresentationSpec;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
import p.studio.vfs.messages.VfsDocumentTypeIds;
|
||||
import p.studio.workspaces.editor.syntaxhighlight.EditorDocumentSyntaxHighlighting;
|
||||
@ -55,21 +56,19 @@ final class EditorDocumentPresentationRegistry {
|
||||
private EditorDocumentPresentation frontendPresentation(
|
||||
final String normalizedTypeId,
|
||||
final LspSemanticPresentationDTO semanticPresentation) {
|
||||
if (semanticPresentation == null) {
|
||||
return new EditorDocumentPresentation(
|
||||
normalizedTypeId,
|
||||
java.util.List.of(),
|
||||
java.util.List.of(),
|
||||
EditorDocumentSyntaxHighlighting.plainText());
|
||||
}
|
||||
final var frontendSpec = FrontendRegistryService.getFrontendSpec(normalizedTypeId).orElseThrow();
|
||||
final FrontendSemanticPresentationSpec frontendPresentationSpec = frontendSpec.getSemanticPresentation();
|
||||
final java.util.List<String> stylesheetUrls = resolveFrontendStylesheets(
|
||||
semanticPresentation == null ? java.util.List.of() : semanticPresentation.resources(),
|
||||
frontendPresentationSpec.resources());
|
||||
final java.util.List<String> semanticKeys = semanticPresentation == null
|
||||
? frontendPresentationSpec.semanticKeys()
|
||||
: semanticPresentation.semanticKeys();
|
||||
return new EditorDocumentPresentation(
|
||||
normalizedTypeId,
|
||||
semanticPresentation.resources().stream()
|
||||
.map(EditorDocumentPresentationRegistry::resourceStylesheet)
|
||||
.flatMap(Optional::stream)
|
||||
.toList(),
|
||||
semanticPresentation.semanticKeys(),
|
||||
EditorDocumentSyntaxHighlighting.plainText());
|
||||
stylesheetUrls,
|
||||
semanticKeys,
|
||||
frontendSyntaxHighlighting(normalizedTypeId));
|
||||
}
|
||||
|
||||
private String normalize(final String typeId) {
|
||||
@ -93,6 +92,29 @@ final class EditorDocumentPresentationRegistry {
|
||||
return Optional.of(resource.toExternalForm());
|
||||
}
|
||||
|
||||
private static java.util.List<String> resolveFrontendStylesheets(
|
||||
final java.util.List<String> primaryResources,
|
||||
final java.util.List<String> fallbackResources) {
|
||||
final java.util.List<String> primary = primaryResources.stream()
|
||||
.map(EditorDocumentPresentationRegistry::resourceStylesheet)
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
if (!primary.isEmpty()) {
|
||||
return primary;
|
||||
}
|
||||
return fallbackResources.stream()
|
||||
.map(EditorDocumentPresentationRegistry::resourceStylesheet)
|
||||
.flatMap(Optional::stream)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static EditorDocumentSyntaxHighlighting frontendSyntaxHighlighting(final String normalizedTypeId) {
|
||||
return switch (normalizedTypeId) {
|
||||
case "pbs" -> EditorDocumentSyntaxHighlighting.pbs();
|
||||
default -> EditorDocumentSyntaxHighlighting.plainText();
|
||||
};
|
||||
}
|
||||
|
||||
private static String normalizeResourcePath(final String resourcePath) {
|
||||
final String normalized = Objects.requireNonNull(resourcePath, "resourcePath").trim();
|
||||
if (normalized.isEmpty()) {
|
||||
|
||||
@ -7,6 +7,7 @@ import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
public final class EditorDocumentSemanticHighlighting {
|
||||
@ -40,4 +41,24 @@ public final class EditorDocumentSemanticHighlighting {
|
||||
}
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
public static StyleSpans<Collection<String>> overlay(
|
||||
final StyleSpans<Collection<String>> baseHighlighting,
|
||||
final StyleSpans<Collection<String>> semanticHighlighting) {
|
||||
return baseHighlighting.overlay(semanticHighlighting, EditorDocumentSemanticHighlighting::mergeStyles);
|
||||
}
|
||||
|
||||
private static Collection<String> mergeStyles(
|
||||
final Collection<String> baseStyles,
|
||||
final Collection<String> semanticStyles) {
|
||||
if (baseStyles.isEmpty()) {
|
||||
return semanticStyles;
|
||||
}
|
||||
if (semanticStyles.isEmpty()) {
|
||||
return baseStyles;
|
||||
}
|
||||
final LinkedHashSet<String> merged = new LinkedHashSet<>(baseStyles);
|
||||
merged.addAll(semanticStyles);
|
||||
return List.copyOf(merged);
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,10 @@ public record EditorDocumentSyntaxHighlighting(
|
||||
return EditorDocumentSyntaxHighlightingBash.BASH;
|
||||
}
|
||||
|
||||
public static EditorDocumentSyntaxHighlighting pbs() {
|
||||
return EditorDocumentSyntaxHighlightingPbs.PBS;
|
||||
}
|
||||
|
||||
public StyleSpans<Collection<String>> highlight(final String content) {
|
||||
final StyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>();
|
||||
if (tokenPattern == null) {
|
||||
|
||||
@ -46,7 +46,8 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
analysis);
|
||||
|
||||
assertEquals(EditorDocumentHighlightOwner.LSP, result.owner());
|
||||
assertEquals(List.of("editor-semantic-pbs-keyword"), List.copyOf(result.styleSpans().getStyleSpan(0).getStyle()));
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-keyword"));
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-function"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -80,7 +81,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void frontendDocumentsDegradeToLocalWhenSemanticPresentationResourcesAreUnavailable() {
|
||||
void frontendDocumentsFallbackToLocalPbsSyntaxWhenSemanticPresentationResourcesAreUnavailable() {
|
||||
final EditorDocumentPresentationRegistry registry = new EditorDocumentPresentationRegistry();
|
||||
final EditorOpenFileBuffer fileBuffer = new EditorOpenFileBuffer(
|
||||
Path.of("/tmp/example/src/main.pbs"),
|
||||
@ -108,8 +109,9 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
registry.resolve("pbs", analysis.semanticPresentation()),
|
||||
analysis);
|
||||
|
||||
assertEquals(EditorDocumentHighlightOwner.LOCAL, result.owner());
|
||||
assertTrue(result.styleSpans().getStyleSpan(0).getStyle().isEmpty());
|
||||
assertEquals(EditorDocumentHighlightOwner.LSP, result.owner());
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-keyword"));
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-function"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -145,6 +147,41 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-service"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void frontendDocumentsKeepLocalSyntaxForRangesWithoutSemanticCoverage() {
|
||||
final EditorDocumentPresentationRegistry registry = new EditorDocumentPresentationRegistry();
|
||||
final EditorOpenFileBuffer fileBuffer = new EditorOpenFileBuffer(
|
||||
Path.of("/tmp/example/src/main.pbs"),
|
||||
"main.pbs",
|
||||
"pbs",
|
||||
"fn main() -> void { Game.tick(1); }",
|
||||
"LF",
|
||||
true,
|
||||
VfsDocumentAccessMode.READ_ONLY,
|
||||
false);
|
||||
|
||||
final LspAnalyzeDocumentResult analysis = new LspAnalyzeDocumentResult(
|
||||
new LspSessionStateDTO(true, List.of("highlight")),
|
||||
new LspSemanticPresentationDTO(
|
||||
List.of("pbs-service"),
|
||||
List.of("/themes/pbs/semantic-highlighting.css")),
|
||||
List.of(),
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(19, 23), "pbs-service")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
fileBuffer,
|
||||
registry.resolve("pbs", analysis.semanticPresentation()),
|
||||
analysis);
|
||||
|
||||
assertEquals(EditorDocumentHighlightOwner.LSP, result.owner());
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-keyword"));
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-service"));
|
||||
assertTrue(containsStyle(result.styleSpans(), "editor-semantic-pbs-function"));
|
||||
}
|
||||
|
||||
private boolean containsStyle(
|
||||
final org.fxmisc.richtext.model.StyleSpans<Collection<String>> styleSpans,
|
||||
final String styleClass) {
|
||||
|
||||
@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
final class EditorDocumentPresentationRegistryTest {
|
||||
@ -24,7 +25,7 @@ final class EditorDocumentPresentationRegistryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingFrontendResourcesDegradeToPlainFrontendPresentation() {
|
||||
void missingFrontendResourcesFallbackToFrontendOwnedPresentationResources() {
|
||||
final EditorDocumentPresentation presentation = registry.resolve(
|
||||
"pbs",
|
||||
new LspSemanticPresentationDTO(
|
||||
@ -33,7 +34,8 @@ final class EditorDocumentPresentationRegistryTest {
|
||||
|
||||
assertEquals("pbs", presentation.styleKey());
|
||||
assertEquals(java.util.List.of("pbs-keyword"), presentation.semanticKeys());
|
||||
assertTrue(presentation.stylesheetUrls().isEmpty());
|
||||
assertEquals(1, presentation.stylesheetUrls().size());
|
||||
assertFalse(presentation.highlight("fn main() -> void {}").getStyleSpan(0).getStyle().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -28,7 +28,7 @@ fn frame() -> void
|
||||
if (loading_handle == -1) {
|
||||
let t = Assets.load(assets.ui.atlas2, 3);
|
||||
if (t.status != 0) {
|
||||
Log.error("load failed");
|
||||
Log.failure("load failed");
|
||||
} else {
|
||||
loading_handle = t.loading_handle;
|
||||
Log.info("state: loading");
|
||||
@ -38,12 +38,12 @@ fn frame() -> void
|
||||
if (s == 2) {
|
||||
let commit_status = Assets.commit(loading_handle);
|
||||
if (commit_status != 0) {
|
||||
Log.error("commit failed");
|
||||
Log.failure("commit failed");
|
||||
}
|
||||
} else if (s == 3) {
|
||||
let sprite_status = Gfx.set_sprite(3, 10, 150, 150, 0, 0, true, false, false, 1);
|
||||
if (sprite_status != 0) {
|
||||
Log.error("set_sprite failed");
|
||||
Log.failure("set_sprite failed");
|
||||
}
|
||||
} else {
|
||||
Log.info("state: waiting");
|
||||
@ -100,7 +100,7 @@ fn frame() -> void
|
||||
}
|
||||
else if (total == 50)
|
||||
{
|
||||
Log.error("50 is the magic number!");
|
||||
Log.failure("50 is the magic number!");
|
||||
}
|
||||
else if (total == 15)
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user