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-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-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-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
|
id: PLN-0025
|
||||||
ticket: studio-editor-write-wave-supported-non-frontend-files
|
ticket: studio-editor-write-wave-supported-non-frontend-files
|
||||||
title: Implement FE semantic highlight and editor consumption for the read-only LSP phase
|
title: Implement FE semantic highlight and editor consumption for the read-only LSP phase
|
||||||
status: review
|
status: done
|
||||||
created: 2026-03-31
|
created: 2026-03-31
|
||||||
completed:
|
completed: 2026-03-31
|
||||||
tags: [studio, lsp, highlight, frontend, editor]
|
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;
|
package p.lsp.messages;
|
||||||
|
|
||||||
import p.lsp.dtos.PrometeuLspDiagnosticDTO;
|
import p.lsp.dtos.PrometeuLspDiagnosticDTO;
|
||||||
|
import p.lsp.dtos.PrometeuLspHighlightSpanDTO;
|
||||||
import p.lsp.dtos.PrometeuLspSessionStateDTO;
|
import p.lsp.dtos.PrometeuLspSessionStateDTO;
|
||||||
import p.lsp.dtos.PrometeuLspSymbolDTO;
|
import p.lsp.dtos.PrometeuLspSymbolDTO;
|
||||||
|
|
||||||
@ -10,12 +11,14 @@ import java.util.Objects;
|
|||||||
public record PrometeuLspAnalyzeDocumentResult(
|
public record PrometeuLspAnalyzeDocumentResult(
|
||||||
PrometeuLspSessionStateDTO sessionState,
|
PrometeuLspSessionStateDTO sessionState,
|
||||||
List<PrometeuLspDiagnosticDTO> diagnostics,
|
List<PrometeuLspDiagnosticDTO> diagnostics,
|
||||||
|
List<PrometeuLspHighlightSpanDTO> semanticHighlights,
|
||||||
List<PrometeuLspSymbolDTO> documentSymbols,
|
List<PrometeuLspSymbolDTO> documentSymbols,
|
||||||
List<PrometeuLspSymbolDTO> workspaceSymbols) {
|
List<PrometeuLspSymbolDTO> workspaceSymbols) {
|
||||||
|
|
||||||
public PrometeuLspAnalyzeDocumentResult {
|
public PrometeuLspAnalyzeDocumentResult {
|
||||||
Objects.requireNonNull(sessionState, "sessionState");
|
Objects.requireNonNull(sessionState, "sessionState");
|
||||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||||
|
semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights"));
|
||||||
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));
|
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));
|
||||||
workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols"));
|
workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols"));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,7 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
return new PrometeuLspAnalyzeDocumentResult(
|
return new PrometeuLspAnalyzeDocumentResult(
|
||||||
new PrometeuLspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")),
|
new PrometeuLspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")),
|
||||||
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||||
|
session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||||
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||||
session.workspaceSymbols());
|
session.workspaceSymbols());
|
||||||
}
|
}
|
||||||
@ -142,6 +143,7 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
normalize(requestedDocumentPath),
|
normalize(requestedDocumentPath),
|
||||||
diagnosticsByDocument,
|
diagnosticsByDocument,
|
||||||
Map.of(),
|
Map.of(),
|
||||||
|
Map.of(),
|
||||||
List.of(),
|
List.of(),
|
||||||
Map.of(),
|
Map.of(),
|
||||||
Map.of());
|
Map.of());
|
||||||
@ -160,6 +162,7 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
return new SemanticSession(
|
return new SemanticSession(
|
||||||
normalize(requestedDocumentPath),
|
normalize(requestedDocumentPath),
|
||||||
diagnosticsByDocument,
|
diagnosticsByDocument,
|
||||||
|
semanticIndex.semanticHighlightsByDocument(),
|
||||||
semanticIndex.documentSymbolsByDocument(),
|
semanticIndex.documentSymbolsByDocument(),
|
||||||
semanticIndex.workspaceSymbols(),
|
semanticIndex.workspaceSymbols(),
|
||||||
semanticIndex.symbolsByName(),
|
semanticIndex.symbolsByName(),
|
||||||
@ -281,6 +284,7 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
private record SemanticSession(
|
private record SemanticSession(
|
||||||
Path requestedDocumentPath,
|
Path requestedDocumentPath,
|
||||||
Map<Path, List<PrometeuLspDiagnosticDTO>> diagnosticsByDocument,
|
Map<Path, List<PrometeuLspDiagnosticDTO>> diagnosticsByDocument,
|
||||||
|
Map<Path, List<PrometeuLspHighlightSpanDTO>> semanticHighlightsByDocument,
|
||||||
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument,
|
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument,
|
||||||
List<PrometeuLspSymbolDTO> workspaceSymbols,
|
List<PrometeuLspSymbolDTO> workspaceSymbols,
|
||||||
Map<String, List<PrometeuLspSymbolDTO>> symbolsByName,
|
Map<String, List<PrometeuLspSymbolDTO>> symbolsByName,
|
||||||
@ -293,6 +297,7 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final class SemanticIndex {
|
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 Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument = new LinkedHashMap<>();
|
||||||
private final List<PrometeuLspSymbolDTO> workspaceSymbols = new ArrayList<>();
|
private final List<PrometeuLspSymbolDTO> workspaceSymbols = new ArrayList<>();
|
||||||
private final Map<String, List<PrometeuLspSymbolDTO>> symbolsByName = new LinkedHashMap<>();
|
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);
|
symbolsByName.computeIfAbsent(child.name(), ignored -> new ArrayList<>()).add(child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
semanticHighlightsByDocument.put(
|
||||||
|
normalizedDocumentPath,
|
||||||
|
buildSemanticHighlights(tokens, symbolsByName));
|
||||||
documentSymbolsByDocument.put(normalizedDocumentPath, List.copyOf(documentSymbols));
|
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(
|
private PrometeuLspSymbolDTO symbolForTopDecl(
|
||||||
final Path documentPath,
|
final Path documentPath,
|
||||||
final PbsAst.TopDecl topDecl) {
|
final PbsAst.TopDecl topDecl) {
|
||||||
@ -407,6 +473,10 @@ final class PrometeuLspSemanticReadPhase {
|
|||||||
children);
|
children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<Path, List<PrometeuLspHighlightSpanDTO>> semanticHighlightsByDocument() {
|
||||||
|
return Map.copyOf(semanticHighlightsByDocument);
|
||||||
|
}
|
||||||
|
|
||||||
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument() {
|
Map<Path, List<PrometeuLspSymbolDTO>> documentSymbolsByDocument() {
|
||||||
return Map.copyOf(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());
|
EditorDocumentSyntaxHighlighting.bash());
|
||||||
private static final EditorDocumentPresentation FRONTEND_PRESENTATION = new EditorDocumentPresentation(
|
private static final EditorDocumentPresentation FRONTEND_PRESENTATION = new EditorDocumentPresentation(
|
||||||
"fe",
|
"fe",
|
||||||
java.util.List.of(),
|
java.util.List.of(stylesheet("presentations/fe.css")),
|
||||||
EditorDocumentSyntaxHighlighting.plainText());
|
EditorDocumentSyntaxHighlighting.plainText());
|
||||||
|
|
||||||
EditorDocumentPresentation resolve(final String typeId) {
|
EditorDocumentPresentation resolve(final String typeId) {
|
||||||
|
|||||||
@ -128,11 +128,18 @@ public final class EditorWorkspace extends Workspace {
|
|||||||
|
|
||||||
final var fileBuffer = activeFile.orElseThrow();
|
final var fileBuffer = activeFile.orElseThrow();
|
||||||
final EditorDocumentPresentation presentation = presentationRegistry.resolve(fileBuffer.typeId());
|
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);
|
applyPresentationStylesheets(presentation);
|
||||||
syncingEditor = true;
|
syncingEditor = true;
|
||||||
try {
|
try {
|
||||||
codeArea.replaceText(fileBuffer.content());
|
codeArea.replaceText(fileBuffer.content());
|
||||||
codeArea.setStyleSpans(0, presentation.highlight(fileBuffer.content()));
|
codeArea.setStyleSpans(0, highlighting.styleSpans());
|
||||||
} finally {
|
} finally {
|
||||||
syncingEditor = false;
|
syncingEditor = false;
|
||||||
}
|
}
|
||||||
@ -140,7 +147,7 @@ public final class EditorWorkspace extends Workspace {
|
|||||||
EditorDocumentPresentationStyles.applyToCodeArea(codeArea, presentation);
|
EditorDocumentPresentationStyles.applyToCodeArea(codeArea, presentation);
|
||||||
refreshCommandSurfaces(fileBuffer);
|
refreshCommandSurfaces(fileBuffer);
|
||||||
statusBar.showFile(projectReference, fileBuffer, presentation);
|
statusBar.showFile(projectReference, fileBuffer, presentation);
|
||||||
refreshSemanticOutline(fileBuffer);
|
refreshSemanticOutline(fileBuffer, analysis);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revealActiveFileInNavigator() {
|
private void revealActiveFileInNavigator() {
|
||||||
@ -317,13 +324,13 @@ public final class EditorWorkspace extends Workspace {
|
|||||||
textDocument.dirty());
|
textDocument.dirty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshSemanticOutline(final EditorOpenFileBuffer fileBuffer) {
|
private void refreshSemanticOutline(
|
||||||
if (!fileBuffer.frontendDocument()) {
|
final EditorOpenFileBuffer fileBuffer,
|
||||||
|
final PrometeuLspAnalyzeDocumentResult analysis) {
|
||||||
|
if (!fileBuffer.frontendDocument() || analysis == null) {
|
||||||
outlinePanel.showPlaceholder();
|
outlinePanel.showPlaceholder();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final PrometeuLspAnalyzeDocumentResult analysis = prometeuLspService.analyzeDocument(
|
|
||||||
new PrometeuLspAnalyzeDocumentRequest(fileBuffer.path()));
|
|
||||||
outlinePanel.showSemanticReadResult(
|
outlinePanel.showSemanticReadResult(
|
||||||
fileBuffer.path(),
|
fileBuffer.path(),
|
||||||
analysis.diagnostics(),
|
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