Skip to main content

bbnf_analysis/features/
code_actions.rs

1use std::collections::HashMap;
2
3use ls_types::*;
4
5use crate::state::DocumentState;
6
7pub fn code_actions(
8    state: &DocumentState,
9    uri: &Uri,
10    range: Range,
11) -> CodeActionResponse {
12    let mut actions = Vec::new();
13
14    // "Remove unused rule" for rules at cursor with UNNECESSARY tag.
15    for diag in &state.info.diagnostics {
16        if diag.message.starts_with("Unused rule:") {
17            // Check if the diagnostic is in our range.
18            if diag.range.start.line >= range.start.line
19                && diag.range.end.line <= range.end.line
20            {
21                // Find the rule.
22                let name = diag
23                    .message
24                    .strip_prefix("Unused rule: `")
25                    .and_then(|s| s.strip_suffix('`'));
26                if let Some(name) = name {
27                    if let Some(&idx) = state.info.rule_index.get(name) {
28                        let rule = &state.info.rules[idx];
29
30                        // Extend full_span to include trailing whitespace/newline.
31                        let mut delete_end = rule.full_span.1;
32                        // Skip past the terminator (`;` or `.`) and any trailing whitespace.
33                        while delete_end < state.text.len() {
34                            let ch = state.text.as_bytes()[delete_end];
35                            if ch == b';' || ch == b'.' {
36                                delete_end += 1;
37                                break;
38                            }
39                            delete_end += 1;
40                        }
41                        // Also consume trailing newline.
42                        if delete_end < state.text.len()
43                            && state.text.as_bytes()[delete_end] == b'\n'
44                        {
45                            delete_end += 1;
46                        }
47
48                        let mut changes = HashMap::new();
49                        changes.insert(
50                            uri.clone(),
51                            vec![TextEdit {
52                                range: state.line_index.span_to_range(rule.full_span.0, delete_end),
53                                new_text: String::new(),
54                            }],
55                        );
56
57                        actions.push(CodeActionOrCommand::CodeAction(CodeAction {
58                            title: format!("Remove unused rule `{}`", name),
59                            kind: Some(CodeActionKind::QUICKFIX),
60                            diagnostics: Some(vec![diag.clone()]),
61                            edit: Some(WorkspaceEdit {
62                                changes: Some(changes),
63                                ..Default::default()
64                            }),
65                            ..Default::default()
66                        }));
67                    }
68                }
69            }
70        }
71    }
72
73    // "Define undefined rule" for undefined nonterminal references at cursor.
74    for diag in &state.info.diagnostics {
75        if diag.message.starts_with("Undefined rule:")
76            && diag.range.start.line >= range.start.line
77            && diag.range.end.line <= range.end.line
78        {
79                let name = diag
80                    .message
81                    .strip_prefix("Undefined rule: `")
82                    .and_then(|s| s.strip_suffix('`'));
83                if let Some(name) = name {
84                    // Insert a new rule at the end of the document.
85                    let insert_text = format!("\n{} = ;\n", name);
86                    let end_pos = state.line_index.offset_to_position(state.text.len());
87
88                    let mut changes = HashMap::new();
89                    changes.insert(
90                        uri.clone(),
91                        vec![TextEdit {
92                            range: Range::new(end_pos, end_pos),
93                            new_text: insert_text,
94                        }],
95                    );
96
97                    actions.push(CodeActionOrCommand::CodeAction(CodeAction {
98                        title: format!("Define rule `{}`", name),
99                        kind: Some(CodeActionKind::QUICKFIX),
100                        diagnostics: Some(vec![diag.clone()]),
101                        edit: Some(WorkspaceEdit {
102                            changes: Some(changes),
103                            ..Default::default()
104                        }),
105                        ..Default::default()
106                    }));
107                }
108            }
109        }
110
111    actions
112}