Skip to main content

owl_ms_language_server/
lib.rs

1mod catalog;
2mod consts;
3pub mod debugging;
4mod error;
5mod pos;
6mod queries;
7mod range;
8pub mod rope_provider;
9mod sync_backend;
10#[cfg(test)]
11#[allow(clippy::pedantic)]
12mod test_helpers;
13#[cfg(test)]
14#[allow(clippy::pedantic)]
15mod tests;
16pub mod web;
17mod workspace;
18
19use debugging::timeit;
20use error::{Error, ResultExt, ResultIterator};
21use itertools::Itertools;
22use log::{debug, error, info, warn};
23use pos::Position;
24use range::Range;
25use std::collections::{HashMap, HashSet, LinkedList};
26use std::path::Path;
27use std::sync::{Arc, LazyLock};
28use tokio::sync::{OnceCell, RwLock, RwLockReadGuard, RwLockWriteGuard};
29use tokio::task::{self, JoinHandle};
30use tower_lsp::jsonrpc::Result;
31// There are too many LSP types
32#[allow(clippy::wildcard_imports)]
33use tower_lsp::lsp_types::{self, *};
34use tower_lsp::{Client, LanguageServer};
35use tree_sitter_c2rust::Language;
36use workspace::{node_text, trim_full_iri, Workspace};
37
38use crate::sync_backend::SyncBackend;
39use crate::web::HttpClient;
40use crate::workspace::{
41    Document, DocumentReference, FormattingSettings, FrameType, InternalDocument,
42};
43
44// Re-export for benchmarks
45pub use crate::workspace::clear_caches;
46
47// Constants
48
49pub static LANGUAGE: LazyLock<Language> = LazyLock::new(|| tree_sitter_owl_ms::LANGUAGE.into());
50
51// Model
52
53#[derive(Clone)]
54pub struct Backend {
55    pub client: Client,
56    pub http_client: Arc<dyn HttpClient>,
57    position_encoding: OnceCell<PositionEncodingKind>,
58    options: OnceCell<Options>,
59    sync: SyncRef,
60}
61
62pub type SyncRef = Arc<RwLock<SyncBackend>>;
63
64impl Backend {
65    /// Creates a new [`Backend`] with a Ureq http client and UTF16 encoding.
66    /// The workspaces are created empty.
67    #[must_use]
68    pub fn new(client: Client, http_client: Box<dyn HttpClient>) -> Self {
69        Backend {
70            client,
71            http_client: http_client.into(),
72            position_encoding: OnceCell::new(),
73            sync: Arc::new(RwLock::new(SyncBackend::default())),
74            options: OnceCell::new(),
75        }
76    }
77
78    /// Take a document with the profided file url and generate diagnostics for it.
79    /// Then do the same thing with documents that depend on this one.
80    /// # Panics
81    /// If an documents path is not convertable into an URL
82    pub fn update_diagnostics_for_url_and_dependent(&self, file_url: Url) {
83        let mini_backend = self.clone();
84
85        task::spawn(async move {
86            let encoding = mini_backend.encoding();
87            let sync = mini_backend.sync.read().await;
88
89            // So my error type is not send and terefore we need the conversion to ok()
90            #[allow(clippy::match_result_ok)]
91            if let Some((document, workspace)) = sync.get_internal_document(&file_url).ok() {
92                document
93                    .publish_lsp_diagnostics(workspace, encoding, &mini_backend.client)
94                    .await;
95
96                // Create diagnostics for files that depend on this file
97                for other_internal_doc in workspace.internal_documents() {
98                    let depends_on_me = other_internal_doc.reachable_urls(false).iter().any(|u| {
99                        workspace.document_by_url(u) == Some(DocumentReference::Internal(document))
100                    });
101
102                    if depends_on_me {
103                        mini_backend.update_diagnostics_for_url_and_dependent(
104                            Url::from_file_path(other_internal_doc.path())
105                                .expect("Document path should be convertable into file url"),
106                        );
107                    }
108                }
109            }
110        });
111    }
112
113    /// Loads all documents that can be reached by this internal document path
114    /// # Panics
115    /// When the path is not a file path
116    pub fn load_dependencies(&self, path: &Path) -> tokio::task::JoinHandle<()> {
117        let mini_backend = self.clone();
118        let path = path.to_owned();
119        tokio::spawn(async move {
120            let mut todo: LinkedList<(Url, u32)> = { build_todo_list(&mini_backend, &path).await };
121
122            let mut done = HashSet::<Url>::new();
123
124            debug!(
125                "Loading dependencies of {} len = {}",
126                path.display(),
127                todo.len()
128            );
129
130            while let Some((url, depth)) = todo.pop_front() {
131                debug!("Indexing {url} ...");
132                if done.contains(&url) {
133                    debug!("URL {url} was indexed. Skip");
134                    continue;
135                }
136                if depth > 2 {
137                    debug!("Depth {depth} exceeded 2 for {url}. Skip");
138                    continue;
139                }
140
141                let resolved_doc_or_err = {
142                    let sync = mini_backend.sync.read().await;
143                    let workspace = sync
144                        .get_workspace(
145                            &Url::from_file_path(&path)
146                                .expect("File path should be convertable to URL"),
147                        )
148                        .expect("Workspace for document should exist");
149
150                    // TODO well the Error is not Send. That's why we convert to Option
151                    Workspace::resolve_url_to_document(
152                        workspace,
153                        &url,
154                        mini_backend.http_client.clone(),
155                    )
156                    .await
157                    .ok()
158                };
159
160                let resolved_doc = if let Some(resolved_doc) = resolved_doc_or_err {
161                    resolved_doc
162                } else {
163                    // This is the error case
164                    warn!("Resolving {url} did not work");
165                    None
166                };
167
168                if let Some(doc) = resolved_doc {
169                    match doc {
170                        Document::Internal(internal_document) => {
171                            for ele in internal_document.reachable_urls(true) {
172                                todo.push_back((ele.clone(), 1));
173                            }
174                            let file_url = Url::from_file_path(internal_document.path())
175                                .expect("Path should also be a Url");
176                            {
177                                let mut sync = mini_backend.sync.write().await;
178                                let workspace = sync.get_or_insert_workspace_mut(
179                                    &Url::from_file_path(&path)
180                                        .expect("File path should be convertable to URL"),
181                                );
182                                workspace.insert_internal_document(internal_document);
183                            }
184
185                            mini_backend.update_diagnostics_for_url_and_dependent(file_url);
186                        }
187                        Document::External(external_document) => {
188                            // TODO maybe remove this?
189                            // Lets not do that yet
190                            for ele in external_document.reachable_urls() {
191                                todo.push_back((ele.clone(), depth + 1));
192                            }
193                            {
194                                let mut sync = mini_backend.sync.write().await;
195                                let workspace = sync.get_or_insert_workspace_mut(
196                                    &Url::from_file_path(&path)
197                                        .expect("File path should be convertable to URL"),
198                                );
199                                workspace.insert_external_document(external_document);
200                            }
201                        }
202                    }
203                } else {
204                    // The doc is already resolved
205                }
206
207                // Refresh the inline hints, because we got new information now
208                refresh_inlay_hints(&mini_backend).await;
209
210                done.insert(url);
211            }
212
213            // Every dependency is loaded
214        })
215    }
216
217    /// Waits for all background indexing tasks to complete.
218    /// Useful for benchmarks to ensure no background work interferes with measurements.
219    pub async fn wait_for_indexing(&self) {
220        let mut sync = self.write_sync().await;
221        let mut all_handles = Vec::<JoinHandle<()>>::new();
222        for workspace in sync.workspaces_mut() {
223            let handles = std::mem::take(&mut workspace.index_handles);
224            all_handles.extend(handles.into_iter());
225        }
226        drop(sync);
227        for handle in all_handles {
228            let _ = handle.await;
229        }
230    }
231
232    fn server_capabilities(position_encoding_kind: PositionEncodingKind) -> ServerCapabilities {
233        ServerCapabilities {
234            text_document_sync: Some(TextDocumentSyncCapability::Kind(
235                TextDocumentSyncKind::INCREMENTAL,
236            )),
237            hover_provider: Some(HoverProviderCapability::Simple(true)),
238            document_formatting_provider: Some(OneOf::Left(true)),
239            position_encoding: Some(position_encoding_kind),
240            inlay_hint_provider: Some(OneOf::Left(true)),
241            definition_provider: Some(OneOf::Left(true)),
242            code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
243            completion_provider: Some(CompletionOptions {
244                ..Default::default()
245            }),
246            semantic_tokens_provider: Some(
247                SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions {
248                    legend: SemanticTokensLegend {
249                        token_types: vec![
250                            SemanticTokenType::NAMESPACE,
251                            SemanticTokenType::TYPE,
252                            SemanticTokenType::CLASS,
253                            SemanticTokenType::ENUM,
254                            SemanticTokenType::INTERFACE,
255                            SemanticTokenType::STRUCT,
256                            SemanticTokenType::TYPE_PARAMETER,
257                            SemanticTokenType::PARAMETER,
258                            SemanticTokenType::VARIABLE,
259                            SemanticTokenType::PROPERTY,
260                            SemanticTokenType::ENUM_MEMBER,
261                            SemanticTokenType::EVENT,
262                            SemanticTokenType::FUNCTION,
263                            SemanticTokenType::METHOD,
264                            SemanticTokenType::MACRO,
265                            SemanticTokenType::KEYWORD,
266                            SemanticTokenType::MODIFIER,
267                            SemanticTokenType::COMMENT,
268                            SemanticTokenType::STRING,
269                            SemanticTokenType::NUMBER,
270                            SemanticTokenType::REGEXP,
271                            SemanticTokenType::OPERATOR,
272                            SemanticTokenType::DECORATOR,
273                        ],
274                        token_modifiers: vec![],
275                    },
276                    full: Some(SemanticTokensFullOptions::Bool(true)),
277                    range: Some(true),
278                    ..Default::default()
279                }),
280            ),
281            document_symbol_provider: Some(OneOf::Left(true)),
282            workspace_symbol_provider: Some(OneOf::Right(WorkspaceSymbolOptions {
283                work_done_progress_options: WorkDoneProgressOptions {
284                    work_done_progress: Some(false),
285                },
286                resolve_provider: Some(false),
287            })),
288            references_provider: Some(OneOf::Left(true)),
289            rename_provider: Some(OneOf::Right(RenameOptions {
290                prepare_provider: Some(true),
291                work_done_progress_options: WorkDoneProgressOptions {
292                    work_done_progress: None,
293                },
294            })),
295            ..Default::default()
296        }
297    }
298}
299
300#[derive(Debug, Clone)]
301struct Options {
302    order_frames: bool,
303}
304
305fn parse_options(options: Option<serde_json::Value>) -> Options {
306    {
307        let mut is_order_frames = false;
308        if let Some(o) = options {
309            if let Some(root) = o.as_object() {
310                if let Some(omn) = root.get("omn") {
311                    if let Some(omn) = omn.as_object() {
312                        if let Some(order_frames) = omn.get("orderFrames") {
313                            if let Some(order_frames) = order_frames.as_bool() {
314                                is_order_frames = order_frames;
315                            }
316                        }
317                    }
318                }
319            }
320        }
321        Options {
322            order_frames: is_order_frames,
323        }
324    }
325}
326
327async fn refresh_inlay_hints(mini_backend: &Backend) {
328    match mini_backend.client.inline_value_refresh().await {
329        Ok(()) => {
330            debug!("Refresh inline hints");
331        }
332        // Looks like I dont have a specific tower lsp error variant.
333        // Buts thats not that bad. Just log it.
334        Err(err) => {
335            error!("{err}");
336        }
337    }
338}
339
340async fn build_todo_list(
341    mini_backend: &Backend,
342    path: &std::path::PathBuf,
343) -> LinkedList<(Url, u32)> {
344    let sync = mini_backend.sync.read().await;
345    let workspace = sync
346        .get_workspace(&Url::from_file_path(path).expect("File path should be convertable to URL"))
347        .expect("Workspace for document should exist");
348
349    let document = workspace.get_internal_document(path).unwrap();
350    document
351        .reachable_urls(true)
352        .iter()
353        .map(|u| (u.clone(), 1))
354        .collect()
355}
356
357/// This is the main language server implamentation. It is the entry point for all requests to the language server.
358#[tower_lsp::async_trait]
359impl LanguageServer for Backend {
360    /// Initilizes the language server and loads the workspaces with catalog files.
361    ///
362    /// Does not load or index the files inside the workspaces.
363    async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
364        info!("Initialize language server -----------------------------");
365        info!("Client info:\n{:#?}", params.client_info);
366        debug!("Client capabilities:\n{:#?}", params.capabilities);
367        debug!("Options: {:#?}", params.initialization_options);
368
369        let options = params.initialization_options;
370        let options = parse_options(options);
371        debug!("Parsed Options: {options:?}");
372        self.options
373            .set(options)
374            .expect("options should not be set");
375
376        let encodings = params
377            .capabilities
378            .general
379            .and_then(|g| g.position_encodings)
380            .unwrap_or_default();
381
382        self.position_encoding
383            .set(if encodings.contains(&PositionEncodingKind::UTF8) {
384                PositionEncodingKind::UTF8
385            } else {
386                PositionEncodingKind::UTF16
387            })
388            .expect("the encoding to be unset");
389
390        let mut sync = self.write_sync().await;
391
392        for wf in params.workspace_folders.iter().flatten() {
393            sync.push_workspace(Workspace::new(wf.clone()));
394        }
395
396        // Done with init, lets return the findings
397
398        let position_encoding_kind = self
399            .position_encoding
400            .get()
401            .expect("encoding should be set")
402            .clone();
403
404        Ok(InitializeResult {
405            server_info: Some(ServerInfo {
406                name: "owl-ms-language-server".to_string(),
407                version: None,
408            }),
409            capabilities: Backend::server_capabilities(position_encoding_kind),
410        })
411    }
412
413    async fn initialized(&self, _params: InitializedParams) {
414        let sync = self.read_sync().await;
415        let workspace_paths = sync
416            .workspaces()
417            .iter()
418            .map(|w| format!("{w}"))
419            .collect_vec();
420
421        info!("Initialized languag server with workspaces: {workspace_paths:?}");
422    }
423
424    async fn did_open(&self, params: DidOpenTextDocumentParams) {
425        async {
426            let file_url = params.text_document.uri;
427            info!("Did open {file_url} ...",);
428
429            let mut sync = self.write_sync().await;
430
431            let workspace = sync.get_or_insert_workspace_mut(&file_url);
432            let internal_document = InternalDocument::new(
433                file_url.clone(),
434                params.text_document.version,
435                params.text_document.text,
436            );
437
438            let doc = workspace.insert_internal_document(internal_document);
439            let path = doc.path().to_path_buf();
440
441            let handle = self.load_dependencies(&path);
442
443            #[cfg(test)]
444            {
445                // This is just for tests, so that they dont produce a race condition
446                drop(sync);
447                handle.await.unwrap();
448            }
449            #[cfg(not(test))]
450            {
451                workspace.index_handles.push(handle);
452            }
453
454            self.update_diagnostics_for_url_and_dependent(file_url);
455
456            debug!("Did open!");
457
458            Ok(())
459        }
460        .await
461        .log_if_error();
462    }
463
464    async fn did_change(&self, params: DidChangeTextDocumentParams) {
465        async {
466            debug!(
467                "Did change at {} with version {}",
468                params
469                    .text_document
470                    .uri
471                    .path_segments()
472                    .ok_or(Error::InvalidUrl(params.text_document.uri.clone()))?
473                    .next_back()
474                    .ok_or(Error::InvalidUrl(params.text_document.uri.clone()))?,
475                params.text_document.version
476            );
477
478            let url = params.text_document.uri.clone();
479
480            // Do the document edit
481            let mut sync = self.write_sync().await;
482            let (document, workspace) = sync.take_internal_document(&url)?;
483
484            let new_document = timeit("document.edit", || document.edit(params, self.encoding()))?;
485
486            workspace.insert_internal_document(new_document);
487
488            // TODO make this join handle one of a kind maybe. So no two diagnostics threads at the same time.
489            // Async diagnostics
490            self.update_diagnostics_for_url_and_dependent(url);
491
492            Ok(())
493        }
494        .await
495        .log_if_error();
496    }
497
498    async fn did_close(&self, params: DidCloseTextDocumentParams) {
499        (|| {
500            debug!(
501                "Did close at {}",
502                params
503                    .text_document
504                    .uri
505                    .path_segments()
506                    .ok_or(Error::InvalidUrl(params.text_document.uri.clone()))?
507                    .next_back()
508                    .ok_or(Error::InvalidUrl(params.text_document.uri.clone()))?
509            );
510
511            Ok(())
512        })()
513        .log_if_error();
514
515        // We do not close yet :> because of refences
516        // TODO should data be deleted if a file is closed?
517    }
518
519    async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
520        info!("formatting {params:#?}");
521        let url = params.text_document.uri;
522
523        let tab_size = params.options.tab_size;
524
525        let sync = self.read_sync().await;
526        let (doc, _) = sync.get_internal_document(&url)?;
527
528        let options = FormattingSettings {
529            tab_size: if tab_size == 0 { 4 } else { tab_size },
530            ruler_width: 80,
531            order_frames: self
532                .options
533                .get()
534                .expect("options should be initilized")
535                .order_frames,
536        };
537        // TODO just send the diff
538        let text = doc.formatted(&options);
539
540        let range: Range = doc.tree().root_node().range().into();
541
542        return Ok(Some(vec![TextEdit {
543            range: range.into_lsp(doc.rope(), self.encoding())?,
544            new_text: text,
545        }]));
546    }
547
548    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
549        let url = params.text_document_position_params.text_document.uri;
550        info!(
551            "Hover at {:?} in file {url}",
552            params.text_document_position_params.position
553        );
554
555        let sync = self.read_sync().await;
556        let (doc, ws) = sync.get_internal_document(&url)?;
557
558        let pos: Position = Position::from_lsp(
559            params.text_document_position_params.position,
560            doc.rope(),
561            self.encoding(),
562        )?;
563        let node = doc
564            .tree()
565            .root_node()
566            .named_descendant_for_point_range(pos.into(), pos.into())
567            .ok_or(Error::PositionOutOfBounds(pos))?;
568
569        let info = ws.node_info(&node, doc);
570
571        Ok(if info.is_empty() {
572            None
573        } else {
574            // Transitive into
575            let range: Range = node.range().into();
576            Some(Hover {
577                contents: HoverContents::Scalar(MarkedString::String(info)),
578                range: Some(range.into_lsp(doc.rope(), self.encoding())?),
579            })
580        })
581    }
582
583    async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
584        let url = params.text_document.uri;
585        info!("Inlay hint at {url}");
586
587        let sync = self.read_sync().await;
588        let (document, workspace) = sync.get_internal_document(&url)?;
589
590        let range = Range::from_lsp(&params.range, document.rope(), self.encoding())?;
591
592        debug!(
593            "inlay_hint at {}:{range}",
594            url.path_segments()
595                .ok_or(Error::InvalidUrl(url.clone()))?
596                .next_back()
597                .ok_or(Error::InvalidUrl(url.clone()))?
598        );
599
600        let hints = document.inlay_hint(range, self.encoding(), workspace);
601
602        Ok(Some(hints))
603    }
604
605    async fn goto_definition(
606        &self,
607        params: GotoDefinitionParams,
608    ) -> Result<Option<GotoDefinitionResponse>> {
609        let url = params.text_document_position_params.text_document.uri;
610        debug!("goto_definition at {url}");
611
612        let sync = self.read_sync().await;
613        let (doc, workspace) = sync.get_internal_document(&url)?;
614        let pos: Position = Position::from_lsp(
615            params.text_document_position_params.position,
616            doc.rope(),
617            self.encoding(),
618        )?;
619
620        let leaf_node = doc
621            .tree()
622            .root_node()
623            .named_descendant_for_point_range(pos.into(), pos.into())
624            .ok_or(Error::PositionOutOfBounds(pos))?;
625
626        let reachable_docs = doc.reachable_docs_recursive(workspace, true);
627
628        let node_is_iri = ["full_iri", "simple_iri", "abbreviated_iri"].contains(&leaf_node.kind());
629        if node_is_iri {
630            let iri = trim_full_iri(node_text(&leaf_node, doc.rope()));
631            let iri = doc.abbreviated_iri_to_full_iri(&iri).unwrap_or(iri);
632
633            debug!("Try goto definition of {iri}");
634
635            let iri_is_import_iri = leaf_node
636                .parent()
637                .expect("iri node should have parent")
638                .kind()
639                == "import";
640
641            if iri_is_import_iri {
642                let url = Url::parse(&iri).map_err(|_| Error::InvalidUrl(url.clone()))?;
643                // This does not work for external documents from prefixes
644                let path = workspace.url_to_path_with_catalog(&url);
645                if let Some(path) = path {
646                    return Ok(Some(single_path_response(&path)));
647                }
648            } else {
649                let frame_info =
650                    Workspace::get_frame_info_recursive(workspace, &iri, &reachable_docs);
651                if let Some(frame_info) = frame_info {
652                    let locations = frame_info
653                        .definitions
654                        .iter()
655                        .sorted_by_key(|l| {
656                            if l.range == Range::ZERO {
657                                u32::MAX // No range? Then put this at the end
658                            } else {
659                                l.range.start.line()
660                            }
661                        })
662                        .map(|l| l.clone().into_lsp(doc.rope(), self.encoding()))
663                        .filter_and_log()
664                        .collect_vec();
665
666                    return Ok(Some(GotoDefinitionResponse::Array(locations)));
667                }
668            }
669        }
670        Ok(None)
671    }
672
673    async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
674        debug!("code_action at {}", params.text_document.uri);
675        let url = params.text_document.uri;
676        let sync = self.read_sync().await;
677        let (doc, ws) = sync.get_internal_document(&url)?;
678
679        // pos is just the range start. Ignore range end for now.
680        let pos: Position = Position::from_lsp(params.range.start, doc.rope(), self.encoding())?;
681
682        let node = doc
683            .tree()
684            .root_node()
685            .named_descendant_for_point_range(pos.into(), pos.into())
686            .ok_or(Error::PositionOutOfBounds(pos))?;
687
688        // TODO This could be from the parameter, but then the parsing from lsp diagnostics
689        // to internal one should take place somehow. Not needed now I think.
690        // Also I dont know how they are matched
691        let diagnostics = doc.diagnostics(ws);
692        let diagnostics_under_cursor = diagnostics
693            .into_iter()
694            .filter(|diagnostic| diagnostic.range.contains(pos));
695
696        let end: Position = doc.tree().root_node().range().end_point.into();
697        let end_lsp = end.into_lsp(doc.rope(), self.encoding())?;
698        let create_missing_iri_actions = diagnostics_under_cursor.filter_map(|d| match &d.kind {
699            workspace::DiagnosticKind::MissingIri(full_iri) => {
700                let iri = doc.full_iri_to_shorter_iri(full_iri);
701                let iri_kind = node
702                    .parent()
703                    .expect("Missing IRI node should have parent")
704                    .kind();
705
706                let frame_type = FrameType::parse(iri_kind);
707                let definition_str = frame_type.to_definition()?;
708
709                Some(CodeActionOrCommand::CodeAction(CodeAction {
710                    title: format!("Create {frame_type} for {iri}",),
711                    edit: Some(WorkspaceEdit {
712                        changes: Some(HashMap::from([(
713                            url.clone(),
714                            vec![TextEdit {
715                                range: lsp_types::Range {
716                                    start: end_lsp,
717                                    end: end_lsp,
718                                },
719                                new_text: format!("\n{definition_str} {iri}\n"),
720                            }],
721                        )])),
722                        ..Default::default()
723                    }),
724                    // TODO from above diagnostics: Some(vec![d]),
725                    ..Default::default()
726                }))
727            }
728            workspace::DiagnosticKind::SyntaxError { .. } => None,
729        });
730
731        let mut actions = vec![
732            // TODO maybe this would be a great workspace action?
733            // CodeActionOrCommand::CodeAction(CodeAction {
734            // title: "add class".to_string(),
735            // edit: Some(WorkspaceEdit {
736            //     changes: Some(HashMap::from([(
737            //         url.clone(),
738            //         vec![TextEdit {
739            //             range: lsp_types::Range {
740            //                 start: end.into_lsp(doc.rope(), self.encoding())?,
741            //                 end: end.into_lsp(doc.rope(), self.encoding())?,
742            //             },
743            //             new_text:
744            //                 "\nClass: new_class\n    Annotations:\n        rdfs:label \"new class\""
745            //                     .to_string(),
746            //         }],
747            //     )])),
748            //     document_changes: None,
749            //     change_annotations: None,
750            // }),
751            // ..Default::default()
752            // })
753        ];
754
755        actions.extend(create_missing_iri_actions);
756
757        Ok(Some(actions))
758    }
759
760    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
761        debug!(
762            "completion at {} {:?} {:?}",
763            params.text_document_position.text_document.uri,
764            params.text_document_position.position,
765            self.encoding()
766        );
767        let url = params.text_document_position.text_document.uri;
768        let sync = self.read_sync().await;
769        let (doc, workspace) = sync.get_internal_document(&url)?;
770        let pos: Position = Position::from_lsp(
771            params.text_document_position.position,
772            doc.rope(),
773            self.encoding(),
774        )?;
775
776        let kws = timeit("lookahead iterator keywords", || {
777            doc.get_keyword_competions_at(pos)
778        });
779
780        debug!("The resultingn kws are {kws:#?}");
781
782        let iri_completions = doc.get_iri_completions_at(pos, workspace);
783
784        debug!("The resulting iris are {iri_completions:#?}");
785
786        let keywords_completion_items = kws.into_iter().map(|keyword| CompletionItem {
787            label: keyword,
788            kind: Some(CompletionItemKind::KEYWORD),
789            ..Default::default()
790        });
791
792        let iri_completion_items =
793            iri_completions
794                .into_iter()
795                .map(|(label, details, insert_text)| {
796                    CompletionItem {
797                        label,
798                        kind: Some(CompletionItemKind::REFERENCE),
799                        detail: Some(details),
800                        insert_text: Some(insert_text),
801                        // TODO #29 add details from the frame
802                        ..Default::default()
803                    }
804                });
805
806        let items: Vec<CompletionItem> = iri_completion_items
807            .chain(keywords_completion_items)
808            .collect();
809
810        debug!("completion item count {}", items.len());
811
812        Ok(Some(CompletionResponse::Array(items)))
813    }
814
815    async fn semantic_tokens_full(
816        &self,
817        params: SemanticTokensParams,
818    ) -> Result<Option<SemanticTokensResult>> {
819        debug!("semantic_tokens_full at {}", params.text_document.uri);
820        let url = params.text_document.uri;
821        let sync = self.read_sync().await;
822        let (doc, _) = sync.get_internal_document(&url)?;
823
824        let tokens = doc.sematic_tokens(None, self.encoding())?;
825
826        Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
827            result_id: None,
828            data: tokens,
829        })))
830    }
831
832    async fn semantic_tokens_range(
833        &self,
834        params: SemanticTokensRangeParams,
835    ) -> Result<Option<SemanticTokensRangeResult>> {
836        debug!("semantic_tokens_range at {}", params.text_document.uri);
837        let url = params.text_document.uri;
838        let sync = self.read_sync().await;
839        let (doc, _) = sync.get_internal_document(&url)?;
840        let range = Range::from_lsp(&params.range, doc.rope(), self.encoding())?;
841        let tokens = doc.sematic_tokens(Some(range), self.encoding())?;
842
843        return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
844            result_id: None,
845            data: tokens,
846        })));
847    }
848
849    async fn document_symbol(
850        &self,
851        params: DocumentSymbolParams,
852    ) -> Result<Option<DocumentSymbolResponse>> {
853        let url = params.text_document.uri;
854        let sync = self.read_sync().await;
855        let (doc, _) = sync.get_internal_document(&url)?;
856        let infos = doc.all_frame_infos();
857        return Ok(Some(DocumentSymbolResponse::Flat(
858            infos
859                .flat_map(|info| {
860                    let name = info.label().unwrap_or_else(|| {
861                        doc.full_iri_to_abbreviated_iri(&info.iri)
862                            .unwrap_or(info.iri.clone())
863                    });
864                    let kind: SymbolKind = info.frame_type.into();
865                    let url = url.clone();
866                    info.definitions.iter().map(move |def| {
867                        #[allow(deprecated)] // All fields need to be specified
868                        Ok(SymbolInformation {
869                            name: name.clone(),
870                            kind,
871                            tags: None,
872                            deprecated: None,
873                            location: Location {
874                                uri: url.clone(),
875                                range: def.range.into_lsp(doc.rope(), self.encoding())?,
876                            },
877                            container_name: None,
878                        })
879                    })
880                })
881                .filter_and_log()
882                .filter(|s| !s.name.is_empty())
883                .sorted_by_cached_key(|s| format!("{:?}{}", s.kind, s.name))
884                .collect_vec(),
885        )));
886    }
887
888    async fn symbol(
889        &self,
890        params: WorkspaceSymbolParams,
891    ) -> Result<Option<Vec<SymbolInformation>>> {
892        let query = params.query;
893        info!("symbol with query: {query}");
894
895        let sync = self.read_sync().await;
896        let workspaces = sync.workspaces();
897        let all_frame_infos = workspaces
898            .iter()
899            .flat_map(workspace::Workspace::all_frame_infos);
900
901        let symbols = all_frame_infos
902            .filter_map(|fi| {
903                let score = fi.matches(&query);
904                if score > 0 {
905                    Some((score, fi))
906                } else {
907                    None
908                }
909            })
910            .sorted_by_key(|(score, _)| *score)
911            .rev()
912            .flat_map(|(_, fi)| {
913                let name = fi.label().unwrap_or(fi.iri.clone());
914
915                #[allow(deprecated)] // All fields need to be specified
916                fi.definitions
917                    .iter()
918                    .map(|definition| {
919                        let url = &definition.uri;
920                        let location = sync
921                            .get_internal_document(url)
922                            .map(|(doc, _)| {
923                                definition.clone().into_lsp(doc.rope(), self.encoding())
924                            })
925                            .and_then(|r| r.inspect_err(|e| error!("{e}")))
926                            .unwrap_or(Location {
927                                uri: url.clone(),
928                                range: lsp_types::Range::default(),
929                            });
930
931                        SymbolInformation {
932                            name: name.clone(),
933                            kind: fi.frame_type.into(),
934                            tags: None,
935                            deprecated: None,
936                            location,
937                            container_name: None,
938                        }
939                    })
940                    .collect_vec()
941            })
942            .collect_vec();
943
944        Ok(Some(symbols))
945    }
946
947    async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
948        info!(
949            "references {}:{}:{} {:?}",
950            params.text_document_position.text_document.uri,
951            params.text_document_position.position.line,
952            params.text_document_position.position.character,
953            params.context
954        );
955
956        let url = params.text_document_position.text_document.uri;
957        let sync = self.read_sync().await;
958        let (doc, workspace) = sync.get_internal_document(&url)?;
959
960        let pos: Position = Position::from_lsp(
961            params.text_document_position.position,
962            doc.rope(),
963            self.encoding(),
964        )?;
965
966        let node = doc
967            .tree()
968            .root_node()
969            .named_descendant_for_point_range(pos.into(), pos.into())
970            .ok_or(Error::PositionOutOfBounds(pos))?;
971
972        let full_iri_option = match node.kind() {
973            "full_iri" => Some(trim_full_iri(node_text(&node, doc.rope()))),
974            "simple_iri" | "abbreviated_iri" => {
975                let iri = node_text(&node, doc.rope());
976                Some(
977                    doc.abbreviated_iri_to_full_iri(&iri)
978                        .unwrap_or(iri.to_string()),
979                )
980            }
981            _ => None,
982        };
983
984        Ok(if let Some(full_iri) = full_iri_option {
985            let locations = workspace
986                .internal_documents()
987                .flat_map(|doc| {
988                    doc.references(&full_iri, params.context.include_declaration)
989                        .into_iter()
990                        .filter_map(|range| {
991                            range
992                                .into_lsp(doc.rope(), self.encoding())
993                                .inspect_log()
994                                .ok()
995                        })
996                        .map(|range| Location {
997                            uri: Url::from_file_path(doc.path())
998                                .expect("File path should be a valid URL"),
999                            range,
1000                        })
1001                })
1002                .collect_vec();
1003            Some(locations)
1004        } else {
1005            None
1006        })
1007    }
1008
1009    async fn prepare_rename(
1010        &self,
1011        params: TextDocumentPositionParams,
1012    ) -> Result<Option<PrepareRenameResponse>> {
1013        let url = params.text_document.uri;
1014
1015        let sync = self.read_sync().await;
1016        let (doc, _) = sync.get_internal_document(&url)?;
1017        let pos: Position = Position::from_lsp(params.position, doc.rope(), self.encoding())?;
1018
1019        fn node_range(position: Position, doc: &InternalDocument) -> Option<Range> {
1020            debug!("prepare_rename try {position:?}");
1021            let node = doc
1022                .tree()
1023                .root_node()
1024                .named_descendant_for_point_range(position.into(), position.into())?;
1025
1026            // This excludes prefix declaration, import and annotation target IRIs
1027            match node.parent()?.kind() {
1028                "datatype_iri"
1029                | "class_iri"
1030                | "annotation_property_iri"
1031                | "ontology_iri"
1032                | "data_property_iri"
1033                | "version_iri"
1034                | "object_property_iri"
1035                | "annotation_property_iri_annotated_list"
1036                | "individual_iri" => {}
1037                _ => return None,
1038            }
1039
1040            match node.kind() {
1041                "full_iri" => {
1042                    let range: Range = node.range().into();
1043                    let range = Range {
1044                        start: range.start.moved_right(1, doc.rope()),
1045                        end: range.end.moved_left(1, doc.rope()),
1046                    };
1047                    Some(range)
1048                }
1049                "simple_iri" => {
1050                    let range: Range = node.range().into();
1051                    Some(range)
1052                }
1053                "abbreviated_iri" => {
1054                    let range: Range = node.range().into();
1055                    let text = node_text(&node, doc.rope()).to_string();
1056                    let col_offset = text
1057                        .find(':')
1058                        .expect("abbreviated_iri to contain at least one :")
1059                        + 1;
1060                    let range = Range {
1061                        // The column offset will never be that big
1062                        #[allow(clippy::cast_possible_truncation)]
1063                        start: range.start.moved_right(col_offset as u32, doc.rope()),
1064                        ..range
1065                    };
1066                    Some(range)
1067                }
1068                _ => None,
1069            }
1070        }
1071
1072        let range = node_range(pos, doc)
1073            .or_else(|| {
1074                // we need to check one position left of the position because renames should work when the cursor is at end (inclusive) of a word
1075                // For example: ThisIsSomeIri| other text
1076                //                           ^
1077                //                       Cursor
1078                debug!("prepare rename try one position left");
1079                let position = pos.moved_left(1, doc.rope());
1080                node_range(position, doc)
1081            })
1082            .map(|range| {
1083                Ok(PrepareRenameResponse::Range(
1084                    range.into_lsp(doc.rope(), self.encoding())?,
1085                ))
1086            })
1087            .and_then(|r| r.inspect_err(|e: &Error| error!("{e}")).ok());
1088        debug!("prepare rename found range {range:?}");
1089
1090        Ok(range)
1091    }
1092
1093    async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
1094        debug!("rename {params:?}");
1095        let url = params.text_document_position.text_document.uri;
1096        let new_name = params.new_name;
1097
1098        let sync = self.read_sync().await;
1099        let (doc, workspace) = sync.get_internal_document(&url)?;
1100
1101        let pos: Position = Position::from_lsp(
1102            params.text_document_position.position,
1103            doc.rope(),
1104            self.encoding(),
1105        )?;
1106
1107        let old_and_new_iri = if let Some(x) = rename_helper(pos, doc, new_name.clone())? {
1108            Some(x)
1109        } else {
1110            // we need to check one position left of the position because renames should work when the cursor is at end (inclusive) of a word
1111            // For example: ThisIsSomeIri| other text
1112            //                           ^
1113            //                       Cursor
1114            debug!("prepare rename try one position left");
1115            let position = pos.moved_left(1, doc.rope());
1116            rename_helper(position, doc, new_name)?
1117        };
1118
1119        if let Some((full_iri, new_iri, iri_kind, original)) = old_and_new_iri {
1120            debug!("renaming resolved iris from {full_iri:?} to {new_iri:?}");
1121            let changes = workspace
1122                .internal_documents()
1123                .map(|doc| {
1124                    let edits = doc
1125                        .rename_edits(&full_iri, new_iri.as_ref(), &iri_kind, &original)
1126                        .into_iter()
1127                        .filter_map(|(range, str)| {
1128                            range
1129                                .into_lsp(doc.rope(), self.encoding())
1130                                .inspect_log()
1131                                .ok()
1132                                .map(|range| TextEdit {
1133                                    range,
1134                                    new_text: str,
1135                                })
1136                        })
1137                        .collect_vec();
1138                    (doc.uri().clone(), edits)
1139                })
1140                .collect();
1141
1142            return Ok(Some(WorkspaceEdit {
1143                changes: Some(changes),
1144                document_changes: None,
1145                change_annotations: None,
1146            }));
1147        }
1148        Ok(None)
1149    }
1150
1151    async fn shutdown(&self) -> Result<()> {
1152        info!("Shutdown");
1153        // TODO
1154        // let mut workspaces = self.workspaces.write();
1155        // workspaces.clear();
1156        Ok(())
1157    }
1158}
1159
1160fn single_path_response(path: &Path) -> GotoDefinitionResponse {
1161    GotoDefinitionResponse::Scalar(Location {
1162        uri: Url::from_file_path(path).expect("path should be valid url"),
1163        range: lsp_types::Range {
1164            start: lsp_types::Position {
1165                line: 0,
1166                character: 0,
1167            },
1168            end: lsp_types::Position {
1169                line: 0,
1170                character: 0,
1171            },
1172        },
1173    })
1174}
1175
1176type IriKindName = Option<(String, Option<String>, String, String)>;
1177
1178fn rename_helper(
1179    position: Position,
1180    doc: &InternalDocument,
1181    new_name: String,
1182) -> Result<IriKindName> {
1183    let node = doc
1184        .tree()
1185        .root_node()
1186        .named_descendant_for_point_range(position.into(), position.into())
1187        .ok_or(Error::PositionOutOfBounds(position))?;
1188
1189    match node.kind() {
1190        "full_iri" => {
1191            let iri_kind = node
1192                .parent()
1193                .expect("full_iri to have a parent")
1194                .kind()
1195                .to_string();
1196            let iri = trim_full_iri(node_text(&node, doc.rope()));
1197            Ok(Some((
1198                iri.clone(),
1199                Some(new_name.clone()),
1200                iri_kind,
1201                new_name,
1202            )))
1203        }
1204        "simple_iri" => {
1205            let iri = node_text(&node, doc.rope());
1206            let iri_kind = node
1207                .parent()
1208                .expect("simple_iri to have a parent")
1209                .kind()
1210                .to_string();
1211            Ok(Some((
1212                doc.abbreviated_iri_to_full_iri(&iri)
1213                    .unwrap_or(iri.to_string()),
1214                doc.abbreviated_iri_to_full_iri(&new_name),
1215                iri_kind,
1216                new_name,
1217            )))
1218        }
1219        "abbreviated_iri" => {
1220            let iri_kind = node
1221                .parent()
1222                .expect("abbreviated_iri to have a parent")
1223                .kind()
1224                .to_string();
1225            let iri = node_text(&node, doc.rope()).to_string();
1226            let (prefix, _) = iri
1227                .split_once(':')
1228                .expect("abbreviated_iri to contain at least one :");
1229            Ok(Some((
1230                doc.abbreviated_iri_to_full_iri(&iri).unwrap_or(iri.clone()),
1231                doc.abbreviated_iri_to_full_iri(&format!("{prefix}:{new_name}")),
1232                iri_kind,
1233                format!("{prefix}:{new_name}"),
1234            )))
1235        }
1236        _ => Ok(None),
1237    }
1238}
1239
1240impl Backend {
1241    async fn read_sync(&self) -> RwLockReadGuard<'_, SyncBackend> {
1242        self.sync.read().await
1243    }
1244    async fn write_sync(&self) -> RwLockWriteGuard<'_, SyncBackend> {
1245        self.sync.write().await
1246    }
1247
1248    fn encoding(&self) -> &PositionEncodingKind {
1249        self.position_encoding
1250            .get()
1251            .expect("position should be set")
1252    }
1253}
1254
1255pub trait USizeextra
1256where
1257    Self: Sized + TryInto<u32>,
1258{
1259    fn to_u32(self) -> u32 {
1260        TryInto::<u32>::try_into(self).unwrap_or(u32::MAX)
1261    }
1262}
1263
1264impl USizeextra for usize {}