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
Block Folding
List Folding
Table Folding
General
Files to Modify
com.vogella.lsp.asciidoc.server/src/.../AsciidocLanguageServer.java
com.vogella.lsp.asciidoc.server/src/.../AsciidocTextDocumentService.java
Dependencies
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
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.)
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.java2. Implement Folding Range Provider
File:
AsciidocTextDocumentService.java3. Header-Based Folding
4. Block Folding
5. List Folding
6. Table Folding
Testing Checklist
Header Folding
Block Folding
List Folding
Table Folding
General
Files to Modify
com.vogella.lsp.asciidoc.server/src/.../AsciidocLanguageServer.javacom.vogella.lsp.asciidoc.server/src/.../AsciidocTextDocumentService.javaDependencies
Success Criteria
Estimated Effort
1-2 days
Priority
Medium - Nice to have, improves large document navigation
Related Issues
Notes