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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
use std::collections::HashMap;
use regex::Regex;
use tower_lsp::lsp_types::*;
use crate::backend::Backend;
pub trait TypstCodeActions {
fn get_table_parameters(&self) -> HashMap<String, String>;
fn parse_table_params(&self, content: &str) -> Vec<String>;
fn calculate_code_actions(
&self,
content: &str,
range: Range,
uri: Url,
) -> Vec<CodeActionOrCommand>;
}
/* // code action for each params for table function
#table(
columns: auto,
rows: auto,
gutter: auto,
column-gutter: auto,
row-gutter: auto,
fill: none,
align: auto,
stroke: none,
inset: relative,
)
*/
impl TypstCodeActions for Backend {
fn get_table_parameters(&self) -> HashMap<String, String> {
let mut params = HashMap::new();
params.insert("columns".to_owned(), "auto".to_owned());
params.insert("rows".to_owned(), "auto".to_owned());
params.insert("gutter".to_owned(), "auto".to_owned());
params.insert("column-gutter".to_owned(), "auto".to_owned());
params.insert("row-gutter".to_owned(), "auto".to_owned());
params.insert("fill".to_owned(), "none".to_owned());
params.insert("align".to_owned(), "auto".to_owned());
params.insert("stroke".to_owned(), "none".to_owned());
params.insert("inset".to_owned(), "relative".to_owned());
params
}
/// Parses the content inside `#table(...)` and extracts the parameters already defined.
fn parse_table_params(&self, content: &str) -> Vec<String> {
// Regular expression to find parameters (e.g., `param:`).
let re = Regex::new(r"(\w+(-\w+)?)\s*:").unwrap();
let mut existing_params = Vec::new();
for cap in re.captures_iter(content) {
if let Some(param) = cap.get(1) {
existing_params.push(param.as_str().to_owned());
}
}
existing_params
}
fn calculate_code_actions(
&self,
content: &str,
range: Range,
uri: Url,
) -> Vec<CodeActionOrCommand> {
let mut actions = Vec::new();
// Check if the text "VS Code" is within the range
let vs_code_re = Regex::new(r"VS Code").unwrap();
for (line_idx, line) in content.lines().enumerate() {
if let Some(vs_code_match) = vs_code_re.find(line) {
let start = vs_code_match.start();
let end = vs_code_match.end();
// Ensure the match is within the specified range
if line_idx == range.start.line as usize && line_idx == range.end.line as usize {
let edit = TextEdit {
range: Range {
start: Position {
line: line_idx as u32,
character: start as u32,
},
end: Position {
line: line_idx as u32,
character: end as u32,
},
},
new_text: "Neovim".to_owned(),
};
let workspace_edit = WorkspaceEdit {
changes: Some(HashMap::from([(uri.clone(), vec![edit])])),
document_changes: None,
change_annotations: None,
};
let code_action = CodeAction {
title: "Replace 'VS Code' with 'Neovim'".to_owned(),
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: None,
edit: Some(workspace_edit),
command: None,
is_preferred: Some(true),
disabled: None,
data: None,
};
// Wrap CodeAction in CodeActionOrCommand
actions.push(CodeActionOrCommand::CodeAction(code_action));
}
}
}
let mut multiline_table = String::new();
let mut in_table_block = false;
let mut table_start_line = 0;
for (line_idx, line) in content.lines().enumerate() {
// Handle multi-line `#table(...)` blocks.
if line.contains("#table(") {
in_table_block = true;
table_start_line = line_idx;
}
if in_table_block {
multiline_table.push_str(line);
multiline_table.push('\n');
if line.contains(")") {
in_table_block = false;
// Extract existing parameters inside `#table(...)`.
let existing_params: Vec<String> = self.parse_table_params(&multiline_table);
// Get all default parameters.
let all_params: HashMap<String, String> = self.get_table_parameters();
// Generate a separate code action for each missing parameter.
for (param, default_value) in all_params {
if !existing_params.contains(¶m) {
let title = format!("Add missing parameter: {}", param);
// Create a new parameter string.
let new_param = format!("{}: {},\n ", param, default_value);
// Prepare the text edit to add the missing parameter.
let edit = TextEdit {
range: Range {
start: Position {
line: table_start_line as u32 + 1,
character: 2,
// line: table_start_line as u32,
// character: line.find("#table(").unwrap_or(5) as u32 + 7, // Position after `#table(`.
},
end: Position {
line: table_start_line as u32 + 1,
character: 2,
// line: table_start_line as u32,
// character: line.find("#table(").unwrap_or(0) as u32 + 7,
},
},
new_text: new_param,
};
// Define the workspace edit for the code action.
let workspace_edit = WorkspaceEdit {
changes: Some(HashMap::from([(uri.clone(), vec![edit])])),
document_changes: None,
change_annotations: None,
};
// Create the code action for adding the missing parameter.
let code_action = CodeAction {
title,
kind: Some(CodeActionKind::QUICKFIX),
diagnostics: None,
edit: Some(workspace_edit),
command: None,
is_preferred: Some(true),
disabled: None,
data: None,
};
// Add the code action to the list.
actions.push(CodeActionOrCommand::CodeAction(code_action));
}
}
// Reset the multiline table content for the next block.
multiline_table.clear();
}
}
}
actions
}
}