perl_incremental_parsing/incremental/
incremental_integration.rs1use crate::{
7 ast::Node,
8 error::ParseResult,
9 incremental_document::{IncrementalDocument, ParseMetrics},
10 incremental_edit::{IncrementalEdit, IncrementalEditSet},
11 parser::Parser,
12};
13use ropey::Rope;
14use serde_json::Value;
15use std::sync::Arc;
16
17pub struct IncrementalConfig {
19 pub enabled: bool,
21 pub target_parse_time_ms: f64,
23 pub max_cache_size: usize,
25}
26
27impl Default for IncrementalConfig {
28 fn default() -> Self {
29 Self {
30 enabled: std::env::var("PERL_LSP_INCREMENTAL").is_ok(),
32 target_parse_time_ms: 1.0,
33 max_cache_size: 10000,
34 }
35 }
36}
37
38pub fn lsp_change_to_edit(change: &Value, rope: &Rope) -> Option<IncrementalEdit> {
40 if let Some(range) = change.get("range") {
42 let start_line = range["start"]["line"].as_u64()? as usize;
44 let start_char = range["start"]["character"].as_u64()? as usize;
45 let end_line = range["end"]["line"].as_u64()? as usize;
46 let end_char = range["end"]["character"].as_u64()? as usize;
47
48 let start_byte = lsp_pos_to_byte(rope, start_line, start_char);
50 let end_byte = lsp_pos_to_byte(rope, end_line, end_char);
51
52 let new_text = change["text"].as_str()?.to_string();
53
54 let start_position =
56 crate::position::Position::new(start_byte, start_line as u32, start_char as u32);
57 let old_end_position =
58 crate::position::Position::new(end_byte, end_line as u32, end_char as u32);
59
60 Some(IncrementalEdit::with_positions(
61 start_byte,
62 end_byte,
63 new_text,
64 start_position,
65 old_end_position,
66 ))
67 } else {
68 None
70 }
71}
72
73pub fn lsp_pos_to_byte(rope: &Rope, line: usize, character: usize) -> usize {
75 if line >= rope.len_lines() {
76 return rope.len_bytes();
77 }
78
79 let line_start = rope.line_to_byte(line);
80 let line = rope.line(line);
81
82 let mut utf16_pos = 0;
84 let mut byte_pos = 0;
85
86 for ch in line.chars() {
87 if utf16_pos >= character {
88 break;
89 }
90 utf16_pos += ch.len_utf16();
91 byte_pos += ch.len_utf8();
92 }
93
94 line_start + byte_pos
95}
96
97pub fn byte_to_lsp_pos(rope: &Rope, byte_offset: usize) -> (usize, usize) {
99 let byte_offset = byte_offset.min(rope.len_bytes());
100 let line = rope.byte_to_line(byte_offset);
101 let line_start = rope.line_to_byte(line);
102 let column_bytes = byte_offset - line_start;
103
104 let line_str = rope.line(line);
106 let mut utf16_pos = 0;
107 let mut current_bytes = 0;
108
109 for ch in line_str.chars() {
110 if current_bytes >= column_bytes {
111 break;
112 }
113 current_bytes += ch.len_utf8();
114 utf16_pos += ch.len_utf16();
115 }
116
117 (line, utf16_pos)
118}
119
120pub enum DocumentParser {
122 Full { content: String, ast: Option<Arc<Node>> },
124 Incremental { document: Box<IncrementalDocument>, rope: Rope },
126}
127
128impl DocumentParser {
129 pub fn new(content: String, config: &IncrementalConfig) -> ParseResult<Self> {
131 if config.enabled {
132 let document = IncrementalDocument::new(content.clone())?;
134 let rope = Rope::from_str(&content);
135 Ok(DocumentParser::Incremental { document: Box::new(document), rope })
136 } else {
137 let mut parser = Parser::new(&content);
139 let ast = parser.parse().ok().map(Arc::new);
140 Ok(DocumentParser::Full { content, ast })
141 }
142 }
143
144 pub fn apply_changes(
146 &mut self,
147 changes: &[Value],
148 _config: &IncrementalConfig,
149 ) -> ParseResult<()> {
150 match self {
151 DocumentParser::Full { content, ast } => {
152 if let Some(change) = changes.first() {
154 if let Some(text) = change["text"].as_str() {
155 *content = text.to_string();
156 let mut parser = Parser::new(content);
157 *ast = parser.parse().ok().map(Arc::new);
158 }
159 }
160 Ok(())
161 }
162 DocumentParser::Incremental { document: boxed_document, rope } => {
163 let document = boxed_document.as_mut();
164 let mut edits = Vec::new();
166
167 for change in changes {
168 if let Some(edit) = lsp_change_to_edit(change, rope) {
169 edits.push(edit);
170 } else {
171 if let Some(text) = change["text"].as_str() {
173 *document = IncrementalDocument::new(text.to_string())?;
174 *rope = Rope::from_str(text);
175 return Ok(());
176 }
177 }
178 }
179
180 if !edits.is_empty() {
181 let edit_set = IncrementalEditSet { edits };
183 document.apply_edits(&edit_set)?;
184
185 *rope = Rope::from_str(&document.source);
187 }
188
189 Ok(())
190 }
191 }
192 }
193
194 pub fn ast(&self) -> Option<Arc<Node>> {
196 match self {
197 DocumentParser::Full { ast, .. } => ast.clone(),
198 DocumentParser::Incremental { document, .. } => Some(document.root.clone()),
199 }
200 }
201
202 pub fn content(&self) -> &str {
204 match self {
205 DocumentParser::Full { content, .. } => content,
206 DocumentParser::Incremental { document, .. } => &document.source,
207 }
208 }
209
210 pub fn metrics(&self) -> Option<&ParseMetrics> {
212 match self {
213 DocumentParser::Full { .. } => None,
214 DocumentParser::Incremental { document, .. } => Some(document.metrics()),
215 }
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_lsp_pos_to_byte() {
225 let text = "Hello\nWorld\n";
226 let rope = Rope::from_str(text);
227
228 assert_eq!(lsp_pos_to_byte(&rope, 0, 0), 0);
230
231 assert_eq!(lsp_pos_to_byte(&rope, 1, 0), 6);
233
234 assert_eq!(lsp_pos_to_byte(&rope, 1, 3), 9);
236 }
237
238 #[test]
239 fn test_byte_to_lsp_pos() {
240 let text = "Hello\nWorld\n";
241 let rope = Rope::from_str(text);
242
243 assert_eq!(byte_to_lsp_pos(&rope, 0), (0, 0));
245
246 assert_eq!(byte_to_lsp_pos(&rope, 6), (1, 0));
248
249 assert_eq!(byte_to_lsp_pos(&rope, 9), (1, 3));
251 }
252
253 #[test]
254 fn test_crlf_handling() {
255 let text = "Hello\r\nWorld\r\n";
256 let rope = Rope::from_str(text);
257
258 assert_eq!(lsp_pos_to_byte(&rope, 1, 0), 7);
260 assert_eq!(byte_to_lsp_pos(&rope, 7), (1, 0));
261 }
262
263 #[test]
264 fn test_utf16_handling() {
265 let text = "Hello 😀 World"; let rope = Rope::from_str(text);
267
268 let byte_after_emoji = "Hello 😀".len();
270 let (line, char) = byte_to_lsp_pos(&rope, byte_after_emoji);
271 assert_eq!(line, 0);
272 assert_eq!(char, 8); }
274}