1use shape_runtime::metadata::MethodInfo;
4use tower_lsp_server::ls_types::{
5 CompletionItem, CompletionItemKind, Documentation, InsertTextFormat,
6};
7
8pub fn method_completion_item(method: &MethodInfo) -> CompletionItem {
9 let detail = if method.implemented {
10 method.signature.clone()
11 } else {
12 format!("{} (unimplemented)", method.signature)
13 };
14
15 let doc = if method.implemented {
16 method.description.clone()
17 } else {
18 format!("{}\n\nNOTE: Not yet implemented.", method.description)
19 };
20
21 let has_params = !method.signature.contains("()");
22 let insert_text = if has_params {
23 Some(format!("{}(${{1}})", method.name))
24 } else {
25 Some(format!("{}()", method.name))
26 };
27
28 CompletionItem {
29 label: method.name.clone(),
30 kind: Some(CompletionItemKind::METHOD),
31 detail: Some(detail),
32 documentation: Some(Documentation::String(doc)),
33 insert_text,
34 insert_text_format: Some(InsertTextFormat::SNIPPET),
35 ..CompletionItem::default()
36 }
37}
38
39pub fn extract_result_inner(type_name: &str) -> Option<String> {
41 parse_generic_type(type_name).and_then(|(base, args)| {
42 if base.eq_ignore_ascii_case("result") {
43 args.into_iter().next()
44 } else {
45 None
46 }
47 })
48}
49
50pub fn extract_option_inner(type_name: &str) -> Option<String> {
52 if let Some((base, args)) = parse_generic_type(type_name) {
54 if base.eq_ignore_ascii_case("option") {
55 return args.into_iter().next();
56 }
57 }
58 if type_name.ends_with('?') {
60 return Some(type_name[..type_name.len() - 1].to_string());
61 }
62 None
63}
64
65pub fn result_method_completions() -> Vec<CompletionItem> {
67 vec![
68 CompletionItem {
69 label: "unwrap".to_string(),
70 kind: Some(CompletionItemKind::METHOD),
71 detail: Some("T".to_string()),
72 documentation: None,
73 insert_text: Some("unwrap()".to_string()),
74 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
75 ..CompletionItem::default()
76 },
77 CompletionItem {
78 label: "unwrap_or".to_string(),
79 kind: Some(CompletionItemKind::METHOD),
80 detail: Some("T".to_string()),
81 documentation: None,
82 insert_text: Some("unwrap_or(${1:default})".to_string()),
83 insert_text_format: Some(InsertTextFormat::SNIPPET),
84 ..CompletionItem::default()
85 },
86 CompletionItem {
87 label: "is_ok".to_string(),
88 kind: Some(CompletionItemKind::METHOD),
89 detail: Some("Boolean".to_string()),
90 documentation: None,
91 insert_text: Some("is_ok()".to_string()),
92 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
93 ..CompletionItem::default()
94 },
95 CompletionItem {
96 label: "is_err".to_string(),
97 kind: Some(CompletionItemKind::METHOD),
98 detail: Some("Boolean".to_string()),
99 documentation: None,
100 insert_text: Some("is_err()".to_string()),
101 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
102 ..CompletionItem::default()
103 },
104 ]
105}
106
107pub fn option_method_completions() -> Vec<CompletionItem> {
109 vec![
110 CompletionItem {
111 label: "unwrap".to_string(),
112 kind: Some(CompletionItemKind::METHOD),
113 detail: Some("T".to_string()),
114 documentation: None,
115 insert_text: Some("unwrap()".to_string()),
116 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
117 ..CompletionItem::default()
118 },
119 CompletionItem {
120 label: "unwrap_or".to_string(),
121 kind: Some(CompletionItemKind::METHOD),
122 detail: Some("T".to_string()),
123 documentation: None,
124 insert_text: Some("unwrap_or(${1:default})".to_string()),
125 insert_text_format: Some(InsertTextFormat::SNIPPET),
126 ..CompletionItem::default()
127 },
128 CompletionItem {
129 label: "is_some".to_string(),
130 kind: Some(CompletionItemKind::METHOD),
131 detail: Some("Boolean".to_string()),
132 documentation: None,
133 insert_text: Some("is_some()".to_string()),
134 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
135 ..CompletionItem::default()
136 },
137 CompletionItem {
138 label: "is_none".to_string(),
139 kind: Some(CompletionItemKind::METHOD),
140 detail: Some("Boolean".to_string()),
141 documentation: None,
142 insert_text: Some("is_none()".to_string()),
143 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
144 ..CompletionItem::default()
145 },
146 ]
147}
148
149pub fn parse_generic_type(type_name: &str) -> Option<(String, Vec<String>)> {
150 let start = type_name.find('<')?;
151 let end = type_name.rfind('>')?;
152 if end <= start {
153 return None;
154 }
155 let base = type_name[..start].trim().to_string();
156 let inner = type_name[start + 1..end].trim();
157 if inner.is_empty() {
158 return Some((base, Vec::new()));
159 }
160 let args = split_top_level_commas(inner);
161 Some((base, args))
162}
163
164fn split_top_level_commas(input: &str) -> Vec<String> {
165 let mut args = Vec::new();
166 let mut start = 0usize;
167 let mut angle_depth = 0usize;
168 let mut paren_depth = 0usize;
169 let mut bracket_depth = 0usize;
170 let mut brace_depth = 0usize;
171
172 for (idx, ch) in input.char_indices() {
173 match ch {
174 '<' => angle_depth += 1,
175 '>' => angle_depth = angle_depth.saturating_sub(1),
176 '(' => paren_depth += 1,
177 ')' => paren_depth = paren_depth.saturating_sub(1),
178 '[' => bracket_depth += 1,
179 ']' => bracket_depth = bracket_depth.saturating_sub(1),
180 '{' => brace_depth += 1,
181 '}' => brace_depth = brace_depth.saturating_sub(1),
182 ',' => {
183 if angle_depth == 0 && paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 {
184 let part = input[start..idx].trim();
185 if !part.is_empty() {
186 args.push(part.to_string());
187 }
188 start = idx + ch.len_utf8();
189 }
190 }
191 _ => {}
192 }
193 }
194
195 let tail = input[start..].trim();
196 if !tail.is_empty() {
197 args.push(tail.to_string());
198 }
199
200 args
201}
202
203pub fn extract_generic_arg(type_name: &str, index: usize) -> Option<String> {
207 let (_, args) = parse_generic_type(type_name)?;
208 args.get(index).cloned()
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_extract_result_inner() {
217 assert_eq!(
219 extract_result_inner("Result<Instrument>"),
220 Some("Instrument".to_string())
221 );
222 assert_eq!(
223 extract_result_inner("Result<Number>"),
224 Some("Number".to_string())
225 );
226 assert_eq!(extract_result_inner("Instrument"), None);
227 assert_eq!(extract_result_inner("Option<Number>"), None);
228 }
229
230 #[test]
231 fn test_extract_option_inner() {
232 assert_eq!(
234 extract_option_inner("Option<Number>"),
235 Some("Number".to_string())
236 );
237 assert_eq!(extract_option_inner("Number?"), Some("Number".to_string()));
238 assert_eq!(extract_option_inner("Number"), None);
239 assert_eq!(extract_option_inner("Result<Number>"), None);
240 }
241
242 #[test]
243 fn test_parse_generic_type_with_structural_arg() {
244 let parsed = parse_generic_type("Table<{ open: number, close: number }>")
245 .expect("should parse generic type");
246 assert_eq!(parsed.0, "Table");
247 assert_eq!(parsed.1, vec!["{ open: number, close: number }"]);
248 }
249
250 #[test]
251 fn test_parse_generic_type_nested_args() {
252 let parsed =
253 parse_generic_type("Map<string, List<int>>").expect("should parse nested generic type");
254 assert_eq!(parsed.0, "Map");
255 assert_eq!(parsed.1, vec!["string", "List<int>"]);
256 }
257}