typst_analyzer/code_actions/
handle.rs1use std::collections::HashMap;
2
3use regex::Regex;
4use tower_lsp::lsp_types::*;
5
6use crate::backend::Backend;
7
8pub trait TypstCodeActions {
9 fn get_table_parameters(&self) -> HashMap<String, String>;
10 fn parse_table_params(&self, content: &str) -> Vec<String>;
11 fn calculate_code_actions(
12 &self,
13 content: &str,
14 range: Range,
15 uri: Url,
16 ) -> Vec<CodeActionOrCommand>;
17}
18
19impl TypstCodeActions for Backend {
33 fn get_table_parameters(&self) -> HashMap<String, String> {
34 let mut params = HashMap::new();
35 params.insert("columns".to_owned(), "auto".to_owned());
36 params.insert("rows".to_owned(), "auto".to_owned());
37 params.insert("gutter".to_owned(), "auto".to_owned());
38 params.insert("column-gutter".to_owned(), "auto".to_owned());
39 params.insert("row-gutter".to_owned(), "auto".to_owned());
40 params.insert("fill".to_owned(), "none".to_owned());
41 params.insert("align".to_owned(), "auto".to_owned());
42 params.insert("stroke".to_owned(), "none".to_owned());
43 params.insert("inset".to_owned(), "relative".to_owned());
44 params
45 }
46
47 fn parse_table_params(&self, content: &str) -> Vec<String> {
49 let re = Regex::new(r"(\w+(-\w+)?)\s*:").unwrap();
51 let mut existing_params = Vec::new();
52
53 for cap in re.captures_iter(content) {
54 if let Some(param) = cap.get(1) {
55 existing_params.push(param.as_str().to_owned());
56 }
57 }
58 existing_params
59 }
60
61 fn calculate_code_actions(
62 &self,
63 content: &str,
64 range: Range,
65 uri: Url,
66 ) -> Vec<CodeActionOrCommand> {
67 let mut actions = Vec::new();
68
69 let vs_code_re = Regex::new(r"VS Code").unwrap();
71
72 for (line_idx, line) in content.lines().enumerate() {
73 if let Some(vs_code_match) = vs_code_re.find(line) {
74 let start = vs_code_match.start();
75 let end = vs_code_match.end();
76
77 if line_idx == range.start.line as usize && line_idx == range.end.line as usize {
79 let edit = TextEdit {
80 range: Range {
81 start: Position {
82 line: line_idx as u32,
83 character: start as u32,
84 },
85 end: Position {
86 line: line_idx as u32,
87 character: end as u32,
88 },
89 },
90 new_text: "Neovim".to_owned(),
91 };
92
93 let workspace_edit = WorkspaceEdit {
94 changes: Some(HashMap::from([(uri.clone(), vec![edit])])),
95 document_changes: None,
96 change_annotations: None,
97 };
98
99 let code_action = CodeAction {
100 title: "Replace 'VS Code' with 'Neovim'".to_owned(),
101 kind: Some(CodeActionKind::QUICKFIX),
102 diagnostics: None,
103 edit: Some(workspace_edit),
104 command: None,
105 is_preferred: Some(true),
106 disabled: None,
107 data: None,
108 };
109
110 actions.push(CodeActionOrCommand::CodeAction(code_action));
112 }
113 }
114 }
115
116 let mut multiline_table = String::new();
117 let mut in_table_block = false;
118 let mut table_start_line = 0;
119
120 for (line_idx, line) in content.lines().enumerate() {
121 if line.contains("#table(") {
123 in_table_block = true;
124 table_start_line = line_idx;
125 }
126
127 if in_table_block {
128 multiline_table.push_str(line);
129 multiline_table.push('\n');
130
131 if line.contains(")") {
132 in_table_block = false;
133
134 let existing_params: Vec<String> = self.parse_table_params(&multiline_table);
136
137 let all_params: HashMap<String, String> = self.get_table_parameters();
139
140 for (param, default_value) in all_params {
142 if !existing_params.contains(¶m) {
143 let title = format!("Add missing parameter: {}", param);
144
145 let new_param = format!("{}: {},\n ", param, default_value);
147 let edit = TextEdit {
149 range: Range {
150 start: Position {
151 line: table_start_line as u32 + 1,
152 character: 2,
153 },
156 end: Position {
157 line: table_start_line as u32 + 1,
158 character: 2,
159 },
162 },
163 new_text: new_param,
164 };
165
166 let workspace_edit = WorkspaceEdit {
168 changes: Some(HashMap::from([(uri.clone(), vec![edit])])),
169 document_changes: None,
170 change_annotations: None,
171 };
172
173 let code_action = CodeAction {
175 title,
176 kind: Some(CodeActionKind::QUICKFIX),
177 diagnostics: None,
178 edit: Some(workspace_edit),
179 command: None,
180 is_preferred: Some(true),
181 disabled: None,
182 data: None,
183 };
184
185 actions.push(CodeActionOrCommand::CodeAction(code_action));
187 }
188 }
189
190 multiline_table.clear();
192 }
193 }
194 }
195
196 actions
197 }
198}