Skip to content

Commit bf20dcd

Browse files
authored
feat: code action to prefix caps with underscore (ribru17#122)
1 parent 82d6955 commit bf20dcd

3 files changed

Lines changed: 101 additions & 9 deletions

File tree

src/handlers/code_action.rs

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,22 @@ use tower_lsp::{
88
Diagnostic, Position, Range, TextEdit, Url, WorkspaceEdit,
99
},
1010
};
11+
use tree_sitter::QueryCursor;
1112

12-
use crate::Backend;
13+
use crate::{
14+
Backend,
15+
util::{
16+
CAPTURES_QUERY, NodeUtil, TextProviderRope, ToTsPoint, get_current_capture_node,
17+
get_references,
18+
},
19+
};
1320

1421
#[repr(u8)]
1522
#[derive(Serialize, Deserialize, Debug, Clone)]
1623
#[serde(into = "u8", try_from = "u8")]
1724
pub enum CodeActions {
1825
RemoveBackslash,
26+
PrefixUnderscore,
1927
}
2028

2129
impl From<CodeActions> for u8 {
@@ -30,12 +38,17 @@ impl TryFrom<u8> for CodeActions {
3038
fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
3139
match value {
3240
0 => Ok(CodeActions::RemoveBackslash),
41+
1 => Ok(CodeActions::PrefixUnderscore),
3342
_ => Err("Invalid value"),
3443
}
3544
}
3645
}
3746

38-
fn diag_to_code_action(diagnostic: Diagnostic, uri: &Url) -> Option<CodeActionOrCommand> {
47+
fn diag_to_code_action(
48+
backend: &Backend,
49+
diagnostic: Diagnostic,
50+
uri: &Url,
51+
) -> Option<CodeActionOrCommand> {
3952
match serde_json::from_value::<CodeActions>(diagnostic.data.clone()?) {
4053
Ok(CodeActions::RemoveBackslash) => Some(CodeActionOrCommand::CodeAction(CodeAction {
4154
title: String::from("Remove unnecessary backslash"),
@@ -60,20 +73,60 @@ fn diag_to_code_action(diagnostic: Diagnostic, uri: &Url) -> Option<CodeActionOr
6073
diagnostics: Some(vec![diagnostic]),
6174
..Default::default()
6275
})),
76+
Ok(CodeActions::PrefixUnderscore) => {
77+
let tree = backend.document_map.get(uri)?.tree.clone();
78+
let root = tree.root_node();
79+
let rope = backend.document_map.get(uri)?.rope.clone();
80+
let current_node =
81+
get_current_capture_node(root, diagnostic.range.start.to_ts_point(&rope))?;
82+
let mut cursor = QueryCursor::new();
83+
let provider = TextProviderRope(&rope);
84+
let refs = get_references(
85+
&root,
86+
&current_node,
87+
&CAPTURES_QUERY,
88+
&mut cursor,
89+
&provider,
90+
&rope,
91+
);
92+
let edits = refs
93+
.into_iter()
94+
.map(|node| {
95+
let mut range = node.lsp_range(&rope);
96+
range.start.character += 1;
97+
range.end.character = range.start.character;
98+
TextEdit {
99+
new_text: String::from("_"),
100+
range,
101+
}
102+
})
103+
.collect();
104+
Some(CodeActionOrCommand::CodeAction(CodeAction {
105+
title: String::from("Prefix capture name with underscore"),
106+
kind: Some(CodeActionKind::QUICKFIX),
107+
is_preferred: Some(true),
108+
edit: Some(WorkspaceEdit {
109+
changes: Some(HashMap::from([(uri.clone(), edits)])),
110+
..Default::default()
111+
}),
112+
diagnostics: Some(vec![diagnostic]),
113+
..Default::default()
114+
}))
115+
}
63116
_ => None,
64117
}
65118
}
66119

67120
pub async fn code_action(
68-
_backend: &Backend,
121+
backend: &Backend,
69122
params: CodeActionParams,
70123
) -> Result<Option<CodeActionResponse>> {
71124
let uri = &params.text_document.uri;
72125
let diagnostics = params.context.diagnostics;
73126

74127
let actions: Vec<CodeActionOrCommand> = diagnostics
75128
.into_iter()
76-
.filter_map(|diagnostic| diag_to_code_action(diagnostic, uri))
129+
.filter_map(|diagnostic| diag_to_code_action(backend, diagnostic, uri))
77130
.collect();
78131

79132
if actions.is_empty() {
@@ -136,6 +189,40 @@ mod test {
136189
}),
137190
..Default::default()
138191
})])]
192+
#[case(r#"((comment) @jsdoc_comment
193+
(#lua-match? @jsdoc_comment ".*"))"#, Default::default(), Position::new(0, 15), CodeActionContext {
194+
diagnostics: vec![Diagnostic {
195+
message: String::from("bad cap"),
196+
range: Range::new(Position::new(0, 11), Position::new(0, 24)),
197+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
198+
..Default::default()
199+
}],
200+
..Default::default()
201+
}, &[CodeActionOrCommand::CodeAction(CodeAction {
202+
title: String::from("Prefix capture name with underscore"),
203+
kind: Some(CodeActionKind::QUICKFIX),
204+
is_preferred: Some(true),
205+
diagnostics: Some(vec![Diagnostic {
206+
message: String::from("bad cap"),
207+
range: Range::new(Position::new(0, 11), Position::new(0, 24)),
208+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
209+
..Default::default()
210+
}]),
211+
edit: Some(WorkspaceEdit {
212+
changes: Some(HashMap::from([(
213+
TEST_URI.clone(),
214+
vec![TextEdit {
215+
range: Range::new(Position::new(0, 12), Position::new(0, 12)),
216+
new_text: String::from("_")
217+
}, TextEdit {
218+
range: Range::new(Position::new(1, 16), Position::new(1, 16)),
219+
new_text: String::from("_")
220+
}]
221+
), ])),
222+
..Default::default()
223+
}),
224+
..Default::default()
225+
})])]
139226
#[tokio::test(flavor = "current_thread")]
140227
async fn server_code_action(
141228
#[case] source: &str,

src/handlers/diagnostic.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,12 @@ pub async fn get_diagnostics(
324324
.is_some_and(|c| !c.contains_key(&String::from(suffix)))
325325
{
326326
diagnostics.push(Diagnostic {
327-
message: format!("Unsupported capture name \"{capture_text}\", consider prefixing with '_'"),
327+
message: format!(
328+
"Unsupported capture name \"{capture_text}\" (fix available)"
329+
),
328330
severity: Some(DiagnosticSeverity::WARNING),
329331
range,
332+
data: serde_json::to_value(CodeActions::PrefixUnderscore).ok(),
330333
..Default::default()
331334
});
332335
}
@@ -365,7 +368,7 @@ pub async fn get_diagnostics(
365368
message: String::from("Unnecessary escape sequence (fix available)"),
366369
severity: Some(DiagnosticSeverity::WARNING),
367370
range,
368-
data: serde_json::to_value(CodeActions::RemoveBackslash as u8).ok(),
371+
data: serde_json::to_value(CodeActions::RemoveBackslash).ok(),
369372
..Default::default()
370373
});
371374
}
@@ -490,6 +493,7 @@ mod test {
490493
Options, Predicate, PredicateParameter, PredicateParameterArity, PredicateParameterType,
491494
};
492495

