typst_analyzer/
backend.rs1use dashmap::DashMap;
2use serde_json::Value;
3use tower_lsp::jsonrpc::Result;
4use tower_lsp::lsp_types::*;
5use tower_lsp::{Client, LanguageServer};
6use typst_syntax::Source;
7
8use crate::code_actions::handle::TypstCodeActions;
9use crate::completion::handle::TypstCompletion;
10use crate::diagnostics::handle::check_unclosed_delimiters;
11use crate::hints::handle::TypstInlayHints;
12
13#[derive(Debug)]
14pub struct Backend {
15 pub client: Client,
16 pub document: DashMap<String, String>,
17 pub sources: DashMap<String, Source>,
18}
19
20impl Backend {
21 pub fn position_to_offset(&self, text: &str, position: Position) -> Option<usize> {
22 let mut offset = 0;
23 for (line_idx, line) in text.lines().enumerate() {
24 if line_idx == position.line as usize {
25 return Some(offset + position.character as usize);
26 }
27 offset += line.len() + 1; }
29 None
30 }
31}
32
33#[tower_lsp::async_trait]
34impl LanguageServer for Backend {
35 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
36 Ok(InitializeResult {
37 server_info: None,
38 capabilities: ServerCapabilities {
39 inlay_hint_provider: Some(OneOf::Left(true)),
40 hover_provider: Some(HoverProviderCapability::Simple(true)),
41 code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
42 text_document_sync: Some(TextDocumentSyncCapability::Kind(
43 TextDocumentSyncKind::INCREMENTAL,
44 )),
45 completion_provider: Some(CompletionOptions {
46 resolve_provider: Some(false),
47 trigger_characters: Some(vec![".".to_owned()]),
48 work_done_progress_options: Default::default(),
49 all_commit_characters: None,
50 ..Default::default()
51 }),
52 execute_command_provider: Some(ExecuteCommandOptions {
53 commands: vec!["dummy.do_something".to_owned()],
54 work_done_progress_options: Default::default(),
55 }),
56 workspace: Some(WorkspaceServerCapabilities {
57 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
58 supported: Some(true),
59 change_notifications: Some(OneOf::Left(true)),
60 }),
61 file_operations: None,
62 }),
63 ..ServerCapabilities::default()
64 },
65 })
66 }
67
68 async fn initialized(&self, _: InitializedParams) {
69 self.client
70 .log_message(MessageType::INFO, "Language Server initialized!")
71 .await;
72 }
73
74 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
75 let cmp =
76 TypstCompletion::get_completion_items_from_typst(__self, params.text_document_position);
77 Ok(Some(CompletionResponse::Array(
78 cmp, )))
80 }
81
82 async fn hover(&self, _params: HoverParams) -> Result<Option<Hover>> {
83 Ok(Some(Hover {
84 contents: HoverContents::Markup(MarkupContent {
85 kind: MarkupKind::Markdown,
86 value: "# will this get displayed\nyes it will".to_owned(),
87 }),
88 range: None,
89 }))
90 }
91
92 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
93 let uri = params.text_document.uri.to_string();
94 if let Some(doc) = self.document.get(&uri) {
95 let hints = self.calculate_inlay_hints(&doc);
96 return Ok(Some(hints));
97 }
98 Ok(None)
99 }
100
101 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
102 let uri = params.text_document.uri.to_string();
103 self.client
104 .log_message(MessageType::INFO, "Code action requested")
105 .await;
106
107 if let Some(doc) = self.document.get(&uri) {
108 let content = doc.value();
109 let range = params.range;
110
111 let actions = self.calculate_code_actions(content, range, params.text_document.uri);
112
113 if !actions.is_empty() {
114 self.client
115 .log_message(MessageType::INFO, "Code actions generated")
116 .await;
117 return Ok(Some(actions));
118 }
119 }
120 Ok(None)
121 }
122
123 async fn did_open(&self, params: DidOpenTextDocumentParams) {
124 let text_document = params.text_document;
125 self.document
126 .insert(text_document.uri.to_string(), text_document.text.clone());
127 let source = Source::detached(text_document.text.clone());
128 self.sources.insert(text_document.uri.to_string(), source);
129 self.client
130 .log_message(
131 MessageType::INFO,
132 format!("Opened file: {}", text_document.uri),
133 )
134 .await;
135 }
136
137 async fn did_change(&self, params: DidChangeTextDocumentParams) {
138 let uri = params.text_document.uri.to_string();
139 if let Some(mut doc) = self.document.get_mut(&uri) {
140 for change in params.content_changes {
141 let mut current_text = doc.value().clone();
143
144 if let Some(range) = change.range {
146 let start = range.start;
147 let end = range.end;
148
149 let start_idx = self.position_to_offset(¤t_text, start).unwrap_or(0);
150 let end_idx = self
151 .position_to_offset(¤t_text, end)
152 .unwrap_or(current_text.len());
153
154 current_text.replace_range(start_idx..end_idx, &change.text);
155 } else {
156 current_text = change.text.clone();
158 }
159
160 *doc.value_mut() = current_text;
161 }
162 if let Some(mut source) = self.sources.get_mut(&uri) {
163 source.replace(doc.value());
164 }
165 let diagnostics = check_unclosed_delimiters(&doc);
167 self.client
168 .publish_diagnostics(params.text_document.uri.clone(), diagnostics, None)
169 .await;
170
171 self.client
172 .log_message(MessageType::INFO, "File changed!")
173 .await;
174 }
175 }
176
177 async fn did_save(&self, _: DidSaveTextDocumentParams) {
178 self.client
179 .log_message(MessageType::INFO, "File saved!")
180 .await;
181 }
182
183 async fn did_close(&self, params: DidCloseTextDocumentParams) {
184 let uri = params.text_document.uri.to_string();
185 self.document.remove(&uri);
186 self.client
187 .log_message(MessageType::INFO, format!("Closed file: {}", uri))
188 .await;
189 }
190
191 async fn shutdown(&self) -> Result<()> {
192 Ok(())
193 }
194
195 async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
196 self.client
197 .log_message(MessageType::INFO, "Workspace folders changed!")
198 .await;
199 }
200
201 async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {
202 self.client
203 .log_message(MessageType::INFO, "Configuration changed!")
204 .await;
205 }
206
207 async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {
208 self.client
209 .log_message(MessageType::INFO, "Watched files have changed!")
210 .await;
211 }
212
213 async fn execute_command(&self, _: ExecuteCommandParams) -> Result<Option<Value>> {
214 self.client
215 .log_message(MessageType::INFO, "Command executed!")
216 .await;
217
218 match self.client.apply_edit(WorkspaceEdit::default()).await {
219 Ok(res) if res.applied => {
220 self.client
221 .log_message(MessageType::INFO, "Edit applied")
222 .await
223 }
224 Ok(_) => {
225 self.client
226 .log_message(MessageType::INFO, "Edit rejected")
227 .await
228 }
229 Err(err) => self.client.log_message(MessageType::ERROR, err).await,
230 }
231
232 Ok(None)
233 }
234}