1use std::sync::{Arc, RwLock};
11use dotrain::{RainDocument, Store, Rebind};
12use lsp_types::{
13 Hover, Position, Diagnostic, MarkupKind, CompletionItem, TextDocumentItem,
14 SemanticTokensPartialResult, Url,
15};
16
17#[cfg(feature = "js-api")]
18use wasm_bindgen::prelude::*;
19
20pub use dotrain;
21pub use lsp_types;
22pub use hover::get_hover;
23pub use completion::get_completion;
24pub use diagnostic::get_diagnostics;
25pub use semantic_token::get_semantic_token;
26
27mod hover;
28mod completion;
29mod diagnostic;
30mod semantic_token;
31
32#[cfg(feature = "js-api")]
33pub mod js_api;
34
35#[derive(Debug, Clone)]
37pub struct LanguageServiceParams {
38 pub meta_store: Option<Arc<RwLock<Store>>>,
40}
41
42#[cfg_attr(
43 not(target_family = "wasm"),
44 doc = r#"Provides methods for getting language services (such as diagnostics, completion, etc)
45for a given TextDocumentItem or a RainDocument. Each instance is linked to a shared locked
46[Store] instance `Arc<RwLock<Store>>` that holds all the required metadata/functionalities that
47are required during parsing a text.
48
49Position encodings provided by the client are irrevelant as RainDocument/Rainlang supports
50only ASCII characters (parsing will stop at very first encountered non-ASCII character), so any
51position encodings will result in the same LSP provided Position value which is 1 for each char.
52
53## Example
54
55```rust
56use std::sync::{Arc, RwLock};
57use dotrain_lsp::{
58 RainLanguageServices,
59 LanguageServiceParams,
60 dotrain::Store,
61 lsp_types::{TextDocumentItem, MarkupKind, Position, Url}
62};
63
64// instaniate a shared locked Store
65let meta_store = Arc::new(RwLock::new(Store::default()));
66
67// create instatiation params
68let params = LanguageServiceParams {
69 meta_store: Some(meta_store)
70};
71
72// create a new instane with a shared locked Store that is used for all
73// parsings that are triggered through available methods of this instance
74let lang_services = RainLanguageServices::new(¶ms);
75
76let text_document = TextDocumentItem {
77 uri: Url::parse("file:///example.rain").unwrap(),
78 text: "some .rain text content".to_string(),
79 version: 0,
80 language_id: "rainlang".to_string()
81};
82
83// create a new RainDocument instance
84let rain_document = lang_services.new_rain_document(&text_document, None);
85
86// get LSP Diagnostics for a given TextDocumentItem
87let diagnostics_related_information = true;
88let diagnostics = lang_services.do_validate(&text_document, diagnostics_related_information, None);
89
90let position = Position {
91 line: 0,
92 character: 10
93};
94let content_format = Some(MarkupKind::PlainText);
95let hover = lang_services.do_hover(&text_document, position, content_format, None);
96```
97"#
98)]
99#[cfg_attr(
100 target_family = "wasm",
101 doc = " Provides methods for getting language services (such as diagnostics, completion, etc)
102 for a given TextDocumentItem or a RainDocument. Each instance is linked to a shared locked
103 MetaStore instance that holds all the required metadata/functionalities that are required during
104 parsing a text.
105
106 Position encodings provided by the client are irrevelant as RainDocument/Rainlang supports
107 only ASCII characters (parsing will stop at very first encountered non-ASCII character), so any
108 position encodings will result in the same LSP provided Position value which is 1 for each char.
109
110 @example
111 ```javascript
112 // create new MetaStore instance
113 let metaStore = new MetaStore();
114
115 // crate new instance
116 let langServices = new RainLanguageServices(metaStore);
117
118 let textDocument = {
119 text: \"some .rain text\",
120 uri: \"file:///name.rain\",
121 version: 0,
122 languageId: \"rainlang\"
123 };
124
125 // creat new RainDocument
126 let rainDocument = langServices.newRainDocument(textdocument);
127
128 // get LSP Diagnostics
129 let diagnosticsRelatedInformation = true;
130 let diagnostics = langServices.doValidate(textDocument, diagnosticsRelatedInformation);
131 ```
132"
133)]
134#[cfg_attr(feature = "js-api", wasm_bindgen)]
135pub struct RainLanguageServices {
136 pub(crate) meta_store: Arc<RwLock<Store>>,
137}
138
139impl Default for RainLanguageServices {
140 fn default() -> Self {
141 let meta_store = Arc::new(RwLock::new(Store::default()));
142 RainLanguageServices { meta_store }
143 }
144}
145
146impl RainLanguageServices {
147 pub fn meta_store(&self) -> Arc<RwLock<Store>> {
149 self.meta_store.clone()
150 }
151 pub fn new(language_params: &LanguageServiceParams) -> RainLanguageServices {
153 RainLanguageServices {
154 meta_store: language_params
155 .meta_store
156 .as_ref()
157 .map_or(Arc::new(RwLock::new(Store::default())), |s| s.clone()),
158 }
159 }
160
161 pub fn new_rain_document(
163 &self,
164 text_document: &TextDocumentItem,
165 rebinds: Option<Vec<Rebind>>,
166 ) -> RainDocument {
167 RainDocument::create(
168 text_document.text.clone(),
169 Some(self.meta_store.clone()),
170 None,
171 rebinds,
172 )
173 }
174 pub async fn new_rain_document_async(
176 &self,
177 text_document: &TextDocumentItem,
178 rebinds: Option<Vec<Rebind>>,
179 ) -> RainDocument {
180 RainDocument::create_async(
181 text_document.text.clone(),
182 Some(self.meta_store.clone()),
183 None,
184 rebinds,
185 )
186 .await
187 }
188
189 pub fn do_validate(
191 &self,
192 text_document: &TextDocumentItem,
193 related_information: bool,
194 rebinds: Option<Vec<Rebind>>,
195 ) -> Vec<Diagnostic> {
196 let rain_document = RainDocument::create(
197 text_document.text.clone(),
198 Some(self.meta_store.clone()),
199 None,
200 rebinds,
201 );
202 diagnostic::get_diagnostics(&rain_document, &text_document.uri, related_information)
203 }
204 pub async fn do_validate_async(
206 &self,
207 text_document: &TextDocumentItem,
208 related_information: bool,
209 rebinds: Option<Vec<Rebind>>,
210 ) -> Vec<Diagnostic> {
211 let rain_document = RainDocument::create_async(
212 text_document.text.clone(),
213 Some(self.meta_store.clone()),
214 None,
215 rebinds,
216 )
217 .await;
218 diagnostic::get_diagnostics(&rain_document, &text_document.uri, related_information)
219 }
220 pub fn do_validate_rain_document(
222 &self,
223 rain_document: &RainDocument,
224 uri: &Url,
225 related_information: bool,
226 ) -> Vec<Diagnostic> {
227 diagnostic::get_diagnostics(rain_document, uri, related_information)
228 }
229
230 pub fn do_complete(
232 &self,
233 text_document: &TextDocumentItem,
234 position: Position,
235 documentation_format: Option<MarkupKind>,
236 rebinds: Option<Vec<Rebind>>,
237 ) -> Option<Vec<CompletionItem>> {
238 let rain_document = RainDocument::create(
239 text_document.text.clone(),
240 Some(self.meta_store.clone()),
241 None,
242 rebinds,
243 );
244 completion::get_completion(
245 &rain_document,
246 &text_document.uri,
247 position,
248 documentation_format.unwrap_or(MarkupKind::PlainText),
249 )
250 }
251 pub fn do_complete_rain_document(
253 &self,
254 rain_document: &RainDocument,
255 uri: &Url,
256 position: Position,
257 documentation_format: Option<MarkupKind>,
258 ) -> Option<Vec<CompletionItem>> {
259 completion::get_completion(
260 rain_document,
261 uri,
262 position,
263 documentation_format.unwrap_or(MarkupKind::PlainText),
264 )
265 }
266
267 pub fn do_hover(
269 &self,
270 text_document: &TextDocumentItem,
271 position: Position,
272 content_format: Option<MarkupKind>,
273 rebinds: Option<Vec<Rebind>>,
274 ) -> Option<Hover> {
275 let rain_document = RainDocument::create(
276 text_document.text.clone(),
277 Some(self.meta_store.clone()),
278 None,
279 rebinds,
280 );
281 hover::get_hover(
282 &rain_document,
283 position,
284 content_format.unwrap_or(MarkupKind::PlainText),
285 )
286 }
287 pub fn do_hover_rain_document(
289 &self,
290 rain_document: &RainDocument,
291 position: Position,
292 content_format: Option<MarkupKind>,
293 ) -> Option<Hover> {
294 hover::get_hover(
295 rain_document,
296 position,
297 content_format.unwrap_or(MarkupKind::PlainText),
298 )
299 }
300
301 pub fn semantic_tokens(
303 &self,
304 text_document: &TextDocumentItem,
305 semantic_token_types_index: u32,
306 semantic_token_modifiers_len: usize,
307 rebinds: Option<Vec<Rebind>>,
308 ) -> SemanticTokensPartialResult {
309 let rain_document = RainDocument::create(
310 text_document.text.clone(),
311 Some(self.meta_store.clone()),
312 None,
313 rebinds,
314 );
315 get_semantic_token(
316 &rain_document,
317 semantic_token_types_index,
318 semantic_token_modifiers_len,
319 )
320 }
321 pub fn rain_document_semantic_tokens(
323 &self,
324 rain_document: &RainDocument,
325 semantic_token_types_index: u32,
326 semantic_token_modifiers_len: usize,
327 ) -> SemanticTokensPartialResult {
328 get_semantic_token(
329 rain_document,
330 semantic_token_types_index,
331 semantic_token_modifiers_len,
332 )
333 }
334}
335
336pub trait PositionAt {
338 fn position_at(&self, offset: usize) -> Position;
339}
340
341pub trait OffsetAt {
343 fn offset_at(&self, position: &Position) -> usize;
344}
345
346impl PositionAt for &str {
347 fn position_at(&self, offset: usize) -> Position {
348 let effective_offset = 0.max(offset.min(self.len()));
349 let mut line_offsets = vec![];
350 let mut acc = 0;
351 self.split_inclusive('\n').for_each(|v| {
352 line_offsets.push(acc);
353 acc += v.len();
354 });
355 let mut low = 0;
356 let mut high = line_offsets.len();
357 if high == 0 {
358 return Position {
359 line: 0,
360 character: effective_offset as u32,
361 };
362 }
363 while low < high {
364 let mid = (low + high) / 2;
365 if line_offsets[mid] > effective_offset {
366 high = mid;
367 } else {
368 low = mid + 1;
369 }
370 }
371 let line = low - 1;
374 Position {
375 line: line as u32,
376 character: (effective_offset - line_offsets[line]) as u32,
377 }
378 }
379}
380
381impl OffsetAt for &str {
382 fn offset_at(&self, position: &Position) -> usize {
383 let mut line_offsets = vec![];
384 let mut acc = 0;
385 self.split_inclusive('\n').for_each(|v| {
386 line_offsets.push(acc);
387 acc += v.len();
388 });
389 if position.line >= line_offsets.len() as u32 {
390 return self.len();
391 }
392 let line_offset = line_offsets[position.line as usize];
393 let next_line_offset = if position.line + 1 < line_offsets.len() as u32 {
394 line_offsets[position.line as usize + 1]
395 } else {
396 self.len()
397 };
398 line_offset.max((line_offset + position.character as usize).min(next_line_offset))
399 }
400}
401
402impl PositionAt for String {
403 fn position_at(&self, offset: usize) -> Position {
404 let effective_offset = 0.max(offset.min(self.len()));
405 let mut line_offsets = vec![];
406 let mut acc = 0;
407 self.split_inclusive('\n').for_each(|v| {
408 line_offsets.push(acc);
409 acc += v.len();
410 });
411 let mut low = 0;
412 let mut high = line_offsets.len();
413 if high == 0 {
414 return Position {
415 line: 0,
416 character: effective_offset as u32,
417 };
418 }
419 while low < high {
420 let mid = (low + high) / 2;
421 if line_offsets[mid] > effective_offset {
422 high = mid;
423 } else {
424 low = mid + 1;
425 }
426 }
427 let line = low - 1;
430 Position {
431 line: line as u32,
432 character: (effective_offset - line_offsets[line]) as u32,
433 }
434 }
435}
436
437impl OffsetAt for String {
438 fn offset_at(&self, position: &Position) -> usize {
439 let mut line_offsets = vec![];
440 let mut acc = 0;
441 self.split_inclusive('\n').for_each(|v| {
442 line_offsets.push(acc);
443 acc += v.len();
444 });
445 if position.line >= line_offsets.len() as u32 {
446 return self.len();
447 }
448 let line_offset = line_offsets[position.line as usize];
449 let next_line_offset = if position.line + 1 < line_offsets.len() as u32 {
450 line_offsets[position.line as usize + 1]
451 } else {
452 self.len()
453 };
454 line_offset.max((line_offset + position.character as usize).min(next_line_offset))
455 }
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
463 fn test_position_at() {
464 let text = r"abcd
465 efgh
466 ijkl";
467
468 let pos1 = text.position_at(14);
469 let pos1_expected = Position {
470 line: 1,
471 character: 9,
472 };
473 assert_eq!(pos1, pos1_expected);
474
475 let pos2 = text.position_at(28);
476 let pos2_expected = Position {
477 line: 2,
478 character: 10,
479 };
480 assert_eq!(pos2, pos2_expected);
481 }
482
483 #[test]
484 fn test_offset_at() {
485 let text = r"abcd
486 efgh
487 ijkl";
488
489 let offset1 = text.offset_at(&Position {
490 line: 1,
491 character: 9,
492 });
493 let expected_offset1 = 14;
494 assert_eq!(offset1, expected_offset1);
495
496 let offset2 = text.offset_at(&Position {
497 line: 2,
498 character: 10,
499 });
500 let expected_offset2 = 28;
501 assert_eq!(offset2, expected_offset2);
502 }
503}