496+
use crate::handlers::code_action::CodeActions;
493497
use crate::{
494498
SymbolInfo,
495499
handlers::diagnostic::get_diagnostics,
@@ -523,7 +527,8 @@ mod test {
523527
},
524528
},
525529
severity: Some(DiagnosticSeverity::WARNING),
526-
message: String::from("Unsupported capture name \"@constant\", consider prefixing with '_'"),
530+
message: String::from("Unsupported capture name \"@constant\" (fix available)"),
531+
data: Some(serde_json::to_value(CodeActions::PrefixUnderscore).unwrap()),
527532
..Default::default()
528533
}, Diagnostic {
529534
range: Range {
@@ -965,7 +970,7 @@ mod test {
965970
},
966971
severity: Some(DiagnosticSeverity::WARNING),
967972
message: String::from("Unnecessary escape sequence (fix available)"),
968-
data: Some(serde_json::to_value(0).unwrap()),
973+
data: Some(serde_json::to_value(CodeActions::RemoveBackslash).unwrap()),
969974
..Default::default()
970975
}],
971976
)]

tests/lint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ mod test {
4040
#[rstest]
4141
#[case(
4242
concat!(env!("CARGO_MANIFEST_DIR"), "/queries/formatting_test_files/after_trailing_whitespace.scm"),
43-
Some(["Unsupported capture name \"@cap\", consider prefixing with '_'"].as_slice())
43+
Some(["Unsupported capture name \"@cap\" (fix available)"].as_slice())
4444
)]
4545
#[case(
4646
concat!(env!("CARGO_MANIFEST_DIR"), "/queries/formatting_test_files/before_predicates.scm"),

0 commit comments

Comments
 (0)