implements PLN-0025
This commit is contained in:
parent
bcc89dcfbd
commit
b64be2a9a1
@ -11,4 +11,4 @@
|
||||
{"type":"discussion","id":"DSC-0010","status":"done","ticket":"studio-code-editor-workspace-foundations","title":"Establish Code Editor workspace foundations in Studio without LSP","created_at":"2026-03-30","updated_at":"2026-03-31","tags":["studio","editor","workspace","multi-frontend","lsp-deferred"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0026","file":"discussion/lessons/DSC-0010-studio-code-editor-workspace-foundations/LSN-0026-read-only-editor-foundations-and-semantic-deferral.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31"}]}
|
||||
{"type":"discussion","id":"DSC-0011","status":"done","ticket":"compiler-analyze-compile-build-pipeline-split","title":"Split compiler pipeline into analyze, compile, and build entrypoints","created_at":"2026-03-30","updated_at":"2026-03-30","tags":["compiler","pipeline","artifacts","build","analysis"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"discussion/lessons/DSC-0011-compiler-analyze-compile-build-pipeline-split/LSN-0025-compiler-pipeline-entrypoints-and-result-boundaries.md","status":"done","created_at":"2026-03-30","updated_at":"2026-03-30"}]}
|
||||
{"type":"discussion","id":"DSC-0012","status":"done","ticket":"studio-editor-document-vfs-boundary","title":"Definir um boundary de VFS documental para tree/view/open files no Code Editor do Studio","created_at":"2026-03-31","updated_at":"2026-03-31","tags":["studio","editor","workspace","vfs","filesystem","boundary"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0027","file":"discussion/lessons/DSC-0012-studio-editor-document-vfs-boundary/LSN-0027-project-document-vfs-and-session-owned-editor-boundary.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31"}]}
|
||||
{"type":"discussion","id":"DSC-0013","status":"open","ticket":"studio-editor-write-wave-supported-non-frontend-files","title":"Definir a wave inicial de edicao no Code Editor apenas para arquivos aceitos e nao relacionados ao FE","created_at":"2026-03-31","updated_at":"2026-03-31","tags":["studio","editor","workspace","write","read-only","vfs","frontend-boundary"],"agendas":[{"id":"AGD-0013","file":"AGD-0013-studio-editor-write-wave-supported-non-frontend-files.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31"},{"id":"AGD-0014","file":"AGD-0014-studio-editor-frontend-edit-rights.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31"}],"decisions":[{"id":"DEC-0010","file":"DEC-0010-studio-controlled-non-frontend-editor-write-wave.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0013"},{"id":"DEC-0011","file":"DEC-0011-studio-frontend-read-only-minimum-lsp-phase.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0014"}],"plans":[{"id":"PLN-0019","file":"PLN-0019-propagate-dec-0010-into-studio-and-vfs-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0020","file":"PLN-0020-build-dec-0010-vfs-access-policy-and-save-core.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0021","file":"PLN-0021-integrate-dec-0010-editor-write-ui-and-workflow.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0022","file":"PLN-0022-propagate-dec-0011-into-studio-vfs-and-lsp-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0023","file":"PLN-0023-scaffold-flat-packed-prometeu-lsp-api-and-session-seams.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0024","file":"PLN-0024-implement-read-only-fe-diagnostics-symbols-and-definition.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0025","file":"PLN-0025-implement-fe-semantic-highlight-consumption.md","status":"review","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0013","status":"open","ticket":"studio-editor-write-wave-supported-non-frontend-files","title":"Definir a wave inicial de edicao no Code Editor apenas para arquivos aceitos e nao relacionados ao FE","created_at":"2026-03-31","updated_at":"2026-03-31","tags":["studio","editor","workspace","write","read-only","vfs","frontend-boundary"],"agendas":[{"id":"AGD-0013","file":"AGD-0013-studio-editor-write-wave-supported-non-frontend-files.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31"},{"id":"AGD-0014","file":"AGD-0014-studio-editor-frontend-edit-rights.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31"}],"decisions":[{"id":"DEC-0010","file":"DEC-0010-studio-controlled-non-frontend-editor-write-wave.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0013"},{"id":"DEC-0011","file":"DEC-0011-studio-frontend-read-only-minimum-lsp-phase.md","status":"accepted","created_at":"2026-03-31","updated_at":"2026-03-31","ref_agenda":"AGD-0014"}],"plans":[{"id":"PLN-0019","file":"PLN-0019-propagate-dec-0010-into-studio-and-vfs-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0020","file":"PLN-0020-build-dec-0010-vfs-access-policy-and-save-core.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0021","file":"PLN-0021-integrate-dec-0010-editor-write-ui-and-workflow.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0010"]},{"id":"PLN-0022","file":"PLN-0022-propagate-dec-0011-into-studio-vfs-and-lsp-specs.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0023","file":"PLN-0023-scaffold-flat-packed-prometeu-lsp-api-and-session-seams.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0024","file":"PLN-0024-implement-read-only-fe-diagnostics-symbols-and-definition.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]},{"id":"PLN-0025","file":"PLN-0025-implement-fe-semantic-highlight-consumption.md","status":"done","created_at":"2026-03-31","updated_at":"2026-03-31","ref_decisions":["DEC-0011"]}],"lessons":[]}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
id: PLN-0025
|
||||
ticket: studio-editor-write-wave-supported-non-frontend-files
|
||||
title: Implement FE semantic highlight and editor consumption for the read-only LSP phase
|
||||
status: review
|
||||
status: done
|
||||
created: 2026-03-31
|
||||
completed:
|
||||
completed: 2026-03-31
|
||||
tags: [studio, lsp, highlight, frontend, editor]
|
||||
---
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
package p.lsp.dtos;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record PrometeuLspHighlightSpanDTO(
|
||||
PrometeuLspRangeDTO range,
|
||||
String semanticKey) {
|
||||
|
||||
public PrometeuLspHighlightSpanDTO {
|
||||
range = Objects.requireNonNull(range, "range");
|
||||
semanticKey = Objects.requireNonNull(semanticKey, "semanticKey");
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package p.lsp.messages;
|
||||
|
||||
import p.lsp.dtos.PrometeuLspDiagnosticDTO;
|
||||
import p.lsp.dtos.PrometeuLspHighlightSpanDTO;
|
||||
import p.lsp.dtos.PrometeuLspSessionStateDTO;
|
||||
import p.lsp.dtos.PrometeuLspSymbolDTO;
|
||||
|
||||
@ -10,12 +11,14 @@ import java.util.Objects;
|
||||
public record PrometeuLspAnalyzeDocumentResult(
|
||||
PrometeuLspSessionStateDTO sessionState,
|
||||
List<PrometeuLspDiagnosticDTO> diagnostics,
|
||||
List<PrometeuLspHighlightSpanDTO> semanticHighlights,
|
||||
List<PrometeuLspSymbolDTO> documentSymbols,
|
||||
List<PrometeuLspSymbolDTO> workspaceSymbols) {
|
||||
|
||||
public PrometeuLspAnalyzeDocumentResult {
|
||||
Objects.requireNonNull(sessionState, "sessionState");
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights"));
|
||||
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));
|
||||
workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols"));
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ final class PrometeuLspSemanticReadPhase {
|
||||
return new PrometeuLspAnalyzeDocumentResult(
|
||||
new PrometeuLspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")),
|
||||
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.workspaceSymbols());
|
||||
}
|
||||
@ -142,6 +143,7 @@ final class PrometeuLspSemanticReadPhase {
|
||||
normalize(requestedDocumentPath),
|
||||
diagnosticsByDocument,
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
List.of(),
|
||||
Map.of(),
|
||||
Map.of());
|
||||
@ -160,6 +162,7 @@ final class PrometeuLspSemanticReadPhase {
|
||||
return new SemanticSession(
|
||||
normalize(requestedDocumentPath),
|
||||
diagnosticsByDocument,
|
||||
semanticIndex.semanticHighlightsByDocument(),
|
||||
semanticIndex.documentSymbolsByDocument(),
|
||||
semanticIndex.workspaceSymbols(),
|
||||
semanticIndex.symbolsByName(),
|
||||
@ -281,6 +284,7 @@ final class PrometeuLspSemanticReadPhase {
|
||||
private record SemanticSession(
|
||||
Path requestedDocumentPath,
|
||||
Map<Path, List<PrometeuLspDiagnosticDTO>> diagnosticsByDocument,
|
||||
Map<Path, List<PrometeuLspHighlightSpanDTO>> semanticHighlightsByDocument,
|
||||
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument,
|
||||
List<PrometeuLspSymbolDTO> workspaceSymbols,
|
||||
Map<String, List<PrometeuLspSymbolDTO>> symbolsByName,
|
||||
@ -293,6 +297,7 @@ final class PrometeuLspSemanticReadPhase {
|
||||
}
|
||||
|
||||
private static final class SemanticIndex {
|
||||
private final Map<Path, List<PrometeuLspHighlightSpanDTO>> semanticHighlightsByDocument = new LinkedHashMap<>();
|
||||
private final Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument = new LinkedHashMap<>();
|
||||
private final List<PrometeuLspSymbolDTO> workspaceSymbols = new ArrayList<>();
|
||||
private final Map<String, List<PrometeuLspSymbolDTO>> symbolsByName = new LinkedHashMap<>();
|
||||
@ -317,9 +322,70 @@ final class PrometeuLspSemanticReadPhase {
|
||||
symbolsByName.computeIfAbsent(child.name(), ignored -> new ArrayList<>()).add(child);
|
||||
}
|
||||
}
|
||||
semanticHighlightsByDocument.put(
|
||||
normalizedDocumentPath,
|
||||
buildSemanticHighlights(tokens, symbolsByName));
|
||||
documentSymbolsByDocument.put(normalizedDocumentPath, List.copyOf(documentSymbols));
|
||||
}
|
||||
|
||||
private List<PrometeuLspHighlightSpanDTO> buildSemanticHighlights(
|
||||
final List<PbsToken> tokens,
|
||||
final Map<String, List<PrometeuLspSymbolDTO>> indexedSymbolsByName) {
|
||||
final List<PrometeuLspHighlightSpanDTO> highlights = new ArrayList<>();
|
||||
for (final PbsToken token : tokens) {
|
||||
if (token.kind() == PbsTokenKind.EOF) {
|
||||
continue;
|
||||
}
|
||||
final String semanticKey = semanticKey(token, indexedSymbolsByName);
|
||||
if (semanticKey == null || semanticKey.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
highlights.add(new PrometeuLspHighlightSpanDTO(
|
||||
new PrometeuLspRangeDTO(token.start(), token.end()),
|
||||
semanticKey));
|
||||
}
|
||||
return List.copyOf(highlights);
|
||||
}
|
||||
|
||||
private String semanticKey(
|
||||
final PbsToken token,
|
||||
final Map<String, List<PrometeuLspSymbolDTO>> indexedSymbolsByName) {
|
||||
return switch (token.kind()) {
|
||||
case COMMENT -> "fe-comment";
|
||||
case STRING_LITERAL -> "fe-string";
|
||||
case INT_LITERAL, FLOAT_LITERAL, BOUNDED_LITERAL -> "fe-number";
|
||||
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(
|
||||
final String lexeme,
|
||||
final Map<String, List<PrometeuLspSymbolDTO>> indexedSymbolsByName) {
|
||||
final List<PrometeuLspSymbolDTO> symbols = indexedSymbolsByName.getOrDefault(lexeme, List.of());
|
||||
if (symbols.isEmpty()) {
|
||||
return "fe-identifier";
|
||||
}
|
||||
final PrometeuLspSymbolKindDTO kind = symbols.get(0).kind();
|
||||
return switch (kind) {
|
||||
case FUNCTION, METHOD, CALLBACK -> "fe-callable";
|
||||
case STRUCT, CONTRACT, HOST, BUILTIN_TYPE, SERVICE, ERROR, ENUM -> "fe-type";
|
||||
case GLOBAL, CONST -> "fe-binding";
|
||||
default -> "fe-identifier";
|
||||
};
|
||||
}
|
||||
|
||||
private PrometeuLspSymbolDTO symbolForTopDecl(
|
||||
final Path documentPath,
|
||||
final PbsAst.TopDecl topDecl) {
|
||||
@ -407,6 +473,10 @@ final class PrometeuLspSemanticReadPhase {
|
||||
children);
|
||||
}
|
||||
|
||||
Map<Path, List<PrometeuLspHighlightSpanDTO>> semanticHighlightsByDocument() {
|
||||
return Map.copyOf(semanticHighlightsByDocument);
|
||||
}
|
||||
|
||||
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument() {
|
||||
return Map.copyOf(documentSymbolsByDocument);
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
enum EditorDocumentHighlightOwner {
|
||||
LOCAL,
|
||||
LSP
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
import org.fxmisc.richtext.model.StyleSpans;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
record EditorDocumentHighlightingResult(
|
||||
EditorDocumentHighlightOwner owner,
|
||||
StyleSpans<Collection<String>> styleSpans) {
|
||||
|
||||
EditorDocumentHighlightingResult {
|
||||
owner = Objects.requireNonNull(owner, "owner");
|
||||
styleSpans = Objects.requireNonNull(styleSpans, "styleSpans");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
import p.lsp.messages.PrometeuLspAnalyzeDocumentResult;
|
||||
import p.studio.workspaces.editor.syntaxhighlight.EditorDocumentSemanticHighlighting;
|
||||
|
||||
final class EditorDocumentHighlightingRouter {
|
||||
private EditorDocumentHighlightingRouter() {
|
||||
}
|
||||
|
||||
static EditorDocumentHighlightingResult route(
|
||||
final EditorOpenFileBuffer fileBuffer,
|
||||
final EditorDocumentPresentation presentation,
|
||||
final PrometeuLspAnalyzeDocumentResult analysis) {
|
||||
if (fileBuffer.frontendDocument()
|
||||
&& analysis != null
|
||||
&& !analysis.semanticHighlights().isEmpty()) {
|
||||
return new EditorDocumentHighlightingResult(
|
||||
EditorDocumentHighlightOwner.LSP,
|
||||
EditorDocumentSemanticHighlighting.highlight(fileBuffer.content(), analysis.semanticHighlights()));
|
||||
}
|
||||
return new EditorDocumentHighlightingResult(
|
||||
EditorDocumentHighlightOwner.LOCAL,
|
||||
presentation.highlight(fileBuffer.content()));
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ final class EditorDocumentPresentationRegistry {
|
||||
EditorDocumentSyntaxHighlighting.bash());
|
||||
private static final EditorDocumentPresentation FRONTEND_PRESENTATION = new EditorDocumentPresentation(
|
||||
"fe",
|
||||
java.util.List.of(),
|
||||
java.util.List.of(stylesheet("presentations/fe.css")),
|
||||
EditorDocumentSyntaxHighlighting.plainText());
|
||||
|
||||
EditorDocumentPresentation resolve(final String typeId) {
|
||||
|
||||
@ -128,11 +128,18 @@ public final class EditorWorkspace extends Workspace {
|
||||
|
||||
final var fileBuffer = activeFile.orElseThrow();
|
||||
final EditorDocumentPresentation presentation = presentationRegistry.resolve(fileBuffer.typeId());
|
||||
final PrometeuLspAnalyzeDocumentResult analysis = fileBuffer.frontendDocument()
|
||||
? prometeuLspService.analyzeDocument(new PrometeuLspAnalyzeDocumentRequest(fileBuffer.path()))
|
||||
: null;
|
||||
final EditorDocumentHighlightingResult highlighting = EditorDocumentHighlightingRouter.route(
|
||||
fileBuffer,
|
||||
presentation,
|
||||
analysis);
|
||||
applyPresentationStylesheets(presentation);
|
||||
syncingEditor = true;
|
||||
try {
|
||||
codeArea.replaceText(fileBuffer.content());
|
||||
codeArea.setStyleSpans(0, presentation.highlight(fileBuffer.content()));
|
||||
codeArea.setStyleSpans(0, highlighting.styleSpans());
|
||||
} finally {
|
||||
syncingEditor = false;
|
||||
}
|
||||
@ -140,7 +147,7 @@ public final class EditorWorkspace extends Workspace {
|
||||
EditorDocumentPresentationStyles.applyToCodeArea(codeArea, presentation);
|
||||
refreshCommandSurfaces(fileBuffer);
|
||||
statusBar.showFile(projectReference, fileBuffer, presentation);
|
||||
refreshSemanticOutline(fileBuffer);
|
||||
refreshSemanticOutline(fileBuffer, analysis);
|
||||
}
|
||||
|
||||
private void revealActiveFileInNavigator() {
|
||||
@ -317,13 +324,13 @@ public final class EditorWorkspace extends Workspace {
|
||||
textDocument.dirty());
|
||||
}
|
||||
|
||||
private void refreshSemanticOutline(final EditorOpenFileBuffer fileBuffer) {
|
||||
if (!fileBuffer.frontendDocument()) {
|
||||
private void refreshSemanticOutline(
|
||||
final EditorOpenFileBuffer fileBuffer,
|
||||
final PrometeuLspAnalyzeDocumentResult analysis) {
|
||||
if (!fileBuffer.frontendDocument() || analysis == null) {
|
||||
outlinePanel.showPlaceholder();
|
||||
return;
|
||||
}
|
||||
final PrometeuLspAnalyzeDocumentResult analysis = prometeuLspService.analyzeDocument(
|
||||
new PrometeuLspAnalyzeDocumentRequest(fileBuffer.path()));
|
||||
outlinePanel.showSemanticReadResult(
|
||||
fileBuffer.path(),
|
||||
analysis.diagnostics(),
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package p.studio.workspaces.editor.syntaxhighlight;
|
||||
|
||||
import org.fxmisc.richtext.model.StyleSpans;
|
||||
import org.fxmisc.richtext.model.StyleSpansBuilder;
|
||||
import p.lsp.dtos.PrometeuLspHighlightSpanDTO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public final class EditorDocumentSemanticHighlighting {
|
||||
private EditorDocumentSemanticHighlighting() {
|
||||
}
|
||||
|
||||
public static StyleSpans<Collection<String>> highlight(
|
||||
final String content,
|
||||
final List<PrometeuLspHighlightSpanDTO> semanticHighlights) {
|
||||
final StyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>();
|
||||
final List<PrometeuLspHighlightSpanDTO> orderedHighlights = semanticHighlights.stream()
|
||||
.sorted(Comparator.comparingInt(highlight -> highlight.range().startOffset()))
|
||||
.toList();
|
||||
int cursor = 0;
|
||||
for (final PrometeuLspHighlightSpanDTO highlight : orderedHighlights) {
|
||||
final int start = Math.max(cursor, highlight.range().startOffset());
|
||||
final int end = Math.min(content.length(), highlight.range().endOffset());
|
||||
if (start > cursor) {
|
||||
builder.add(Collections.emptyList(), start - cursor);
|
||||
}
|
||||
if (end > start) {
|
||||
builder.add(List.of("editor-syntax-" + highlight.semanticKey()), end - start);
|
||||
cursor = end;
|
||||
}
|
||||
}
|
||||
if (cursor < content.length()) {
|
||||
builder.add(Collections.emptyList(), content.length() - cursor);
|
||||
}
|
||||
if (content.isEmpty()) {
|
||||
builder.add(Collections.emptyList(), 0);
|
||||
}
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
.editor-workspace-code-area-type-fe {
|
||||
-fx-highlight-fill: #1b3244;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text {
|
||||
-fx-fill: #edf4fb;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .lineno {
|
||||
-fx-text-fill: #71859a;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-keyword {
|
||||
-fx-fill: #8dc7ff;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-callable {
|
||||
-fx-fill: #f0cb79;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-type {
|
||||
-fx-fill: #9ddba8;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-binding {
|
||||
-fx-fill: #ffb1c8;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-string {
|
||||
-fx-fill: #e2c48c;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-number {
|
||||
-fx-fill: #c4e58a;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-comment {
|
||||
-fx-fill: #6f8192;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-literal {
|
||||
-fx-fill: #c8a2ff;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-operator {
|
||||
-fx-fill: #dbe6f1;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-punctuation {
|
||||
-fx-fill: #adc1d4;
|
||||
}
|
||||
|
||||
.editor-workspace-code-area-type-fe .text.editor-syntax-fe-identifier {
|
||||
-fx-fill: #edf4fb;
|
||||
}
|
||||
|
||||
.editor-workspace-status-chip-type-fe {
|
||||
-fx-background-color: #152432;
|
||||
-fx-border-color: #4d8db9;
|
||||
-fx-text-fill: #e8f5ff;
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.lsp.dtos.PrometeuLspHighlightSpanDTO;
|
||||
import p.lsp.dtos.PrometeuLspRangeDTO;
|
||||
import p.lsp.dtos.PrometeuLspSessionStateDTO;
|
||||
import p.lsp.messages.PrometeuLspAnalyzeDocumentResult;
|
||||
import p.studio.vfs.VfsDocumentAccessMode;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
final class EditorDocumentHighlightingRouterTest {
|
||||
@Test
|
||||
void frontendDocumentsUseLspOwnedHighlightsWhenSemanticSpansExist() {
|
||||
final EditorDocumentPresentationRegistry registry = new EditorDocumentPresentationRegistry();
|
||||
final EditorOpenFileBuffer fileBuffer = new EditorOpenFileBuffer(
|
||||
Path.of("/tmp/example/src/main.pbs"),
|
||||
"main.pbs",
|
||||
"pbs",
|
||||
"fn main() -> void {}",
|
||||
"LF",
|
||||
true,
|
||||
VfsDocumentAccessMode.READ_ONLY,
|
||||
false);
|
||||
|
||||
final PrometeuLspAnalyzeDocumentResult analysis = new PrometeuLspAnalyzeDocumentResult(
|
||||
new PrometeuLspSessionStateDTO(true, List.of("highlight")),
|
||||
List.of(),
|
||||
List.of(new PrometeuLspHighlightSpanDTO(new PrometeuLspRangeDTO(0, 2), "fe-keyword")),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
fileBuffer,
|
||||
registry.resolve("pbs"),
|
||||
analysis);
|
||||
|
||||
assertEquals(EditorDocumentHighlightOwner.LSP, result.owner());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonFrontendDocumentsStayOnLocalHighlighting() {
|
||||
final EditorDocumentPresentationRegistry registry = new EditorDocumentPresentationRegistry();
|
||||
final EditorOpenFileBuffer fileBuffer = new EditorOpenFileBuffer(
|
||||
Path.of("/tmp/example/prometeu.json"),
|
||||
"prometeu.json",
|
||||
"json",
|
||||
"{\n \"name\": \"Example\"\n}\n",
|
||||
"LF",
|
||||
false,
|
||||
VfsDocumentAccessMode.EDITABLE,
|
||||
false);
|
||||
|
||||
final PrometeuLspAnalyzeDocumentResult analysis = new PrometeuLspAnalyzeDocumentResult(
|
||||
new PrometeuLspSessionStateDTO(true, List.of("highlight")),
|
||||
List.of(),
|
||||
List.of(new PrometeuLspHighlightSpanDTO(new PrometeuLspRangeDTO(0, 1), "fe-punctuation")),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
fileBuffer,
|
||||
registry.resolve("json"),
|
||||
analysis);
|
||||
|
||||
assertEquals(EditorDocumentHighlightOwner.LOCAL, result.owner());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user