use super::*;
pub fn compute_code_actions(
text: &str,
path: &std::path::Path,
lint: &LintConfig,
uri: &Uri,
range: Range,
) -> CodeActionResponse {
let diagnostics = crate::linter::check_document(path, text, lint).unwrap_or_default();
code_actions_from_findings(&diagnostics, text, uri, range)
}
pub(crate) fn code_actions_from_findings(
findings: &[Diagnostic],
text: &str,
uri: &Uri,
range: Range,
) -> CodeActionResponse {
let line_index = LineIndex::new(text);
findings
.iter()
.filter_map(|d| {
let fix = d.fix.as_ref()?;
let diag_range = Range {
start: line_index.byte_to_position(u32::from(d.range.start()) as usize),
end: line_index.byte_to_position(u32::from(d.range.end()) as usize),
};
if !ranges_overlap(diag_range, range) {
return None;
}
let edit = TextEdit {
range: Range {
start: line_index.byte_to_position(fix.start),
end: line_index.byte_to_position(fix.end),
},
new_text: fix.content.clone(),
};
let mut changes = HashMap::new();
changes.insert(uri.clone(), vec![edit]);
Some(CodeActionOrCommand::CodeAction(CodeAction {
title: fix.description.clone(),
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: Some(vec![to_lsp_diagnostic(d, &line_index)]),
edit: Some(WorkspaceEdit {
changes: Some(changes),
..Default::default()
}),
..Default::default()
}))
})
.collect()
}
pub(crate) fn ranges_overlap(a: Range, b: Range) -> bool {
!(position_lt(a.end, b.start) || position_lt(b.end, a.start))
}
pub(crate) fn position_lt(a: Position, b: Position) -> bool {
(a.line, a.character) < (b.line, b.character)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn code_action_offers_quickfix_for_diagnostic_in_range() {
let src = "if (x = 1) print(x)\n";
let actions = compute_code_actions(
src,
test_path(),
&LintConfig::default(),
&test_uri(),
full_line_0(),
);
let CodeActionOrCommand::CodeAction(action) = actions
.iter()
.find(|a| matches!(a, CodeActionOrCommand::CodeAction(a) if a.title.contains("==")))
.expect("an `=` → `==` quick-fix")
else {
unreachable!()
};
assert_eq!(action.kind, Some(CodeActionKind::QUICKFIX));
let changes = action
.edit
.as_ref()
.and_then(|e| e.changes.as_ref())
.expect("workspace edit with changes");
let edits = changes.get(&test_uri()).expect("edits for our uri");
assert_eq!(edits.len(), 1);
assert_eq!(edits[0].new_text, "==");
assert_eq!(edits[0].range.start.line, 0);
}
#[test]
fn code_action_empty_when_range_misses_diagnostics() {
let src = "if (x = 1) print(x)\n";
let far = Range {
start: pos(5, 0),
end: pos(5, 0),
};
let actions =
compute_code_actions(src, test_path(), &LintConfig::default(), &test_uri(), far);
assert!(actions.is_empty(), "expected no actions, got {actions:?}");
}
}