|
| 1 | +use anyhow::{anyhow, Result}; |
| 2 | +use std::collections::HashMap; |
| 3 | +use tower_lsp::lsp_types::{ |
| 4 | + DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, Position, |
| 5 | + Range, |
| 6 | +}; |
| 7 | + |
| 8 | +#[derive(Debug)] |
| 9 | +pub struct Store { |
| 10 | + documents: HashMap<String, TextDocument>, |
| 11 | + versions: HashMap<String, i32>, |
| 12 | +} |
| 13 | + |
| 14 | +impl Store { |
| 15 | + pub fn new() -> Self { |
| 16 | + Self { |
| 17 | + documents: HashMap::new(), |
| 18 | + versions: HashMap::new(), |
| 19 | + } |
| 20 | + } |
| 21 | + |
| 22 | + pub fn handle_did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> { |
| 23 | + let document = TextDocument::new( |
| 24 | + String::from(params.text_document.uri), |
| 25 | + params.text_document.text, |
| 26 | + params.text_document.version, |
| 27 | + params.text_document.language_id, |
| 28 | + ); |
| 29 | + |
| 30 | + self.add_document(document); |
| 31 | + |
| 32 | + Ok(()) |
| 33 | + } |
| 34 | + |
| 35 | + pub fn handle_did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> { |
| 36 | + let uri = params.text_document.uri.as_str().to_string(); |
| 37 | + let version = params.text_document.version; |
| 38 | + |
| 39 | + let document = self |
| 40 | + .get_document_mut(&uri) |
| 41 | + .ok_or_else(|| anyhow!("Document not found: {}", uri))?; |
| 42 | + |
| 43 | + for change in params.content_changes { |
| 44 | + if let Some(range) = change.range { |
| 45 | + document.apply_change(range, &change.text)?; |
| 46 | + } else { |
| 47 | + // Full document update |
| 48 | + document.set_content(change.text); |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + document.version = version; |
| 53 | + self.versions.insert(uri, version); |
| 54 | + |
| 55 | + Ok(()) |
| 56 | + } |
| 57 | + |
| 58 | + pub fn handle_did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> { |
| 59 | + self.remove_document(&String::from(params.text_document.uri)); |
| 60 | + |
| 61 | + Ok(()) |
| 62 | + } |
| 63 | + |
| 64 | + fn add_document(&mut self, document: TextDocument) { |
| 65 | + let uri = document.uri.clone(); |
| 66 | + let version = document.version; |
| 67 | + |
| 68 | + self.documents.insert(uri.clone(), document); |
| 69 | + self.versions.insert(uri, version); |
| 70 | + } |
| 71 | + |
| 72 | + fn remove_document(&mut self, uri: &str) { |
| 73 | + self.documents.remove(uri); |
| 74 | + self.versions.remove(uri); |
| 75 | + } |
| 76 | + |
| 77 | + fn get_document(&self, uri: &str) -> Option<&TextDocument> { |
| 78 | + self.documents.get(uri) |
| 79 | + } |
| 80 | + |
| 81 | + fn get_document_mut(&mut self, uri: &str) -> Option<&mut TextDocument> { |
| 82 | + self.documents.get_mut(uri) |
| 83 | + } |
| 84 | + |
| 85 | + pub fn get_all_documents(&self) -> impl Iterator<Item = &TextDocument> { |
| 86 | + self.documents.values() |
| 87 | + } |
| 88 | + |
| 89 | + pub fn get_documents_by_language( |
| 90 | + &self, |
| 91 | + language_id: LanguageId, |
| 92 | + ) -> impl Iterator<Item = &TextDocument> { |
| 93 | + self.documents |
| 94 | + .values() |
| 95 | + .filter(move |doc| doc.language_id == language_id) |
| 96 | + } |
| 97 | + |
| 98 | + pub fn get_version(&self, uri: &str) -> Option<i32> { |
| 99 | + self.versions.get(uri).copied() |
| 100 | + } |
| 101 | + |
| 102 | + pub fn is_version_valid(&self, uri: &str, version: i32) -> bool { |
| 103 | + self.get_version(uri).map_or(false, |v| v == version) |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +#[derive(Clone, Debug)] |
| 108 | +pub struct TextDocument { |
| 109 | + uri: String, |
| 110 | + contents: String, |
| 111 | + index: LineIndex, |
| 112 | + version: i32, |
| 113 | + language_id: LanguageId, |
| 114 | +} |
| 115 | + |
| 116 | +impl TextDocument { |
| 117 | + fn new(uri: String, contents: String, version: i32, language_id: String) -> Self { |
| 118 | + let index = LineIndex::new(&contents); |
| 119 | + Self { |
| 120 | + uri, |
| 121 | + contents, |
| 122 | + index, |
| 123 | + version, |
| 124 | + language_id: LanguageId::from(language_id), |
| 125 | + } |
| 126 | + } |
| 127 | + |
| 128 | + pub fn apply_change(&mut self, range: Range, new_text: &str) -> Result<()> { |
| 129 | + let start_offset = self |
| 130 | + .index |
| 131 | + .offset(range.start) |
| 132 | + .ok_or_else(|| anyhow!("Invalid start position: {:?}", range.start))? |
| 133 | + as usize; |
| 134 | + let end_offset = self |
| 135 | + .index |
| 136 | + .offset(range.end) |
| 137 | + .ok_or_else(|| anyhow!("Invalid end position: {:?}", range.end))? |
| 138 | + as usize; |
| 139 | + |
| 140 | + let mut new_content = String::with_capacity( |
| 141 | + self.contents.len() - (end_offset - start_offset) + new_text.len(), |
| 142 | + ); |
| 143 | + |
| 144 | + new_content.push_str(&self.contents[..start_offset]); |
| 145 | + new_content.push_str(new_text); |
| 146 | + new_content.push_str(&self.contents[end_offset..]); |
| 147 | + |
| 148 | + self.set_content(new_content); |
| 149 | + |
| 150 | + Ok(()) |
| 151 | + } |
| 152 | + |
| 153 | + pub fn set_content(&mut self, new_content: String) { |
| 154 | + self.contents = new_content; |
| 155 | + self.index = LineIndex::new(&self.contents); |
| 156 | + } |
| 157 | + |
| 158 | + pub fn get_text(&self) -> &str { |
| 159 | + &self.contents |
| 160 | + } |
| 161 | + |
| 162 | + pub fn get_text_range(&self, range: Range) -> Option<&str> { |
| 163 | + let start = self.index.offset(range.start)? as usize; |
| 164 | + let end = self.index.offset(range.end)? as usize; |
| 165 | + |
| 166 | + Some(&self.contents[start..end]) |
| 167 | + } |
| 168 | + |
| 169 | + pub fn get_line(&self, line: u32) -> Option<&str> { |
| 170 | + let start = self.index.line_starts.get(line as usize)?; |
| 171 | + let end = self |
| 172 | + .index |
| 173 | + .line_starts |
| 174 | + .get(line as usize + 1) |
| 175 | + .copied() |
| 176 | + .unwrap_or(self.index.length); |
| 177 | + |
| 178 | + Some(&self.contents[*start as usize..end as usize]) |
| 179 | + } |
| 180 | + |
| 181 | + pub fn line_count(&self) -> usize { |
| 182 | + self.index.line_starts.len() |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +#[derive(Clone, Debug)] |
| 187 | +pub struct LineIndex { |
| 188 | + line_starts: Vec<u32>, |
| 189 | + length: u32, |
| 190 | +} |
| 191 | + |
| 192 | +impl LineIndex { |
| 193 | + pub fn new(text: &str) -> Self { |
| 194 | + let mut line_starts = vec![0]; |
| 195 | + let mut pos = 0; |
| 196 | + |
| 197 | + for c in text.chars() { |
| 198 | + pos += c.len_utf8() as u32; |
| 199 | + if c == '\n' { |
| 200 | + line_starts.push(pos); |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + Self { |
| 205 | + line_starts, |
| 206 | + length: pos, |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + pub fn offset(&self, position: Position) -> Option<u32> { |
| 211 | + let line_start = self.line_starts.get(position.line as usize)?; |
| 212 | + |
| 213 | + Some(line_start + position.character) |
| 214 | + } |
| 215 | + |
| 216 | + pub fn position(&self, offset: u32) -> Position { |
| 217 | + let line = match self.line_starts.binary_search(&offset) { |
| 218 | + Ok(line) => line, |
| 219 | + Err(line) => line - 1, |
| 220 | + }; |
| 221 | + |
| 222 | + let line_start = self.line_starts[line]; |
| 223 | + let character = offset - line_start; |
| 224 | + |
| 225 | + Position::new(line as u32, character) |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +#[derive(Clone, Debug, PartialEq)] |
| 230 | +pub enum LanguageId { |
| 231 | + HtmlDjango, |
| 232 | + Other, |
| 233 | + Python, |
| 234 | +} |
| 235 | + |
| 236 | +impl From<&str> for LanguageId { |
| 237 | + fn from(language_id: &str) -> Self { |
| 238 | + match language_id { |
| 239 | + "htmldjango" => Self::HtmlDjango, |
| 240 | + "python" => Self::Python, |
| 241 | + _ => Self::Other, |
| 242 | + } |
| 243 | + } |
| 244 | +} |
| 245 | + |
| 246 | +impl From<String> for LanguageId { |
| 247 | + fn from(language_id: String) -> Self { |
| 248 | + Self::from(language_id.as_str()) |
| 249 | + } |
| 250 | +} |
0 commit comments