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