implement PLN-0032 structural anchor payloads and tests
This commit is contained in:
parent
20851c0958
commit
d93fe98bcd
@ -14,4 +14,4 @@
|
||||
{"type":"discussion","id":"DSC-0013","status":"done","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-04-02","tags":["studio","editor","workspace","write","read-only","vfs","frontend-boundary"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0013-studio-editor-write-wave-supported-non-frontend-files/LSN-0028-controlled-editor-write-wave-and-read-only-frontend-semantic-phase.md","status":"done","created_at":"2026-04-02","updated_at":"2026-04-02"}]}
|
||||
{"type":"discussion","id":"DSC-0014","status":"done","ticket":"studio-frontend-owned-semantic-editor-presentation","title":"Definir ownership do schema visual semantico do editor por frontend","created_at":"2026-04-02","updated_at":"2026-04-02","tags":["studio","editor","frontend","presentation","semantic-highlighting","compiler","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"discussion/lessons/DSC-0014-studio-frontend-owned-semantic-editor-presentation/LSN-0029-frontend-owned-semantic-presentation-descriptor-and-host-consumption.md","status":"done","created_at":"2026-04-02","updated_at":"2026-04-02"}]}
|
||||
{"type":"discussion","id":"DSC-0015","status":"done","ticket":"pbs-service-facade-reserved-metadata","title":"SDK Service Bodies Calling Builtin/Intrinsic Proxies as Ordinary PBS Code","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["compiler","pbs","sdk","stdlib","lowering","service","intrinsic","sdk-interface"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"discussion/lessons/DSC-0015-pbs-service-facade-reserved-metadata/LSN-0030-sdk-service-bodies-over-private-reserved-proxies.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"}]}
|
||||
{"type":"discussion","id":"DSC-0016","status":"open","ticket":"studio-editor-scope-guides-and-brace-anchoring","title":"Scope Guides do Code Editor com ancoragem exata em braces e destaque do escopo ativo","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["studio","editor","scope-guides","braces","semantic-read","frontend-contract"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-studio-editor-scope-guides-and-brace-anchoring.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"decisions":[{"id":"DEC-0014","file":"DEC-0014-studio-editor-active-scope-and-structural-anchors.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"plans":[{"id":"PLN-0030","file":"PLN-0030-studio-active-container-and-active-scope-gutter-wave-1.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0031","file":"PLN-0031-studio-structural-anchor-semantic-surface-specification.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0032","file":"PLN-0032-frontend-structural-anchor-payloads-and-anchor-aware-tests.md","status":"review","created_at":"2026-04-03","updated_at":"2026-04-03"}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0016","status":"open","ticket":"studio-editor-scope-guides-and-brace-anchoring","title":"Scope Guides do Code Editor com ancoragem exata em braces e destaque do escopo ativo","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["studio","editor","scope-guides","braces","semantic-read","frontend-contract"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-studio-editor-scope-guides-and-brace-anchoring.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"decisions":[{"id":"DEC-0014","file":"DEC-0014-studio-editor-active-scope-and-structural-anchors.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"plans":[{"id":"PLN-0030","file":"PLN-0030-studio-active-container-and-active-scope-gutter-wave-1.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0031","file":"PLN-0031-studio-structural-anchor-semantic-surface-specification.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0032","file":"PLN-0032-frontend-structural-anchor-payloads-and-anchor-aware-tests.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"}],"lessons":[]}
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
id: PLN-0032
|
||||
ticket: studio-editor-scope-guides-and-brace-anchoring
|
||||
title: Frontend structural-anchor payload propagation and anchor-aware test coverage
|
||||
status: review
|
||||
status: done
|
||||
created: 2026-04-03
|
||||
completed:
|
||||
completed: 2026-04-03
|
||||
tags:
|
||||
- studio
|
||||
- frontend
|
||||
@ -108,10 +108,10 @@ The implementation must preserve the DEC-0014 rule that structural anchors are t
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] Frontend semantic-read payloads expose structural anchors through a dedicated semantic surface.
|
||||
- [ ] Studio consumes structural anchors without overloading `documentSymbols`.
|
||||
- [ ] Exact guide positioning uses structural-anchor metadata when available.
|
||||
- [ ] Tests cover payload transport, anchor interpretation, and anchor-aware rendering.
|
||||
- [x] Frontend semantic-read payloads expose structural anchors through a dedicated semantic surface.
|
||||
- [x] Studio consumes structural anchors without overloading `documentSymbols`.
|
||||
- [x] Exact guide positioning uses structural-anchor metadata when available.
|
||||
- [x] Tests cover payload transport, anchor interpretation, and anchor-aware rendering.
|
||||
|
||||
## Dependencies
|
||||
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package p.studio.lsp.dtos;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record LspStructuralAnchorDTO(
|
||||
LspRangeDTO range,
|
||||
LspRangeDTO startAnchor,
|
||||
LspRangeDTO endAnchor,
|
||||
List<LspStructuralAnchorDTO> children) {
|
||||
|
||||
public LspStructuralAnchorDTO {
|
||||
range = Objects.requireNonNull(range, "range");
|
||||
startAnchor = Objects.requireNonNull(startAnchor, "startAnchor");
|
||||
endAnchor = Objects.requireNonNull(endAnchor, "endAnchor");
|
||||
children = List.copyOf(Objects.requireNonNull(children, "children"));
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import p.studio.lsp.dtos.LspDiagnosticDTO;
|
||||
import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
import p.studio.lsp.dtos.LspSessionStateDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
|
||||
import java.util.List;
|
||||
@ -15,6 +16,7 @@ public record LspAnalyzeDocumentResult(
|
||||
List<LspDiagnosticDTO> diagnostics,
|
||||
List<LspHighlightSpanDTO> semanticHighlights,
|
||||
List<LspSymbolDTO> documentSymbols,
|
||||
List<LspStructuralAnchorDTO> structuralAnchors,
|
||||
List<LspSymbolDTO> workspaceSymbols) {
|
||||
|
||||
public LspAnalyzeDocumentResult {
|
||||
@ -23,6 +25,7 @@ public record LspAnalyzeDocumentResult(
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights"));
|
||||
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));
|
||||
structuralAnchors = List.copyOf(Objects.requireNonNull(structuralAnchors, "structuralAnchors"));
|
||||
workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,11 +15,12 @@ class LspSemanticAnalyseService {
|
||||
final LspAnalyzeDocumentRequest request) {
|
||||
final var normalizedRequestedDocument = normalize(request.documentPath());
|
||||
return new LspAnalyzeDocumentResult(
|
||||
new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight")),
|
||||
new LspSessionStateDTO(true, List.of("diagnostics", "symbols", "definition", "highlight", "structural-anchors")),
|
||||
session.semanticPresentation(),
|
||||
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.structuralAnchorsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.workspaceSymbols());
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,6 +145,7 @@ final class LspSemanticReadPhase {
|
||||
diagnosticsByDocument,
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
List.of(),
|
||||
Map.of(),
|
||||
Map.of());
|
||||
@ -180,6 +181,7 @@ final class LspSemanticReadPhase {
|
||||
diagnosticsByDocument,
|
||||
semanticIndex.semanticHighlightsByDocument(),
|
||||
semanticIndex.documentSymbolsByDocument(),
|
||||
semanticIndex.structuralAnchorsByDocument(),
|
||||
semanticIndex.workspaceSymbols(),
|
||||
semanticIndex.symbolsByName(),
|
||||
semanticIndex.tokensByDocument());
|
||||
|
||||
@ -4,6 +4,7 @@ import p.studio.compiler.pbs.lexer.PbsToken;
|
||||
import p.studio.lsp.dtos.LspDiagnosticDTO;
|
||||
import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@ -16,6 +17,7 @@ public record SemanticSession(
|
||||
Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument,
|
||||
Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument,
|
||||
Map<Path, List<LspSymbolDTO>> documentSymbolsByDocument,
|
||||
Map<Path, List<LspStructuralAnchorDTO>> structuralAnchorsByDocument,
|
||||
List<LspSymbolDTO> workspaceSymbols,
|
||||
Map<String, List<LspSymbolDTO>> symbolsByName,
|
||||
Map<Path, List<PbsToken>> tokensByDocument) {
|
||||
|
||||
@ -7,6 +7,7 @@ import p.studio.compiler.pbs.lexer.PbsTokenKind;
|
||||
import p.studio.compiler.source.Span;
|
||||
import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import p.studio.lsp.dtos.LspRangeDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
import p.studio.lsp.messages.LspSymbolKind;
|
||||
|
||||
@ -22,6 +23,7 @@ import static p.studio.lsp.LspSemanticUtilities.normalize;
|
||||
public final class SemanticIndex {
|
||||
private final Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument = new LinkedHashMap<>();
|
||||
private final Map<Path, List<LspSymbolDTO>> documentSymbolsByDocument = new LinkedHashMap<>();
|
||||
private final Map<Path, List<LspStructuralAnchorDTO>> structuralAnchorsByDocument = new LinkedHashMap<>();
|
||||
private final List<LspSymbolDTO> workspaceSymbols = new ArrayList<>();
|
||||
private final Map<String, List<LspSymbolDTO>> symbolsByName = new LinkedHashMap<>();
|
||||
private final Map<Path, List<PbsToken>> tokensByDocument = new LinkedHashMap<>();
|
||||
@ -62,6 +64,9 @@ public final class SemanticIndex {
|
||||
}
|
||||
}
|
||||
documentSymbolsByDocument.put(normalizedDocumentPath, List.copyOf(documentSymbols));
|
||||
structuralAnchorsByDocument.put(
|
||||
normalizedDocumentPath,
|
||||
structuralAnchors(documentSymbols, tokens));
|
||||
}
|
||||
|
||||
public void buildHighlights(
|
||||
@ -498,6 +503,66 @@ public final class SemanticIndex {
|
||||
children);
|
||||
}
|
||||
|
||||
private List<LspStructuralAnchorDTO> structuralAnchors(
|
||||
final List<LspSymbolDTO> documentSymbols,
|
||||
final List<PbsToken> tokens) {
|
||||
final List<LspStructuralAnchorDTO> anchors = new ArrayList<>();
|
||||
for (final LspSymbolDTO symbol : documentSymbols) {
|
||||
final LspStructuralAnchorDTO anchor = structuralAnchor(symbol, tokens);
|
||||
if (anchor != null) {
|
||||
anchors.add(anchor);
|
||||
}
|
||||
}
|
||||
return List.copyOf(anchors);
|
||||
}
|
||||
|
||||
private LspStructuralAnchorDTO structuralAnchor(
|
||||
final LspSymbolDTO symbol,
|
||||
final List<PbsToken> tokens) {
|
||||
final List<LspStructuralAnchorDTO> children = new ArrayList<>();
|
||||
for (final LspSymbolDTO child : symbol.children()) {
|
||||
final LspStructuralAnchorDTO anchorChild = structuralAnchor(child, tokens);
|
||||
if (anchorChild != null) {
|
||||
children.add(anchorChild);
|
||||
}
|
||||
}
|
||||
final AnchorPair anchorPair = anchorPair(symbol.range(), tokens);
|
||||
if (anchorPair == null) {
|
||||
if (children.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new LspStructuralAnchorDTO(symbol.range(), symbol.range(), symbol.range(), children);
|
||||
}
|
||||
return new LspStructuralAnchorDTO(symbol.range(), anchorPair.start(), anchorPair.end(), children);
|
||||
}
|
||||
|
||||
private AnchorPair anchorPair(
|
||||
final LspRangeDTO range,
|
||||
final List<PbsToken> tokens) {
|
||||
PbsToken firstLeftBrace = null;
|
||||
PbsToken lastRightBrace = null;
|
||||
for (final PbsToken token : tokens) {
|
||||
if (token.kind() == PbsTokenKind.EOF) {
|
||||
continue;
|
||||
}
|
||||
if (token.start() < range.startOffset() || token.end() > range.endOffset()) {
|
||||
continue;
|
||||
}
|
||||
if (token.kind() == PbsTokenKind.LEFT_BRACE && firstLeftBrace == null) {
|
||||
firstLeftBrace = token;
|
||||
}
|
||||
if (token.kind() == PbsTokenKind.RIGHT_BRACE) {
|
||||
lastRightBrace = token;
|
||||
}
|
||||
}
|
||||
if (firstLeftBrace == null || lastRightBrace == null || lastRightBrace.start() < firstLeftBrace.start()) {
|
||||
return null;
|
||||
}
|
||||
return new AnchorPair(
|
||||
new LspRangeDTO(firstLeftBrace.start(), firstLeftBrace.end()),
|
||||
new LspRangeDTO(lastRightBrace.start(), lastRightBrace.end()));
|
||||
}
|
||||
|
||||
public Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument() {
|
||||
return Map.copyOf(semanticHighlightsByDocument);
|
||||
}
|
||||
@ -506,6 +571,10 @@ public final class SemanticIndex {
|
||||
return Map.copyOf(documentSymbolsByDocument);
|
||||
}
|
||||
|
||||
public Map<Path, List<LspStructuralAnchorDTO>> structuralAnchorsByDocument() {
|
||||
return Map.copyOf(structuralAnchorsByDocument);
|
||||
}
|
||||
|
||||
public List<LspSymbolDTO> workspaceSymbols() {
|
||||
return List.copyOf(workspaceSymbols);
|
||||
}
|
||||
@ -521,4 +590,9 @@ public final class SemanticIndex {
|
||||
public Map<Path, List<PbsToken>> tokensByDocument() {
|
||||
return Map.copyOf(tokensByDocument);
|
||||
}
|
||||
|
||||
private record AnchorPair(
|
||||
LspRangeDTO start,
|
||||
LspRangeDTO end) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package p.studio.lsp;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.messages.LspProjectContext;
|
||||
import p.studio.lsp.dtos.LspDefinitionTargetDTO;
|
||||
import p.studio.lsp.messages.LspAnalyzeDocumentRequest;
|
||||
@ -104,6 +105,12 @@ final class LspServiceImplTest {
|
||||
assertTrue(
|
||||
flatten(analysis.documentSymbols()).stream().anyMatch(symbol -> symbol.kind() == p.studio.lsp.messages.LspSymbolKind.HANDLE),
|
||||
analysis.toString());
|
||||
assertFalse(analysis.structuralAnchors().isEmpty(), analysis.toString());
|
||||
assertEquals(OVERLAY_SOURCE.indexOf("{"), analysis.structuralAnchors().getFirst().startAnchor().startOffset());
|
||||
assertTrue(
|
||||
flattenAnchors(analysis.structuralAnchors()).stream().anyMatch(anchor ->
|
||||
anchor.startAnchor().startOffset() == OVERLAY_SOURCE.indexOf("if true {") + "if true ".length()),
|
||||
analysis.toString());
|
||||
assertTrue(
|
||||
analysis.workspaceSymbols().stream().anyMatch(symbol ->
|
||||
symbol.name().equals("helper") && symbol.documentPath().equals(normalize(helperFile))),
|
||||
@ -236,6 +243,16 @@ final class LspServiceImplTest {
|
||||
return flattened;
|
||||
}
|
||||
|
||||
private static List<LspStructuralAnchorDTO> flattenAnchors(
|
||||
final List<LspStructuralAnchorDTO> anchors) {
|
||||
final List<LspStructuralAnchorDTO> flattened = new java.util.ArrayList<>();
|
||||
for (final var anchor : anchors) {
|
||||
flattened.add(anchor);
|
||||
flattened.addAll(flattenAnchors(anchor.children()));
|
||||
}
|
||||
return flattened;
|
||||
}
|
||||
|
||||
private VfsProjectContext projectContext(final Path projectRoot) {
|
||||
return new VfsProjectContext("Example", "pbs", projectRoot);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package p.studio.workspaces.editor;
|
||||
|
||||
import p.studio.lsp.dtos.LspRangeDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
import p.studio.lsp.messages.LspSymbolKind;
|
||||
|
||||
@ -34,11 +35,70 @@ final class EditorDocumentScopeGuideModel {
|
||||
static EditorDocumentScopeGuideModel from(
|
||||
final String content,
|
||||
final List<LspSymbolDTO> symbols) {
|
||||
return fromRanges(
|
||||
content,
|
||||
symbols,
|
||||
0,
|
||||
null,
|
||||
(symbol, depth, parentId, nextId, lineStarts, contentLength) -> {
|
||||
if (symbol.kind() == LspSymbolKind.UNKNOWN) {
|
||||
return null;
|
||||
}
|
||||
final LspRangeDTO range = symbol.range();
|
||||
return toGuideRange(
|
||||
lineStarts,
|
||||
contentLength,
|
||||
range,
|
||||
new LspRangeDTO(range.startOffset(), Math.min(range.endOffset(), range.startOffset() + 1)),
|
||||
new LspRangeDTO(Math.max(range.startOffset(), range.endOffset() - 1), range.endOffset()),
|
||||
depth,
|
||||
parentId,
|
||||
nextId[0]);
|
||||
},
|
||||
LspSymbolDTO::children);
|
||||
}
|
||||
|
||||
static EditorDocumentScopeGuideModel fromStructuralAnchors(
|
||||
final String content,
|
||||
final List<LspStructuralAnchorDTO> structuralAnchors) {
|
||||
return fromRanges(
|
||||
content,
|
||||
structuralAnchors,
|
||||
0,
|
||||
null,
|
||||
(anchor, depth, parentId, nextId, lineStarts, contentLength) -> toGuideRange(
|
||||
lineStarts,
|
||||
contentLength,
|
||||
anchor.range(),
|
||||
anchor.startAnchor(),
|
||||
anchor.endAnchor(),
|
||||
depth,
|
||||
parentId,
|
||||
nextId[0]),
|
||||
LspStructuralAnchorDTO::children);
|
||||
}
|
||||
|
||||
private static <T> EditorDocumentScopeGuideModel fromRanges(
|
||||
final String content,
|
||||
final List<T> roots,
|
||||
final int initialDepth,
|
||||
final Integer initialParentId,
|
||||
final RangeFactory<T> rangeFactory,
|
||||
final ChildAccessor<T> childAccessor) {
|
||||
final List<Integer> lineStarts = lineStarts(content);
|
||||
final List<GuideRange> ranges = new ArrayList<>();
|
||||
final int[] nextId = new int[] {1};
|
||||
for (final LspSymbolDTO symbol : symbols) {
|
||||
appendSymbol(ranges, lineStarts, content.length(), symbol, 0, null, nextId);
|
||||
for (final T root : roots) {
|
||||
appendRangeNode(
|
||||
ranges,
|
||||
lineStarts,
|
||||
content.length(),
|
||||
root,
|
||||
initialDepth,
|
||||
initialParentId,
|
||||
nextId,
|
||||
rangeFactory,
|
||||
childAccessor);
|
||||
}
|
||||
ranges.sort(Comparator
|
||||
.comparingInt(GuideRange::startOffset)
|
||||
@ -88,25 +148,25 @@ final class EditorDocumentScopeGuideModel {
|
||||
return lineStarts.size();
|
||||
}
|
||||
|
||||
private static void appendSymbol(
|
||||
private static <T> void appendRangeNode(
|
||||
final List<GuideRange> ranges,
|
||||
final List<Integer> lineStarts,
|
||||
final int contentLength,
|
||||
final LspSymbolDTO symbol,
|
||||
final T node,
|
||||
final int depth,
|
||||
final Integer parentId,
|
||||
final int[] nextId) {
|
||||
final int[] nextId,
|
||||
final RangeFactory<T> rangeFactory,
|
||||
final ChildAccessor<T> childAccessor) {
|
||||
Integer currentParentId = parentId;
|
||||
if (symbol.kind() != LspSymbolKind.UNKNOWN) {
|
||||
final GuideRange range = toGuideRange(lineStarts, contentLength, symbol.range(), depth, parentId, nextId[0]);
|
||||
if (range != null) {
|
||||
ranges.add(range);
|
||||
currentParentId = range.id();
|
||||
nextId[0]++;
|
||||
}
|
||||
final GuideRange range = rangeFactory.create(node, depth, parentId, nextId, lineStarts, contentLength);
|
||||
if (range != null) {
|
||||
ranges.add(range);
|
||||
currentParentId = range.id();
|
||||
nextId[0]++;
|
||||
}
|
||||
for (final LspSymbolDTO child : symbol.children()) {
|
||||
appendSymbol(ranges, lineStarts, contentLength, child, depth + 1, currentParentId, nextId);
|
||||
for (final T child : childAccessor.children(node)) {
|
||||
appendRangeNode(ranges, lineStarts, contentLength, child, depth + 1, currentParentId, nextId, rangeFactory, childAccessor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,18 +174,25 @@ final class EditorDocumentScopeGuideModel {
|
||||
final List<Integer> lineStarts,
|
||||
final int contentLength,
|
||||
final LspRangeDTO range,
|
||||
final LspRangeDTO startAnchor,
|
||||
final LspRangeDTO endAnchor,
|
||||
final int depth,
|
||||
final Integer parentId,
|
||||
final int id) {
|
||||
final int startOffset = clamp(range.startOffset(), 0, contentLength);
|
||||
final int endOffset = Math.max(startOffset, clamp(range.endOffset(), 0, contentLength));
|
||||
final int startLine = lineForOffset(lineStarts, startOffset);
|
||||
final int startAnchorOffset = clamp(startAnchor.startOffset(), startOffset, endOffset);
|
||||
final int endAnchorOffset = clamp(Math.max(startAnchorOffset, endAnchor.startOffset()), startOffset, endOffset);
|
||||
final int startLine = lineForOffset(lineStarts, startAnchorOffset);
|
||||
final int inclusiveEndOffset = Math.max(startOffset, Math.max(startOffset, endOffset) - 1);
|
||||
final int endLine = lineForOffset(lineStarts, clamp(inclusiveEndOffset, 0, contentLength));
|
||||
final int inclusiveEndAnchorOffset = Math.max(
|
||||
startAnchorOffset,
|
||||
Math.min(Math.max(startOffset, endOffset) - 1, Math.max(endAnchorOffset, startAnchorOffset)));
|
||||
final int endLine = lineForOffset(lineStarts, clamp(inclusiveEndAnchorOffset, 0, contentLength));
|
||||
if (endLine <= startLine) {
|
||||
return null;
|
||||
}
|
||||
return new GuideRange(id, depth, startOffset, endOffset, startLine, endLine, parentId);
|
||||
return new GuideRange(id, depth, startOffset, endOffset, startAnchorOffset, endAnchorOffset, startLine, endLine, parentId);
|
||||
}
|
||||
|
||||
private GuideRange findById(final int id) {
|
||||
@ -225,6 +292,8 @@ final class EditorDocumentScopeGuideModel {
|
||||
int depth,
|
||||
int startOffset,
|
||||
int endOffset,
|
||||
int startAnchorOffset,
|
||||
int endAnchorOffset,
|
||||
int startLine,
|
||||
int endLine,
|
||||
Integer parentId) {
|
||||
@ -252,4 +321,20 @@ final class EditorDocumentScopeGuideModel {
|
||||
Objects.requireNonNull(kind, "kind");
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ChildAccessor<T> {
|
||||
List<T> children(T node);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface RangeFactory<T> {
|
||||
GuideRange create(
|
||||
T node,
|
||||
int depth,
|
||||
Integer parentId,
|
||||
int[] nextId,
|
||||
List<Integer> lineStarts,
|
||||
int contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
@ -381,6 +381,11 @@ public final class EditorWorkspace extends Workspace {
|
||||
if (!fileBuffer.frontendDocument() || analysis == null) {
|
||||
return EditorDocumentScopeGuideModel.empty();
|
||||
}
|
||||
if (!analysis.structuralAnchors().isEmpty()) {
|
||||
return EditorDocumentScopeGuideModel.fromStructuralAnchors(
|
||||
fileBuffer.content(),
|
||||
analysis.structuralAnchors());
|
||||
}
|
||||
return EditorDocumentScopeGuideModel.from(fileBuffer.content(), analysis.documentSymbols());
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(),
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "pbs-keyword")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -67,6 +68,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(),
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 1), "fe-punctuation")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -98,6 +100,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(),
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "pbs-keyword")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -130,6 +133,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(),
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(16, 20), "pbs-service")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
|
||||
@ -2,6 +2,7 @@ package p.studio.workspaces.editor;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import p.studio.lsp.dtos.LspRangeDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
import p.studio.lsp.messages.LspSymbolKind;
|
||||
|
||||
@ -108,6 +109,33 @@ final class EditorDocumentScopeGuideModelTest {
|
||||
assertEquals(EditorDocumentScopeGuideModel.ActiveGuides.empty(), model.resolveActiveGuides(content.indexOf("value")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void structuralAnchorsDriveGuideStartAndEndLines() {
|
||||
final String content = """
|
||||
fn main(
|
||||
value: int
|
||||
) -> void
|
||||
{
|
||||
helper();
|
||||
}
|
||||
""";
|
||||
final LspStructuralAnchorDTO anchor = new LspStructuralAnchorDTO(
|
||||
new LspRangeDTO(content.indexOf("fn main"), content.length()),
|
||||
new LspRangeDTO(content.indexOf("{"), content.indexOf("{") + 1),
|
||||
new LspRangeDTO(content.indexOf("}"), content.indexOf("}") + 1),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentScopeGuideModel model = EditorDocumentScopeGuideModel.fromStructuralAnchors(content, List.of(anchor));
|
||||
final EditorDocumentScopeGuideModel.ActiveGuides guides = model.resolveActiveGuides(content.indexOf("helper"));
|
||||
|
||||
assertSegments(model, 0, guides);
|
||||
assertSegments(model, 1, guides);
|
||||
assertSegments(model, 2, guides);
|
||||
assertSegments(model, 3, guides, "ACTIVE_SCOPE:START");
|
||||
assertSegments(model, 4, guides, "ACTIVE_SCOPE:CONTINUE");
|
||||
assertSegments(model, 5, guides, "ACTIVE_SCOPE:END");
|
||||
}
|
||||
|
||||
private static LspSymbolDTO symbol(
|
||||
final String content,
|
||||
final String startMarker,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user