use agnix_core::Fix;
use std::collections::HashMap;
use tower_lsp::lsp_types::{CodeAction, CodeActionKind, TextEdit, Url, WorkspaceEdit};
use crate::position::byte_range_to_lsp_range;
pub fn fix_to_code_action(uri: &Url, fix: &Fix, content: &str) -> CodeAction {
let range = byte_range_to_lsp_range(content, fix.start_byte, fix.end_byte);
let text_edit = TextEdit {
range,
new_text: fix.replacement.clone(),
};
let mut changes = HashMap::new();
changes.insert(uri.clone(), vec![text_edit]);
let workspace_edit = WorkspaceEdit {
changes: Some(changes),
document_changes: None,
change_annotations: None,
};
CodeAction {
title: fix.description.clone(),
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: None,
edit: Some(workspace_edit),
command: None,
is_preferred: Some(fix.safe),
disabled: None,
data: None,
}
}
pub fn fixes_to_code_actions(uri: &Url, fixes: &[Fix], content: &str) -> Vec<CodeAction> {
fixes
.iter()
.map(|fix| fix_to_code_action(uri, fix, content))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn make_fix(start: usize, end: usize, replacement: &str, description: &str, safe: bool) -> Fix {
Fix {
start_byte: start,
end_byte: end,
replacement: replacement.to_string(),
description: description.to_string(),
safe,
}
}
#[test]
fn test_fix_to_code_action_basic() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "name: Invalid Name";
let fix = make_fix(6, 18, "valid-name", "Replace with valid name", true);
let action = fix_to_code_action(&uri, &fix, content);
assert_eq!(action.title, "Replace with valid name");
assert_eq!(action.kind, Some(CodeActionKind::QUICKFIX));
assert_eq!(action.is_preferred, Some(true));
assert!(action.edit.is_some());
let edit = action.edit.unwrap();
let changes = edit.changes.unwrap();
let edits = changes.get(&uri).unwrap();
assert_eq!(edits.len(), 1);
assert_eq!(edits[0].new_text, "valid-name");
}
#[test]
fn test_fix_to_code_action_unsafe() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "hello";
let fix = make_fix(0, 5, "world", "Replace hello with world", false);
let action = fix_to_code_action(&uri, &fix, content);
assert_eq!(action.is_preferred, Some(false));
}
#[test]
fn test_fix_to_code_action_insertion() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "name: test";
let fix = make_fix(10, 10, "\nversion: 1.0.0", "Add version field", true);
let action = fix_to_code_action(&uri, &fix, content);
let edit = action.edit.unwrap();
let changes = edit.changes.unwrap();
let edits = changes.get(&uri).unwrap();
assert_eq!(edits[0].range.start, edits[0].range.end);
assert_eq!(edits[0].new_text, "\nversion: 1.0.0");
}
#[test]
fn test_fix_to_code_action_deletion() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "hello world";
let fix = make_fix(5, 11, "", "Remove world", true);
let action = fix_to_code_action(&uri, &fix, content);
let edit = action.edit.unwrap();
let changes = edit.changes.unwrap();
let edits = changes.get(&uri).unwrap();
assert_eq!(edits[0].new_text, "");
}
#[test]
fn test_fixes_to_code_actions_multiple() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "hello\nworld";
let fixes = vec![
make_fix(0, 5, "hi", "Replace hello", true),
make_fix(6, 11, "earth", "Replace world", false),
];
let actions = fixes_to_code_actions(&uri, &fixes, content);
assert_eq!(actions.len(), 2);
assert_eq!(actions[0].title, "Replace hello");
assert_eq!(actions[0].is_preferred, Some(true));
assert_eq!(actions[1].title, "Replace world");
assert_eq!(actions[1].is_preferred, Some(false));
}
#[test]
fn test_fixes_to_code_actions_empty() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "hello";
let fixes: Vec<Fix> = vec![];
let actions = fixes_to_code_actions(&uri, &fixes, content);
assert!(actions.is_empty());
}
#[test]
fn test_fix_to_code_action_multiline() {
let uri = Url::parse("file:///test.md").unwrap();
let content = "---\nname: test\n---";
let fix = make_fix(0, 18, "---\nname: fixed\n---", "Fix frontmatter", true);
let action = fix_to_code_action(&uri, &fix, content);
let edit = action.edit.unwrap();
let changes = edit.changes.unwrap();
let edits = changes.get(&uri).unwrap();
assert_eq!(edits[0].range.start.line, 0);
assert_eq!(edits[0].range.start.character, 0);
assert_eq!(edits[0].range.end.line, 2);
assert_eq!(edits[0].range.end.character, 3);
}
}