solscript_lsp/
document.rs1use ropey::Rope;
4use solscript_ast::Program;
5
6pub struct Document {
8 pub text: String,
10 pub rope: Rope,
12 pub version: i32,
14 pub ast: Option<Program>,
16 pub parse_errors: Vec<String>,
18 pub type_errors: Vec<solscript_typeck::TypeError>,
20}
21
22impl Document {
23 pub fn new(text: String, version: i32) -> Self {
25 let rope = Rope::from_str(&text);
26 let mut doc = Self {
27 text: text.clone(),
28 rope,
29 version,
30 ast: None,
31 parse_errors: Vec::new(),
32 type_errors: Vec::new(),
33 };
34 doc.analyze();
35 doc
36 }
37
38 pub fn update(&mut self, text: String, version: i32) {
40 self.text = text.clone();
41 self.rope = Rope::from_str(&text);
42 self.version = version;
43 self.analyze();
44 }
45
46 fn analyze(&mut self) {
48 self.parse_errors.clear();
49 self.type_errors.clear();
50 self.ast = None;
51
52 match solscript_parser::parse(&self.text) {
54 Ok(program) => {
55 if let Err(errors) = solscript_typeck::typecheck(&program, &self.text) {
57 self.type_errors = errors;
58 }
59 self.ast = Some(program);
60 }
61 Err(e) => {
62 self.parse_errors.push(format!("{:?}", e));
63 }
64 }
65 }
66
67 pub fn offset_at(&self, line: u32, character: u32) -> Option<usize> {
69 let line_idx = line as usize;
70 if line_idx >= self.rope.len_lines() {
71 return None;
72 }
73
74 let line_start = self.rope.line_to_byte(line_idx);
75 let line_text = self.rope.line(line_idx);
76 let char_offset = (character as usize).min(line_text.len_chars());
77
78 Some(line_start + char_offset)
79 }
80
81 pub fn position_at(&self, offset: usize) -> (u32, u32) {
83 let line = self.rope.byte_to_line(offset);
84 let line_start = self.rope.line_to_byte(line);
85 let character = offset - line_start;
86 (line as u32, character as u32)
87 }
88
89 pub fn word_at(&self, line: u32, character: u32) -> Option<String> {
91 let offset = self.offset_at(line, character)?;
92
93 let bytes = self.text.as_bytes();
95 let mut start = offset;
96 let mut end = offset;
97
98 while start > 0 && is_identifier_char(bytes[start - 1] as char) {
100 start -= 1;
101 }
102
103 while end < bytes.len() && is_identifier_char(bytes[end] as char) {
105 end += 1;
106 }
107
108 if start < end {
109 Some(self.text[start..end].to_string())
110 } else {
111 None
112 }
113 }
114
115 pub fn line_text(&self, line: u32) -> Option<String> {
117 let line_idx = line as usize;
118 if line_idx >= self.rope.len_lines() {
119 return None;
120 }
121 Some(self.rope.line(line_idx).to_string())
122 }
123}
124
125fn is_identifier_char(c: char) -> bool {
126 c.is_alphanumeric() || c == '_'
127}