Skip to main content

merman_editor_core/
code_actions.rs

1use crate::types::{Position, Range};
2use merman_analysis::{DiagnosticFix, DiagnosticFixEdit};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct EditorCodeAction {
6    pub title: String,
7    pub edits: Vec<EditorCodeActionEdit>,
8    pub is_preferred: bool,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct EditorCodeActionEdit {
13    pub range: Range,
14    pub new_text: String,
15}
16
17pub fn code_actions_from_fixes<'a>(
18    fixes: impl IntoIterator<Item = &'a DiagnosticFix>,
19) -> Vec<EditorCodeAction> {
20    fixes.into_iter().filter_map(code_action_from_fix).collect()
21}
22
23pub fn code_action_from_fix(fix: &DiagnosticFix) -> Option<EditorCodeAction> {
24    let mut edits = fix
25        .edits
26        .iter()
27        .map(code_action_edit_from_fix_edit)
28        .collect::<Vec<_>>();
29    if edits.is_empty() {
30        return None;
31    }
32
33    edits.sort_by_key(|edit| range_sort_key(edit.range));
34    if has_overlapping_edits(&edits) {
35        return None;
36    }
37
38    Some(EditorCodeAction {
39        title: fix.title.clone(),
40        edits,
41        is_preferred: fix.is_preferred,
42    })
43}
44
45fn code_action_edit_from_fix_edit(edit: &DiagnosticFixEdit) -> EditorCodeActionEdit {
46    EditorCodeActionEdit {
47        range: Range::new(
48            Position::new(
49                edit.span.lsp_range.start.line,
50                edit.span.lsp_range.start.character,
51            ),
52            Position::new(
53                edit.span.lsp_range.end.line,
54                edit.span.lsp_range.end.character,
55            ),
56        ),
57        new_text: edit.replacement.clone(),
58    }
59}
60
61fn has_overlapping_edits(edits: &[EditorCodeActionEdit]) -> bool {
62    edits.windows(2).any(|window| {
63        let [left, right] = window else {
64            return false;
65        };
66        position_key(left.range.end) > position_key(right.range.start)
67    })
68}
69
70fn range_sort_key(range: Range) -> (usize, usize, usize, usize) {
71    (
72        range.start.line,
73        range.start.character,
74        range.end.line,
75        range.end.character,
76    )
77}
78
79fn position_key(position: Position) -> (usize, usize) {
80    (position.line, position.character)
81}