-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Description
Implement folding ranges in the LSP server to allow users to collapse and expand sections of AsciiDoc documents in the editor. This improves navigation and readability for large documents.
Current State
Status: Not implemented - LSP server doesn't provide folding ranges
Required Changes
1. Add Folding Range Capability
File: AsciidocLanguageServer.java
ServerCapabilities capabilities = new ServerCapabilities();
capabilities.setFoldingRangeProvider(true);2. Implement Folding Range Provider
File: AsciidocTextDocumentService.java
@Override
public CompletableFuture<List<FoldingRange>> foldingRange(FoldingRangeRequestParams params) {
String uri = params.getTextDocument().getUri();
AsciidocDocumentModel model = documentCache.get(uri);
if (model == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<FoldingRange> ranges = new ArrayList<>();
List<String> lines = model.getLines();
// Find foldable sections
ranges.addAll(findHeaderFoldingRanges(lines));
ranges.addAll(findBlockFoldingRanges(lines));
ranges.addAll(findListFoldingRanges(lines));
ranges.addAll(findTableFoldingRanges(lines));
return CompletableFuture.completedFuture(ranges);
}3. Header-Based Folding
private List<FoldingRange> findHeaderFoldingRanges(List<String> lines) {
List<FoldingRange> ranges = new ArrayList<>();
Stack<HeaderInfo> headerStack = new Stack<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.startsWith("=") && !line.startsWith("====")) {
int level = getHeaderLevel(line);
// Close all headers of same or higher level
while (!headerStack.isEmpty() && headerStack.peek().level >= level) {
HeaderInfo header = headerStack.pop();
FoldingRange range = new FoldingRange(header.startLine, i - 1);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
}
// Start new header section
headerStack.push(new HeaderInfo(level, i));
}
}
// Close remaining headers at end of document
while (!headerStack.isEmpty()) {
HeaderInfo header = headerStack.pop();
FoldingRange range = new FoldingRange(header.startLine, lines.size() - 1);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
}
return ranges;
}
private static class HeaderInfo {
int level;
int startLine;
HeaderInfo(int level, int startLine) {
this.level = level;
this.startLine = startLine;
}
}4. Block Folding
private List<FoldingRange> findBlockFoldingRanges(List<String> lines) {
List<FoldingRange> ranges = new ArrayList<>();
Map<String, Integer> blockStarts = new HashMap<>();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
// Code blocks (----)
if (line.equals("----")) {
if (blockStarts.containsKey("----")) {
FoldingRange range = new FoldingRange(blockStarts.remove("----"), i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
} else {
blockStarts.put("----", i);
}
}
// Example blocks (====)
else if (line.equals("====")) {
if (blockStarts.containsKey("====")) {
FoldingRange range = new FoldingRange(blockStarts.remove("===="), i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
} else {
blockStarts.put("====", i);
}
}
// Sidebar blocks (****)
else if (line.equals("****")) {
if (blockStarts.containsKey("****")) {
FoldingRange range = new FoldingRange(blockStarts.remove("****"), i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
} else {
blockStarts.put("****", i);
}
}
// Literal blocks (....)
else if (line.equals("....")) {
if (blockStarts.containsKey("....")) {
FoldingRange range = new FoldingRange(blockStarts.remove("...."), i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
} else {
blockStarts.put("....", i);
}
}
// Quote blocks (____)
else if (line.equals("____")) {
if (blockStarts.containsKey("____")) {
FoldingRange range = new FoldingRange(blockStarts.remove("____"), i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
} else {
blockStarts.put("____", i);
}
}
// Comment blocks (////)
else if (line.equals("////")) {
if (blockStarts.containsKey("////")) {
FoldingRange range = new FoldingRange(blockStarts.remove("////"), i);
range.setKind(FoldingRangeKind.Comment);
ranges.add(range);
} else {
blockStarts.put("////", i);
}
}
}
return ranges;
}5. List Folding
private List<FoldingRange> findListFoldingRanges(List<String> lines) {
List<FoldingRange> ranges = new ArrayList<>();
Integer listStart = null;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
boolean isList = line.startsWith("* ") ||
line.startsWith("- ") ||
line.matches("^\\d+\\.\\s+.*");
if (isList) {
if (listStart == null) {
listStart = i;
}
} else if (listStart != null && !line.isEmpty()) {
// End of list (at least 2 items)
if (i - listStart > 1) {
FoldingRange range = new FoldingRange(listStart, i - 1);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
}
listStart = null;
}
}
// Close list at end of document
if (listStart != null && lines.size() - listStart > 1) {
FoldingRange range = new FoldingRange(listStart, lines.size() - 1);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
}
return ranges;
}6. Table Folding
private List<FoldingRange> findTableFoldingRanges(List<String> lines) {
List<FoldingRange> ranges = new ArrayList<>();
Integer tableStart = null;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.equals("|===")) {
if (tableStart == null) {
tableStart = i;
} else {
// End of table
FoldingRange range = new FoldingRange(tableStart, i);
range.setKind(FoldingRangeKind.Region);
ranges.add(range);
tableStart = null;
}
}
}
return ranges;
}Testing Checklist
Header Folding
- Fold document sections by header level
- Nested sections fold correctly
- Unfolding works properly
- Test with all header levels (=, ==, ===, etc.)
Block Folding
- Code blocks (----) can be folded
- Example blocks (====) can be folded
- Sidebar blocks (****) can be folded
- Quote blocks (____) can be folded
- Comment blocks (////) can be folded
- Nested blocks handled correctly
List Folding
- Unordered lists can be folded
- Ordered lists can be folded
- Single-item lists don't create fold
- Multi-level lists fold correctly
Table Folding
- Tables (|===...===|) can be folded
- Multiple tables in document work
General
- Folding indicators appear in editor gutter
- Fold/unfold actions work via UI
- Keyboard shortcuts work (if configured)
- Performance acceptable for large documents
- Overlapping ranges handled correctly
Files to Modify
com.vogella.lsp.asciidoc.server/src/.../AsciidocLanguageServer.javacom.vogella.lsp.asciidoc.server/src/.../AsciidocTextDocumentService.java
Dependencies
- Requires: [Phase 1] Connect LSP Server to .adoc Content Type #27 (LSP connected to .adoc content type)
Success Criteria
- ✅ Headers create foldable sections
- ✅ Code blocks and other delimited blocks fold
- ✅ Lists can be folded
- ✅ Tables can be folded
- ✅ Nested structures fold correctly
- ✅ Fold/unfold works smoothly in Eclipse
- ✅ Performance acceptable
Estimated Effort
1-2 days
Priority
Medium - Nice to have, improves large document navigation
Related Issues
- Parent: Migration Plan: AsciiDoc LSP Integration #26 - Migration Plan: AsciiDoc LSP Integration
- Depends on: [Phase 1] Connect LSP Server to .adoc Content Type #27 - Connect LSP to .adoc content type
Notes
- Eclipse LSP4E should support folding ranges out of the box
- Test with large documents (100+ sections) to ensure performance
- Consider configuration options (fold by default, fold levels, etc.)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request