-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Description
Enhance the LSP server's code action support to provide useful quickfixes and refactorings for common AsciiDoc operations. Currently, code actions only detect PLACEHOLDER_TEXT. We need practical actions like fixing broken references, generating TOC, converting to code blocks, etc.
Current State
Current Code Actions:
- Detects
PLACEHOLDER_TEXTpattern - Provides quickfix to replace with
replacement_text
Missing: Practical AsciiDoc refactorings and fixes
Required Changes
1. Enhance Code Action Provider
File: AsciidocTextDocumentService.java
@Override
public CompletableFuture<List<Either<Command, CodeAction>>> codeAction(CodeActionParams params) {
String uri = params.getTextDocument().getUri();
AsciidocDocumentModel model = documentCache.get(uri);
List<Diagnostic> diagnostics = params.getContext().getDiagnostics();
if (model == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
List<Either<Command, CodeAction>> actions = new ArrayList<>();
// Actions based on diagnostics (quickfixes)
for (Diagnostic diagnostic : diagnostics) {
actions.addAll(getQuickfixActions(diagnostic, uri, model));
}
// Contextual refactorings (not tied to diagnostics)
Range range = params.getRange();
actions.addAll(getRefactoringActions(range, uri, model));
return CompletableFuture.completedFuture(actions);
}2. Quickfix Actions
private List<Either<Command, CodeAction>> getQuickfixActions(Diagnostic diagnostic,
String uri,
AsciidocDocumentModel model) {
List<Either<Command, CodeAction>> actions = new ArrayList<>();
String code = diagnostic.getCode().getLeft(); // Assuming code is string
switch (code) {
case "broken-include":
actions.add(Either.forRight(createIncludeFileAction(diagnostic, uri)));
break;
case "missing-image":
actions.add(Either.forRight(createIgnoreMissingImageAction(diagnostic)));
break;
case "broken-link":
actions.add(Either.forRight(createFixBrokenLinkAction(diagnostic, uri)));
break;
case "header-format":
actions.add(Either.forRight(createFixHeaderFormatAction(diagnostic, uri, model)));
break;
case "malformed-attribute":
actions.add(Either.forRight(createFixAttributeAction(diagnostic, uri, model)));
break;
case "unclosed-block":
actions.add(Either.forRight(createCloseBlockAction(diagnostic, uri, model)));
break;
case "undefined-attribute":
actions.add(Either.forRight(createDefineAttributeAction(diagnostic, uri, model)));
break;
}
return actions;
}3. Create Include File Action
private CodeAction createIncludeFileAction(Diagnostic diagnostic, String uri) {
CodeAction action = new CodeAction("Create missing include file");
action.setKind(CodeActionKind.QuickFix);
action.setDiagnostics(Collections.singletonList(diagnostic));
// Extract file path from diagnostic message
String message = diagnostic.getMessage();
String filePath = message.substring(message.indexOf(":") + 2);
// Create file with basic template
Path docDir = Paths.get(URI.create(uri)).getParent();
Path newFile = docDir.resolve(filePath);
WorkspaceEdit edit = new WorkspaceEdit();
// Create new file with basic content
TextDocumentEdit textEdit = new TextDocumentEdit(
new VersionedTextDocumentIdentifier(newFile.toUri().toString(), null),
Collections.singletonList(new TextEdit(
new Range(new Position(0, 0), new Position(0, 0)),
"= New Document\n\n// TODO: Add content\n"
))
);
edit.setDocumentChanges(Collections.singletonList(Either.forLeft(textEdit)));
action.setEdit(edit);
return action;
}4. Fix Header Format Action
private CodeAction createFixHeaderFormatAction(Diagnostic diagnostic, String uri,
AsciidocDocumentModel model) {
CodeAction action = new CodeAction("Add space after '='");
action.setKind(CodeActionKind.QuickFix);
action.setDiagnostics(Collections.singletonList(diagnostic));
int line = diagnostic.getRange().getStart().getLine();
String lineContent = model.getLineContent(line);
// Count leading '='
int eqCount = 0;
while (eqCount < lineContent.length() && lineContent.charAt(eqCount) == '=') {
eqCount++;
}
// Insert space after '='
String fixed = lineContent.substring(0, eqCount) + " " + lineContent.substring(eqCount);
WorkspaceEdit edit = new WorkspaceEdit();
TextEdit textEdit = new TextEdit(
new Range(new Position(line, 0), new Position(line, lineContent.length())),
fixed
);
edit.setChanges(Collections.singletonMap(uri, Collections.singletonList(textEdit)));
action.setEdit(edit);
return action;
}5. Define Attribute Action
private CodeAction createDefineAttributeAction(Diagnostic diagnostic, String uri,
AsciidocDocumentModel model) {
// Extract attribute name from diagnostic
String message = diagnostic.getMessage();
String attrName = message.substring(message.indexOf(":") + 2);
CodeAction action = new CodeAction("Define attribute '" + attrName + "'");
action.setKind(CodeActionKind.QuickFix);
action.setDiagnostics(Collections.singletonList(diagnostic));
// Find document header (after title, before first section)
int insertLine = findAttributeInsertionPoint(model);
WorkspaceEdit edit = new WorkspaceEdit();
TextEdit textEdit = new TextEdit(
new Range(new Position(insertLine, 0), new Position(insertLine, 0)),
":" + attrName + ": \n"
);
edit.setChanges(Collections.singletonMap(uri, Collections.singletonList(textEdit)));
action.setEdit(edit);
return action;
}
private int findAttributeInsertionPoint(AsciidocDocumentModel model) {
List<String> lines = model.getLines();
// Look for first section header (==)
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).trim().startsWith("==")) {
return i;
}
}
// Otherwise insert after document title
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).trim().startsWith("=")) {
return i + 1;
}
}
return 0; // Insert at top if no headers found
}6. Refactoring Actions
private List<Either<Command, CodeAction>> getRefactoringActions(Range range, String uri,
AsciidocDocumentModel model) {
List<Either<Command, CodeAction>> actions = new ArrayList<>();
int line = range.getStart().getLine();
String lineContent = model.getLineContent(line);
// Convert selection to code block
if (!lineContent.trim().startsWith("[source")) {
actions.add(Either.forRight(createConvertToCodeBlockAction(range, uri, model)));
}
// Generate table of contents
if (line == 0) {
actions.add(Either.forRight(createGenerateTocAction(uri, model)));
}
// Convert to include (if multiple lines selected)
if (range.getEnd().getLine() - range.getStart().getLine() > 5) {
actions.add(Either.forRight(createExtractToIncludeAction(range, uri, model)));
}
// Add image alt text
if (lineContent.contains("image::") && lineContent.contains("[]")) {
actions.add(Either.forRight(createAddAltTextAction(line, uri, model)));
}
return actions;
}7. Convert to Code Block Action
private CodeAction createConvertToCodeBlockAction(Range range, String uri,
AsciidocDocumentModel model) {
CodeAction action = new CodeAction("Convert to code block");
action.setKind(CodeActionKind.Refactor);
WorkspaceEdit edit = new WorkspaceEdit();
List<TextEdit> edits = new ArrayList<>();
// Insert [source] and ---- before
edits.add(new TextEdit(
new Range(new Position(range.getStart().getLine(), 0),
new Position(range.getStart().getLine(), 0)),
"[source]\n----\n"
));
// Insert ---- after
edits.add(new TextEdit(
new Range(new Position(range.getEnd().getLine() + 1, 0),
new Position(range.getEnd().getLine() + 1, 0)),
"----\n"
));
edit.setChanges(Collections.singletonMap(uri, edits));
action.setEdit(edit);
return action;
}8. Generate Table of Contents Action
private CodeAction createGenerateTocAction(String uri, AsciidocDocumentModel model) {
CodeAction action = new CodeAction("Generate table of contents");
action.setKind(CodeActionKind.Refactor);
// Build TOC from headers
StringBuilder toc = new StringBuilder("\n== Table of Contents\n\n");
List<String> lines = model.getLines();
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i).trim();
if (line.startsWith("==") && !line.startsWith("===")) {
String title = line.substring(2).trim();
toc.append("* <<").append(sanitizeAnchor(title)).append(",").append(title).append(">>\n");
}
}
toc.append("\n");
WorkspaceEdit edit = new WorkspaceEdit();
TextEdit textEdit = new TextEdit(
new Range(new Position(1, 0), new Position(1, 0)),
toc.toString()
);
edit.setChanges(Collections.singletonMap(uri, Collections.singletonList(textEdit)));
action.setEdit(edit);
return action;
}
private String sanitizeAnchor(String title) {
return title.toLowerCase()
.replaceAll("[^a-z0-9]+", "-")
.replaceAll("^-|-$", "");
}Testing Checklist
Quickfix Actions
- "Create missing include file" works
- "Add space after '='" fixes headers
- "Define attribute" inserts definition correctly
- "Close block" adds missing delimiter
- All quickfixes appear in Problems view
Refactoring Actions
- "Convert to code block" wraps selection
- "Generate TOC" creates valid TOC
- "Extract to include" creates new file
- "Add alt text" inserts placeholder
General
- Code actions appear on Ctrl+1 (Quick Assist)
- Apply action modifies document correctly
- Undo/redo works properly
- Performance acceptable
Files to Modify
com.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)
- Requires: [Phase 1] Enhance LSP Diagnostics with Broken Reference Detection #32 (Enhanced diagnostics for quickfixes)
Success Criteria
- ✅ Quickfixes for all diagnostic codes
- ✅ Useful refactoring actions
- ✅ Code actions integrate with Eclipse UI
- ✅ Actions apply correctly
- ✅ Good UX (clear action titles)
Estimated Effort
2-3 days
Priority
Medium - Enhances productivity significantly
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, [Phase 1] Enhance LSP Diagnostics with Broken Reference Detection #32
Notes
- LSP code actions map to Eclipse Quick Assist (Ctrl+1)
- WorkspaceEdit support varies in LSP4E - test thoroughly
- Consider user preferences for action behavior
- File creation actions may need special permissions
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request