editor with write capability

This commit is contained in:
bQUARKz 2026-04-04 12:42:14 +01:00
parent 40a4d656ca
commit 770aa6a387
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 97 additions and 63 deletions

View File

@ -10,6 +10,7 @@ public enum PbsSemanticKind {
STRING("pbs-string"),
NUMBER("pbs-number"),
LITERAL("pbs-literal"),
LIFECYCLE("pbs-lifecycle"),
KEYWORD("pbs-keyword"),
OPERATOR("pbs-operator"),
PUNCTUATION("pbs-punctuation"),

View File

@ -14,6 +14,10 @@
-fx-fill: #569cd6;
}
.editor-workspace-code-area-type-pbs .text.editor-semantic-pbs-lifecycle {
-fx-fill: #ef50c0;
}
.editor-workspace-code-area-type-pbs .text.editor-semantic-pbs-function {
-fx-fill: #f2c14e;
}

View File

@ -104,11 +104,12 @@ public final class SemanticIndex {
final List<PbsToken> tokens,
final Map<String, LspSymbolKind> visibleKindsByName) {
final List<LspHighlightSpanDTO> highlights = new ArrayList<>();
for (final PbsToken token : tokens) {
for (int index = 0; index < tokens.size(); index++) {
final PbsToken token = tokens.get(index);
if (token.kind() == PbsTokenKind.EOF) {
continue;
}
final String semanticKey = semanticKey(token, visibleKindsByName);
final String semanticKey = semanticKey(tokens, index, visibleKindsByName);
if (semanticKey == null || semanticKey.isBlank()) {
continue;
}
@ -120,14 +121,50 @@ public final class SemanticIndex {
}
private String semanticKey(
final PbsToken token,
final List<PbsToken> tokens,
final int tokenIndex,
final Map<String, LspSymbolKind> visibleKindsByName) {
final PbsToken token = tokens.get(tokenIndex);
if (isLifecycleAttributeToken(tokens, tokenIndex)) {
return PbsSemanticKind.LIFECYCLE.semanticKey();
}
final PbsSemanticKind semanticKind = token.kind() == p.studio.compiler.pbs.lexer.PbsTokenKind.IDENTIFIER
? semanticKindForIdentifier(token.lexeme(), visibleKindsByName)
: PbsSemanticKind.forToken(token);
return semanticKind == null ? null : semanticKind.semanticKey();
}
private boolean isLifecycleAttributeToken(final List<PbsToken> tokens, final int tokenIndex) {
if (tokenIndex < 0 || tokenIndex >= tokens.size()) {
return false;
}
final PbsToken token = tokens.get(tokenIndex);
return switch (token.kind()) {
case LEFT_BRACKET -> hasLifecycleAttributeWindow(tokens, tokenIndex, tokenIndex + 1, tokenIndex + 2);
case IDENTIFIER -> hasLifecycleAttributeWindow(tokens, tokenIndex - 1, tokenIndex, tokenIndex + 1);
case RIGHT_BRACKET -> hasLifecycleAttributeWindow(tokens, tokenIndex - 2, tokenIndex - 1, tokenIndex);
default -> false;
};
}
private boolean hasLifecycleAttributeWindow(
final List<PbsToken> tokens,
final int leftBracketIndex,
final int identifierIndex,
final int rightBracketIndex) {
if (leftBracketIndex < 0 || rightBracketIndex >= tokens.size()) {
return false;
}
return tokens.get(leftBracketIndex).kind() == PbsTokenKind.LEFT_BRACKET
&& tokens.get(identifierIndex).kind() == PbsTokenKind.IDENTIFIER
&& isLifecycleAttributeName(tokens.get(identifierIndex).lexeme())
&& tokens.get(rightBracketIndex).kind() == PbsTokenKind.RIGHT_BRACKET;
}
private boolean isLifecycleAttributeName(final String lexeme) {
return "Init".equals(lexeme) || "Frame".equals(lexeme);
}
private PbsSemanticKind semanticKindForIdentifier(
final String lexeme,
final Map<String, LspSymbolKind> visibleKindsByName) {

View File

@ -109,10 +109,7 @@ final class EditorDocumentPresentationRegistry {
}
private static EditorDocumentSyntaxHighlighting frontendSyntaxHighlighting(final String normalizedTypeId) {
return switch (normalizedTypeId) {
case "pbs" -> EditorDocumentSyntaxHighlighting.pbs();
default -> EditorDocumentSyntaxHighlighting.plainText();
};
return EditorDocumentSyntaxHighlighting.plainText();
}
private static String normalizeResourcePath(final String resourcePath) {

View File

@ -93,6 +93,7 @@ public final class EditorTabStrip extends HBox {
final var fileBuffer = openFiles.get(index);
final var tabButton = new Button(fileBuffer.tabLabel());
final var tabLabel = new Label(fileBuffer.tabLabel());
final var dirtyIndicator = new StackPane();
final var closeChip = new StackPane();
final var closeIcon = new SVGPath();
final var tabContent = new HBox();
@ -107,6 +108,7 @@ public final class EditorTabStrip extends HBox {
: "editor-workspace-tab-button-editable");
tabLabel.getStyleClass().add("editor-workspace-tab-label");
tabLabel.setTextOverrun(OverrunStyle.ELLIPSIS);
dirtyIndicator.getStyleClass().add("editor-workspace-tab-dirty-indicator");
closeChip.getStyleClass().add("editor-workspace-tab-close-chip");
closeIcon.setContent("M 3 3 L 9 9 M 9 3 L 3 9");
closeIcon.getStyleClass().add("editor-workspace-tab-close-icon");
@ -115,10 +117,12 @@ public final class EditorTabStrip extends HBox {
tabContent.getStyleClass().add("editor-workspace-tab-content");
tabContent.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(tabLabel, Priority.ALWAYS);
tabContent.getChildren().addAll(tabLabel, closeChip);
dirtyIndicator.setManaged(fileBuffer.dirty());
dirtyIndicator.setVisible(fileBuffer.dirty());
tabContent.getChildren().addAll(tabLabel, dirtyIndicator, closeChip);
tabButton.setText(null);
tabButton.setGraphic(tabContent);
applyTabMetrics(tabButton, tabLabel, closeChip);
applyTabMetrics(tabButton, tabLabel, dirtyIndicator, closeChip);
if (fileBuffer.path().equals(activePath)) {
tabButton.getStyleClass().add("editor-workspace-tab-button-active");
}
@ -180,7 +184,11 @@ public final class EditorTabStrip extends HBox {
return Math.max(1, (int) Math.floor(usableWidth / TAB_WIDTH_HINT));
}
private void applyTabMetrics(final Button button, final Label tabLabel, final StackPane closeChip) {
private void applyTabMetrics(
final Button button,
final Label tabLabel,
final StackPane dirtyIndicator,
final StackPane closeChip) {
button.setMinWidth(TAB_WIDTH_HINT);
button.setPrefWidth(TAB_WIDTH_HINT);
button.setMaxWidth(TAB_WIDTH_HINT);
@ -191,6 +199,12 @@ public final class EditorTabStrip extends HBox {
tabLabel.setMinWidth(0);
tabLabel.setPrefWidth(TAB_WIDTH_HINT - 30);
tabLabel.setMaxWidth(Double.MAX_VALUE);
dirtyIndicator.setMinWidth(8);
dirtyIndicator.setPrefWidth(8);
dirtyIndicator.setMaxWidth(8);
dirtyIndicator.setMinHeight(8);
dirtyIndicator.setPrefHeight(8);
dirtyIndicator.setMaxHeight(8);
closeChip.setMinWidth(14);
closeChip.setPrefWidth(14);
closeChip.setMaxWidth(14);

View File

@ -24,21 +24,9 @@ public record EditorDocumentSyntaxHighlighting(
}
}
public static EditorDocumentSyntaxHighlighting plainText() {
return EditorDocumentSyntaxHighlightingPlainText.PLAIN_TEXT;
}
public static EditorDocumentSyntaxHighlighting json() {
return EditorDocumentSyntaxHighlightingJson.JSON;
}
public static EditorDocumentSyntaxHighlighting bash() {
return EditorDocumentSyntaxHighlightingBash.BASH;
}
public static EditorDocumentSyntaxHighlighting pbs() {
return EditorDocumentSyntaxHighlightingPbs.PBS;
}
public static EditorDocumentSyntaxHighlighting plainText() { return EditorDocumentSyntaxHighlightingPlainText.PLAIN_TEXT; }
public static EditorDocumentSyntaxHighlighting json() { return EditorDocumentSyntaxHighlightingJson.JSON; }
public static EditorDocumentSyntaxHighlighting bash() { return EditorDocumentSyntaxHighlightingBash.BASH; }
public StyleSpans<Collection<String>> highlight(final String content) {
final StyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>();

View File

@ -1,32 +0,0 @@
package p.studio.workspaces.editor.syntaxhighlight;
import java.util.List;
import java.util.regex.Pattern;
public final class EditorDocumentSyntaxHighlightingPbs {
public static final EditorDocumentSyntaxHighlighting PBS = new EditorDocumentSyntaxHighlighting(
Pattern.compile(
"(?<COMMENT>//[^\\n\\r]*)"
+ "|(?<STRING>\"(?:\\\\.|[^\"\\\\])*\")"
+ "|(?<NUMBER>\\b\\d+(?:\\.\\d+)?b?\\b)"
+ "|(?<LITERAL>\\b(?:true|false|none)\\b)"
+ "|(?<KEYWORD>\\b(?: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)\\b)"
+ "|(?<FUNCTION>\\b[A-Za-z_][A-Za-z0-9_]*\\b(?=\\s*\\())"
+ "|(?<OPERATOR>\\+\\=|\\-\\=|\\*\\=|/\\=|%\\=|\\=\\=|!\\=|<\\=|>\\=|&&|\\|\\||\\->|[+\\-*/%!=<>?])"
+ "|(?<PUNCTUATION>\\.|\\.\\.|[(){}\\[\\],:;@])"),
List.of(
new EditorDocumentHighlightToken("COMMENT", "editor-semantic-pbs-comment"),
new EditorDocumentHighlightToken("STRING", "editor-semantic-pbs-string"),
new EditorDocumentHighlightToken("NUMBER", "editor-semantic-pbs-number"),
new EditorDocumentHighlightToken("LITERAL", "editor-semantic-pbs-literal"),
new EditorDocumentHighlightToken("KEYWORD", "editor-semantic-pbs-keyword"),
new EditorDocumentHighlightToken("FUNCTION", "editor-semantic-pbs-function"),
new EditorDocumentHighlightToken("OPERATOR", "editor-semantic-pbs-operator"),
new EditorDocumentHighlightToken("PUNCTUATION", "editor-semantic-pbs-punctuation")));
private EditorDocumentSyntaxHighlightingPbs() {
}
}

View File

@ -592,6 +592,13 @@
-fx-font-size: 12px;
}
.editor-workspace-tab-dirty-indicator {
-fx-background-color: #d9e3ef;
-fx-background-radius: 999;
-fx-border-radius: 999;
-fx-opacity: 0.9;
}
.editor-workspace-tab-close-chip {
-fx-alignment: center;
-fx-background-color: #131820;
@ -627,6 +634,10 @@
-fx-text-fill: #d9dee5;
}
.editor-workspace-tab-button-read-only .editor-workspace-tab-dirty-indicator {
-fx-background-color: #d9dee5;
}
.editor-workspace-tab-button-read-only .editor-workspace-tab-close-icon {
-fx-stroke: #d9dee5;
}
@ -646,6 +657,10 @@
-fx-text-fill: #eff4fa;
}
.editor-workspace-tab-button-read-only:hover .editor-workspace-tab-dirty-indicator {
-fx-background-color: #eff4fa;
}
.editor-workspace-tab-button-read-only:hover .editor-workspace-tab-close-chip {
-fx-background-color: #1c232c;
-fx-border-color: #6b7989;
@ -688,6 +703,10 @@
-fx-text-fill: #e8f6eb;
}
.editor-workspace-tab-button-editable .editor-workspace-tab-dirty-indicator {
-fx-background-color: #b9f0c7;
}
.editor-workspace-tab-button-editable .editor-workspace-tab-close-icon {
-fx-stroke: #e8f6eb;
}
@ -707,6 +726,10 @@
-fx-text-fill: #f4fff5;
}
.editor-workspace-tab-button-editable:hover .editor-workspace-tab-dirty-indicator {
-fx-background-color: #f4fff5;
}
.editor-workspace-tab-button-editable:hover .editor-workspace-tab-close-chip {
-fx-background-color: #192720;
-fx-border-color: #789886;
@ -739,6 +762,10 @@
-fx-font-weight: bold;
}
.editor-workspace-tab-button-active .editor-workspace-tab-dirty-indicator {
-fx-background-color: #ffffff;
}
.editor-workspace-tab-button-active .editor-workspace-tab-close-icon {
-fx-stroke: #ffffff;
}
@ -841,10 +868,9 @@
}
.editor-workspace-command-button {
-fx-min-height: 34;
-fx-pref-height: 34;
-fx-max-height: 34;
-fx-padding: 0 14 0 14;
-fx-alignment: center;
-fx-pref-width: 118px;
-fx-min-width: 118px;
}
.editor-workspace-warning {

View File

@ -4,7 +4,6 @@ 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 {
@ -35,7 +34,7 @@ final class EditorDocumentPresentationRegistryTest {
assertEquals("pbs", presentation.styleKey());
assertEquals(java.util.List.of("pbs-keyword"), presentation.semanticKeys());
assertEquals(1, presentation.stylesheetUrls().size());
assertFalse(presentation.highlight("fn main() -> void {}").getStyleSpan(0).getStyle().isEmpty());
assertTrue(presentation.highlight("fn main() -> void {}").getStyleSpan(0).getStyle().isEmpty());
}
@Test