1mod code_actions;
20mod completion;
21mod document_symbols;
22mod index_queries;
23mod inlay_hints;
24mod locals_nav;
25mod position;
26mod project;
27mod publish;
28mod signature_help;
29mod structure;
30mod symbols;
31
32use std::path::PathBuf;
33use std::sync::Arc;
34
35use tokio::sync::RwLock;
36use tower_lsp::jsonrpc::Result as JsonRpcResult;
37use tower_lsp::lsp_types::request::{
38 GotoImplementationParams, GotoImplementationResponse, GotoTypeDefinitionParams,
39 GotoTypeDefinitionResponse,
40};
41use tower_lsp::lsp_types::*;
42use tower_lsp::{Client, LanguageServer, LspService, Server};
43
44use crate::project::ProjectConfig;
45
46const SERVER_NAME: &str = "bynkc-lsp";
47const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
48
49#[derive(Debug, Clone)]
51struct DocumentState {
52 text: String,
53 version: i32,
54}
55
56#[derive(Debug)]
61struct Analysis {
62 src_root: PathBuf,
65 index: bynk_check::index::ProjectIndex,
66 snapshots: std::collections::HashMap<PathBuf, String>,
68 versions: std::collections::HashMap<PathBuf, i32>,
71 diagnostics: std::collections::HashMap<PathBuf, Vec<bynk_ide::Diagnostic>>,
77 hints: bynk_check::hints::FileHints,
80 requirements: bynk_check::requirements::FileRequirements,
84 locals: bynk_check::locals::FileLocals,
88 expr_types: bynk_check::expr_types::FileExprTypes,
91 unit_sources: std::collections::HashMap<String, Vec<PathBuf>>,
94}
95
96impl Analysis {
97 fn diag_categories(&self) -> Vec<(PathBuf, String)> {
100 self.diagnostics
101 .iter()
102 .flat_map(|(path, diags)| {
103 diags
104 .iter()
105 .map(|d| (path.clone(), d.error.category.to_string()))
106 })
107 .collect()
108 }
109}
110
111#[derive(Debug, Default)]
113struct State {
114 project_root: Option<PathBuf>,
118 config: ProjectConfig,
120 docs: std::collections::HashMap<Url, DocumentState>,
122 published: std::collections::HashSet<Url>,
126 analysis_generation: u64,
129 analysis: Option<Arc<Analysis>>,
133}
134
135#[derive(Clone)]
136struct Backend {
137 client: Client,
138 state: Arc<RwLock<State>>,
139}
140
141impl Backend {
142 fn new(client: Client) -> Self {
143 Self {
144 client,
145 state: Arc::new(RwLock::new(State::default())),
146 }
147 }
148
149 fn find_project_root(start: &std::path::Path) -> Option<PathBuf> {
152 let mut current = if start.is_file() {
153 start.parent()?.to_path_buf()
154 } else {
155 start.to_path_buf()
156 };
157 loop {
158 let candidate = current.join("bynk.toml");
159 if candidate.is_file() {
160 return Some(current);
161 }
162 current = current.parent()?.to_path_buf();
163 }
164 }
165
166 async fn recompile_and_publish(&self, uri: &Url) {
170 if self.state.read().await.project_root.is_some() {
174 self.schedule_project_diagnostics().await;
175 return;
176 }
177 let text = {
178 let state = self.state.read().await;
179 state.docs.get(uri).map(|d| d.text.clone())
180 };
181 let Some(text) = text else { return };
182 let diagnostics = bynk_ide::diagnose(&text);
183 let lsp_diags: Vec<Diagnostic> = diagnostics
184 .into_iter()
185 .map(|d| make_diagnostic(&d, &text, uri))
186 .collect();
187 let version = {
188 let state = self.state.read().await;
189 state.docs.get(uri).map(|d| d.version)
190 };
191 self.client
192 .publish_diagnostics(uri.clone(), lsp_diags, version)
193 .await;
194 }
195
196 async fn schedule_project_diagnostics(&self) {
200 let generation = {
201 let mut state = self.state.write().await;
202 state.analysis_generation += 1;
203 state.analysis_generation
204 };
205 let this = self.clone();
206 tokio::spawn(async move {
207 tokio::time::sleep(std::time::Duration::from_millis(200)).await;
208 if this.state.read().await.analysis_generation != generation {
209 return;
210 }
211 this.run_project_diagnostics().await;
212 });
213 }
214
215 async fn run_project_diagnostics(&self) {
220 let (root, src_root, overlay, versions, previously_dirty) = {
221 let state = self.state.read().await;
222 let Some(root) = state.project_root.clone() else {
223 return;
224 };
225 let src_root = root.join(&state.config.src_dir);
226 let canonical_src_root = src_root.canonicalize().unwrap_or_else(|_| src_root.clone());
227 let mut overlay = std::collections::HashMap::new();
228 let mut versions = std::collections::HashMap::new();
229 for (uri, doc) in &state.docs {
230 if let Ok(p) = uri.to_file_path() {
231 let canonical = p.canonicalize().unwrap_or(p);
232 if let Ok(rel) = canonical.strip_prefix(&canonical_src_root) {
235 versions.insert(rel.to_path_buf(), doc.version);
236 }
237 overlay.insert(canonical, doc.text.clone());
238 }
239 }
240 (root, src_root, overlay, versions, state.published.clone())
241 };
242
243 let analysis_root = src_root.clone();
244 let Ok(result) = tokio::task::spawn_blocking(move || {
245 bynk_ide::diagnose_project(&analysis_root, &overlay)
246 })
247 .await
248 else {
249 return;
250 };
251
252 let mut new_by_uri: std::collections::HashMap<Url, Vec<Diagnostic>> =
253 std::collections::HashMap::new();
254 let mut snapshots = std::collections::HashMap::new();
255 let mut diagnostics: std::collections::HashMap<PathBuf, Vec<bynk_ide::Diagnostic>> =
256 std::collections::HashMap::new();
257 for file in &result.files {
258 let abs = src_root.join(&file.source_path);
259 let abs = abs.canonicalize().unwrap_or(abs);
260 let Ok(uri) = Url::from_file_path(&abs) else {
261 continue;
262 };
263 let diags: Vec<Diagnostic> = file
266 .diagnostics
267 .iter()
268 .map(|d| make_diagnostic(d, &file.text, &uri))
269 .collect();
270 new_by_uri.insert(uri, diags);
271 diagnostics.insert(file.source_path.clone(), file.diagnostics.clone());
272 snapshots.insert(file.source_path.clone(), file.text.clone());
273 }
274 {
278 let analysis = Arc::new(Analysis {
279 src_root: src_root.canonicalize().unwrap_or_else(|_| src_root.clone()),
280 index: result.index.clone(),
281 snapshots,
282 versions,
283 diagnostics,
284 hints: result.hints,
285 requirements: result.requirements,
286 locals: result.locals,
287 expr_types: result.expr_types,
288 unit_sources: result.unit_sources,
289 });
290 self.state.write().await.analysis = Some(analysis);
291 }
292 if !result.unattributed.is_empty()
295 && let Ok(toml_uri) = Url::from_file_path(root.join("bynk.toml"))
296 {
297 let entry = new_by_uri.entry(toml_uri).or_default();
298 for d in &result.unattributed {
299 entry.push(Diagnostic {
300 range: Default::default(),
301 severity: Some(match d.severity {
302 bynk_syntax::Severity::Error => DiagnosticSeverity::ERROR,
303 bynk_syntax::Severity::Warning => DiagnosticSeverity::WARNING,
304 }),
305 code: Some(tower_lsp::lsp_types::NumberOrString::String(
306 d.error.category.to_string(),
307 )),
308 message: d.error.message.clone(),
309 ..Default::default()
310 });
311 }
312 }
313
314 let (publishes, dirty) = publish::publish_plan(&previously_dirty, new_by_uri);
315 for (uri, diags) in publishes {
316 self.client.publish_diagnostics(uri, diags, None).await;
317 }
318 self.state.write().await.published = dirty;
319 }
320
321 async fn project_src_root(&self) -> Option<PathBuf> {
325 let state = self.state.read().await;
326 let root = state.project_root.as_ref()?;
327 Some(root.join(&state.config.src_dir))
328 }
329
330 fn local_sites(
333 &self,
334 analysis: &Analysis,
335 rel: &std::path::Path,
336 offset: usize,
337 ) -> Option<Vec<bynk_syntax::span::Span>> {
338 let text = analysis.snapshots.get(rel)?;
339 let locals = analysis.locals.get(rel)?;
340 crate::locals_nav::local_sites_at(locals, text, offset)
341 }
342
343 async fn locals_completions(&self, uri: &Url, pos: Position) -> Vec<CompletionItem> {
349 let analysis = self.state.read().await.analysis.clone();
350 let Some(analysis) = analysis else {
351 return Vec::new();
352 };
353 let Some(rel) = Self::uri_to_rel(&analysis, uri) else {
354 return Vec::new();
355 };
356 let (Some(text), Some(locals)) = (analysis.snapshots.get(&rel), analysis.locals.get(&rel))
357 else {
358 return Vec::new();
359 };
360 let Some(offset) = crate::position::position_to_offset(text, pos) else {
361 return Vec::new();
362 };
363 bynk_check::locals::locals_at(locals, offset)
364 .into_iter()
365 .map(|b| CompletionItem {
366 label: b.name.clone(),
367 kind: Some(CompletionItemKind::VARIABLE),
368 detail: Some(b.ty.clone()),
369 ..Default::default()
370 })
371 .collect()
372 }
373
374 fn local_locations(
376 &self,
377 analysis: &Analysis,
378 rel: &std::path::Path,
379 spans: &[bynk_syntax::span::Span],
380 ) -> Vec<Location> {
381 let Some(text) = analysis.snapshots.get(rel) else {
382 return Vec::new();
383 };
384 let Ok(uri) = Url::from_file_path(analysis.src_root.join(rel)) else {
385 return Vec::new();
386 };
387 spans
388 .iter()
389 .map(|s| Location {
390 uri: uri.clone(),
391 range: crate::position::span_to_range(text, *s),
392 })
393 .collect()
394 }
395
396 async fn value_member_completions(
402 &self,
403 uri: &Url,
404 text: &str,
405 offset: usize,
406 ) -> Vec<CompletionItem> {
407 let Some((rewritten, recv_offset)) = completion::value_receiver_rewrite(text, offset)
408 else {
409 return Vec::new();
410 };
411 let Some(ty) = self.type_receiver(uri, rewritten, recv_offset).await else {
412 return Vec::new();
413 };
414 let src_root = self.project_src_root().await;
415 completion::value_member_candidates(&ty, text, src_root.as_deref())
416 .into_iter()
417 .map(to_completion_item)
418 .collect()
419 }
420
421 async fn type_receiver(
426 &self,
427 uri: &Url,
428 rewritten: String,
429 recv_offset: usize,
430 ) -> Option<bynk_check::checker::Ty> {
431 let src_root = self.project_src_root().await?;
432 let canonical_src_root = src_root.canonicalize().unwrap_or_else(|_| src_root.clone());
433 let cur = uri.to_file_path().ok()?;
434 let cur = cur.canonicalize().unwrap_or(cur);
435 let rel = cur.strip_prefix(&canonical_src_root).ok()?.to_path_buf();
436 let overlay = {
438 let state = self.state.read().await;
439 let mut ov = std::collections::HashMap::new();
440 for (u, doc) in &state.docs {
441 if let Ok(p) = u.to_file_path() {
442 let canonical = p.canonicalize().unwrap_or(p);
443 let t = if u == uri {
444 rewritten.clone()
445 } else {
446 doc.text.clone()
447 };
448 ov.insert(canonical, t);
449 }
450 }
451 ov
452 };
453 let result =
454 tokio::task::spawn_blocking(move || bynk_ide::diagnose_project(&src_root, &overlay))
455 .await
456 .ok()?;
457 let (_, entries) = result.expr_types.iter().find(|(p, _)| **p == rel)?;
458 bynk_check::expr_types::type_at_offset(entries, recv_offset).cloned()
459 }
460
461 async fn ensure_analysis(&self) -> Option<Arc<Analysis>> {
465 if let Some(a) = self.state.read().await.analysis.clone() {
466 return Some(a);
467 }
468 self.run_project_diagnostics().await;
469 self.state.read().await.analysis.clone()
470 }
471
472 async fn fresh_analysis(&self) -> Option<Arc<Analysis>> {
475 self.run_project_diagnostics().await;
476 self.state.read().await.analysis.clone()
477 }
478
479 fn uri_to_rel(analysis: &Analysis, uri: &Url) -> Option<PathBuf> {
481 let p = uri.to_file_path().ok()?;
482 let canonical = p.canonicalize().unwrap_or(p);
483 canonical
484 .strip_prefix(&analysis.src_root)
485 .ok()
486 .map(|r| r.to_path_buf())
487 }
488
489 async fn unit_reference_definition(&self, uri: &Url, pos: Position) -> Option<Location> {
495 let (text, analysis) = {
496 let s = self.state.read().await;
497 (s.docs.get(uri).map(|d| d.text.clone()), s.analysis.clone())
498 };
499 let (text, analysis) = (text?, analysis?);
500 let offset = cursor_byte_offset(&text, pos);
501 for (unit, span) in crate::symbols::unit_reference_spans(&text) {
502 if span.start <= offset && offset <= span.end {
503 let rel = analysis.unit_sources.get(&unit)?.first()?;
504 let target = Url::from_file_path(analysis.src_root.join(rel)).ok()?;
505 return Some(Location {
506 uri: target,
507 range: Range::default(),
508 });
509 }
510 }
511 None
512 }
513
514 fn site_to_location(
517 analysis: &Analysis,
518 site: &bynk_check::index::SiteRef,
519 ) -> Option<Location> {
520 let text = analysis.snapshots.get(&site.path)?;
521 let abs = analysis.src_root.join(&site.path);
522 let uri = Url::from_file_path(abs).ok()?;
523 Some(Location {
524 uri,
525 range: crate::position::span_to_range(text, site.span),
526 })
527 }
528
529 fn call_hierarchy_item(
534 analysis: &Analysis,
535 key: &bynk_check::index::SymbolKey,
536 def: &bynk_check::index::SiteRef,
537 ) -> Option<CallHierarchyItem> {
538 let location = Self::site_to_location(analysis, def)?;
539 Some(CallHierarchyItem {
540 name: key.name.clone(),
541 kind: lsp_symbol_kind(key.kind),
542 tags: None,
543 detail: Some(key.unit.clone()),
544 uri: location.uri,
545 range: location.range,
546 selection_range: location.range,
547 data: serde_json::to_value(SerKey::from(key)).ok(),
548 })
549 }
550
551 fn call_ranges(analysis: &Analysis, sites: &[&bynk_check::index::SiteRef]) -> Vec<Range> {
554 sites
555 .iter()
556 .filter_map(|s| {
557 let text = analysis.snapshots.get(&s.path)?;
558 Some(crate::position::span_to_range(text, s.span))
559 })
560 .collect()
561 }
562
563 async fn semantic_tokens_for(&self, uri: &Url, range: Option<Range>) -> Vec<SemanticToken> {
568 let analysis = { self.state.read().await.analysis.clone() };
569 let Some(analysis) = analysis else {
570 return Vec::new();
571 };
572 let Some(rel) = Self::uri_to_rel(&analysis, uri) else {
573 return Vec::new();
574 };
575 let Some(text) = analysis.snapshots.get(&rel) else {
576 return Vec::new();
577 };
578 let span = match range {
579 None => None,
580 Some(r) => {
583 let (Some(start), Some(end)) = (
584 crate::position::position_to_offset(text, r.start),
585 crate::position::position_to_offset(text, r.end),
586 ) else {
587 return Vec::new();
588 };
589 Some(bynk_syntax::span::Span::new(start, end))
590 }
591 };
592 let lt = analysis
593 .locals
594 .get(&rel)
595 .map(|l| crate::locals_nav::local_token_sites(l, text))
596 .unwrap_or_default();
597 crate::index_queries::semantic_tokens(&analysis.index, <, &rel, text, span)
598 }
599
600 async fn index_position(
603 &self,
604 uri: &Url,
605 position: Position,
606 fresh: bool,
607 ) -> Option<(Arc<Analysis>, PathBuf, usize)> {
608 let analysis = if fresh {
609 self.fresh_analysis().await?
610 } else {
611 self.ensure_analysis().await?
612 };
613 let rel = Self::uri_to_rel(&analysis, uri)?;
614 let text = analysis.snapshots.get(&rel)?;
615 let offset = crate::position::position_to_offset(text, position)?;
616 Some((analysis, rel, offset))
617 }
618
619 async fn identifier_at(
623 &self,
624 uri: &Url,
625 position: Position,
626 ) -> Option<(String, bynk_syntax::span::Span, String)> {
627 let text = {
628 let state = self.state.read().await;
629 state.docs.get(uri)?.text.clone()
630 };
631 let offset = crate::position::position_to_offset(&text, position)?;
632 let tokens = bynk_syntax::lexer::tokenize(&text).ok()?;
633 for t in &tokens {
635 if t.span.start <= offset
636 && offset < t.span.end
637 && matches!(
638 t.kind,
639 bynk_syntax::lexer::TokenKind::Ident
640 | bynk_syntax::lexer::TokenKind::Int
641 | bynk_syntax::lexer::TokenKind::String
642 | bynk_syntax::lexer::TokenKind::Bool
643 | bynk_syntax::lexer::TokenKind::Float
644 | bynk_syntax::lexer::TokenKind::Result
645 | bynk_syntax::lexer::TokenKind::Option
646 | bynk_syntax::lexer::TokenKind::Effect
647 )
648 {
649 let name = text[t.span.start..t.span.end].to_string();
650 return Some((name, t.span, text));
651 }
652 }
653 None
654 }
655}
656
657#[tower_lsp::async_trait]
658impl LanguageServer for Backend {
659 async fn initialize(&self, params: InitializeParams) -> JsonRpcResult<InitializeResult> {
660 if let Some(folders) = ¶ms.workspace_folders
662 && let Some(first) = folders.first()
663 && let Ok(path) = first.uri.to_file_path()
664 {
665 let mut state = self.state.write().await;
666 if let Some(root) = Self::find_project_root(&path) {
667 state.config = project::load_config(&root).unwrap_or_default();
668 state.project_root = Some(root);
669 }
670 }
671 Ok(InitializeResult {
672 capabilities: server_capabilities(),
673 server_info: Some(ServerInfo {
674 name: SERVER_NAME.into(),
675 version: Some(SERVER_VERSION.into()),
676 }),
677 })
678 }
679
680 async fn initialized(&self, _: InitializedParams) {
681 let root = { self.state.read().await.project_root.clone() };
682 match root {
683 Some(root) => {
684 self.client
685 .log_message(
686 MessageType::INFO,
687 format!("bynkc-lsp: project root at {}", root.display()),
688 )
689 .await;
690 }
691 None => {
692 self.client
693 .log_message(
694 MessageType::INFO,
695 "bynkc-lsp: no bynk.toml found; single-file mode",
696 )
697 .await;
698 }
699 }
700 }
701
702 async fn shutdown(&self) -> JsonRpcResult<()> {
703 Ok(())
704 }
705
706 async fn did_open(&self, params: DidOpenTextDocumentParams) {
707 let uri = params.text_document.uri.clone();
708 {
709 let mut state = self.state.write().await;
710 if state.project_root.is_none()
712 && let Ok(path) = uri.to_file_path()
713 && let Some(root) = Self::find_project_root(&path)
714 {
715 state.config = project::load_config(&root).unwrap_or_default();
716 state.project_root = Some(root);
717 }
718 state.docs.insert(
719 uri.clone(),
720 DocumentState {
721 text: params.text_document.text,
722 version: params.text_document.version,
723 },
724 );
725 }
726 self.recompile_and_publish(&uri).await;
727 }
728
729 async fn did_change(&self, params: DidChangeTextDocumentParams) {
730 let uri = params.text_document.uri.clone();
731 {
732 let mut state = self.state.write().await;
733 if let Some(doc) = state.docs.get_mut(&uri)
734 && let Some(change) = params.content_changes.into_iter().next_back()
735 {
736 doc.text = change.text;
737 doc.version = params.text_document.version;
738 }
739 }
740 let debounce_ms = {
744 let s = self.state.read().await;
745 s.config.diagnostics_debounce_ms
746 };
747 let backend = self.clone();
748 tokio::spawn(async move {
749 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
750 backend.recompile_and_publish(&uri).await;
751 });
752 }
753
754 async fn did_close(&self, params: DidCloseTextDocumentParams) {
755 let uri = params.text_document.uri;
756 let mut state = self.state.write().await;
757 state.docs.remove(&uri);
758 }
759
760 async fn hover(&self, params: HoverParams) -> JsonRpcResult<Option<Hover>> {
761 let uri = params.text_document_position_params.text_document.uri;
762 let pos = params.text_document_position_params.position;
763 if let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await
768 && let Some((key, def)) =
769 crate::index_queries::definition_at(&analysis.index, &rel, offset)
770 && let Some(def_text) = analysis.snapshots.get(&def.path)
771 && let Some(content) = crate::symbols::describe_symbol(def_text, &key.name)
772 {
773 return Ok(Some(Hover {
774 contents: HoverContents::Markup(MarkupContent {
775 kind: MarkupKind::Markdown,
776 value: content,
777 }),
778 range: None,
779 }));
780 }
781 let Some((name, _span, text)) = self.identifier_at(&uri, pos).await else {
782 return Ok(None);
783 };
784 let content = match crate::symbols::describe_symbol(&text, &name) {
786 Some(local) => local,
787 None => {
788 let src_root = self.project_src_root().await;
793 match src_root
794 .and_then(|root| crate::symbols::describe_symbol_cross_file(&root, &uri, &name))
795 .map(|(_other_uri, desc)| desc)
796 .or_else(|| crate::symbols::describe_firstparty_symbol(&name))
797 {
798 Some(desc) => desc,
799 None => return Ok(None),
800 }
801 }
802 };
803 Ok(Some(Hover {
804 contents: HoverContents::Markup(MarkupContent {
805 kind: MarkupKind::Markdown,
806 value: content,
807 }),
808 range: None,
809 }))
810 }
811
812 async fn signature_help(
814 &self,
815 params: SignatureHelpParams,
816 ) -> JsonRpcResult<Option<SignatureHelp>> {
817 let uri = params.text_document_position_params.text_document.uri;
818 let pos = params.text_document_position_params.position;
819 let text = {
820 let s = self.state.read().await;
821 s.docs.get(&uri).map(|d| d.text.clone())
822 };
823 let Some(text) = text else { return Ok(None) };
824 let offset = cursor_byte_offset(&text, pos);
825 let Some(ctx) = crate::signature_help::call_context(&text, offset) else {
826 return Ok(None);
827 };
828 let src_root = self.project_src_root().await;
829 let label =
831 match crate::signature_help::resolve_label(&ctx.callee, &text, src_root.as_deref()) {
832 Some(l) => Some(l),
833 None => match crate::signature_help::value_receiver_method(&ctx.callee) {
836 Some((_, method)) => {
837 if let Some((rewritten, recv_offset)) =
838 crate::signature_help::value_receiver_rewrite(
839 &text,
840 &ctx.callee,
841 ctx.open_paren,
842 offset,
843 )
844 && let Some(ty) = self.type_receiver(&uri, rewritten, recv_offset).await
845 {
846 crate::signature_help::kernel_method_signature(&ty, method)
847 } else {
848 None
849 }
850 }
851 None => None,
852 },
853 };
854 let Some(label) = label else { return Ok(None) };
855 let active = ctx.active_param as u32;
856 let parameters: Vec<ParameterInformation> = crate::signature_help::param_ranges(&label)
857 .into_iter()
858 .map(|(s, e)| ParameterInformation {
859 label: ParameterLabel::LabelOffsets([s as u32, e as u32]),
860 documentation: None,
861 })
862 .collect();
863 Ok(Some(SignatureHelp {
864 signatures: vec![SignatureInformation {
865 label,
866 documentation: None,
867 parameters: Some(parameters),
868 active_parameter: Some(active),
869 }],
870 active_signature: Some(0),
871 active_parameter: Some(active),
872 }))
873 }
874
875 async fn code_lens(&self, params: CodeLensParams) -> JsonRpcResult<Option<Vec<CodeLens>>> {
878 let uri = params.text_document.uri;
879 let analysis = { self.state.read().await.analysis.clone() };
880 let Some(analysis) = analysis else {
881 return Ok(Some(Vec::new()));
882 };
883 let Some(rel) = Self::uri_to_rel(&analysis, &uri) else {
884 return Ok(Some(Vec::new()));
885 };
886 let Some(text) = analysis.snapshots.get(&rel) else {
887 return Ok(Some(Vec::new()));
888 };
889 let lenses: Vec<CodeLens> = crate::index_queries::code_lenses(&analysis.index, &rel)
890 .into_iter()
891 .map(|(def, refs)| {
892 let range = crate::position::span_to_range(text, def.span);
893 let locations: Vec<Location> = refs
894 .iter()
895 .filter_map(|r| Self::site_to_location(&analysis, r))
896 .collect();
897 let n = refs.len();
898 CodeLens {
899 range,
900 command: Some(Command {
901 title: format!("{n} reference{}", if n == 1 { "" } else { "s" }),
902 command: "editor.action.showReferences".to_string(),
905 arguments: Some(vec![
906 serde_json::to_value(&uri).unwrap_or_default(),
907 serde_json::to_value(range.start).unwrap_or_default(),
908 serde_json::to_value(&locations).unwrap_or_default(),
909 ]),
910 }),
911 data: None,
912 }
913 })
914 .collect();
915 Ok(Some(lenses))
916 }
917
918 async fn prepare_call_hierarchy(
919 &self,
920 params: CallHierarchyPrepareParams,
921 ) -> JsonRpcResult<Option<Vec<CallHierarchyItem>>> {
922 let uri = params.text_document_position_params.text_document.uri;
923 let pos = params.text_document_position_params.position;
924 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
925 return Ok(None);
926 };
927 let Some((key, def)) =
928 crate::index_queries::prepare_call_hierarchy(&analysis.index, &rel, offset)
929 else {
930 return Ok(None);
931 };
932 Ok(Self::call_hierarchy_item(&analysis, key, def).map(|item| vec![item]))
933 }
934
935 async fn incoming_calls(
936 &self,
937 params: CallHierarchyIncomingCallsParams,
938 ) -> JsonRpcResult<Option<Vec<CallHierarchyIncomingCall>>> {
939 let analysis = { self.state.read().await.analysis.clone() };
940 let Some(analysis) = analysis else {
941 return Ok(Some(Vec::new()));
942 };
943 let Some(key) = SerKey::read(¶ms.item.data) else {
944 return Ok(Some(Vec::new()));
945 };
946 let calls = crate::index_queries::incoming_calls(&analysis.index, &key)
947 .into_iter()
948 .filter_map(|rel| {
949 let from = Self::call_hierarchy_item(&analysis, rel.key, rel.def)?;
950 let from_ranges = Self::call_ranges(&analysis, &rel.sites);
951 Some(CallHierarchyIncomingCall { from, from_ranges })
952 })
953 .collect();
954 Ok(Some(calls))
955 }
956
957 async fn outgoing_calls(
958 &self,
959 params: CallHierarchyOutgoingCallsParams,
960 ) -> JsonRpcResult<Option<Vec<CallHierarchyOutgoingCall>>> {
961 let analysis = { self.state.read().await.analysis.clone() };
962 let Some(analysis) = analysis else {
963 return Ok(Some(Vec::new()));
964 };
965 let Some(key) = SerKey::read(¶ms.item.data) else {
966 return Ok(Some(Vec::new()));
967 };
968 let calls = crate::index_queries::outgoing_calls(&analysis.index, &key)
969 .into_iter()
970 .filter_map(|rel| {
971 let to = Self::call_hierarchy_item(&analysis, rel.key, rel.def)?;
972 let from_ranges = Self::call_ranges(&analysis, &rel.sites);
973 Some(CallHierarchyOutgoingCall { to, from_ranges })
974 })
975 .collect();
976 Ok(Some(calls))
977 }
978
979 async fn goto_implementation(
984 &self,
985 params: GotoImplementationParams,
986 ) -> JsonRpcResult<Option<GotoImplementationResponse>> {
987 let uri = params.text_document_position_params.text_document.uri;
988 let pos = params.text_document_position_params.position;
989 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
990 return Ok(None);
991 };
992 let Some((key, _)) = analysis.index.symbol_at(&rel, offset) else {
993 return Ok(None);
994 };
995 if key.kind != bynk_check::index::SymbolKind::Capability {
996 return Ok(None);
997 }
998 let locations: Vec<Location> = crate::index_queries::implementations(&analysis.index, key)
999 .into_iter()
1000 .filter_map(|d| Self::site_to_location(&analysis, d))
1001 .collect();
1002 if locations.is_empty() {
1003 return Ok(None);
1004 }
1005 Ok(Some(GotoDefinitionResponse::Array(locations)))
1006 }
1007
1008 async fn goto_type_definition(
1014 &self,
1015 params: GotoTypeDefinitionParams,
1016 ) -> JsonRpcResult<Option<GotoTypeDefinitionResponse>> {
1017 let uri = params.text_document_position_params.text_document.uri;
1018 let pos = params.text_document_position_params.position;
1019 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
1020 return Ok(None);
1021 };
1022 let Some(entries) = analysis.expr_types.get(&rel) else {
1023 return Ok(None);
1024 };
1025 let Some(ty) = bynk_check::expr_types::type_at_offset(entries, offset) else {
1026 return Ok(None);
1027 };
1028 let Some(name) = crate::index_queries::named_type_target(ty) else {
1029 return Ok(None);
1030 };
1031 let locations: Vec<Location> =
1032 crate::index_queries::type_definitions_named(&analysis.index, name)
1033 .into_iter()
1034 .filter_map(|d| Self::site_to_location(&analysis, d))
1035 .collect();
1036 if locations.is_empty() {
1037 return Ok(None);
1038 }
1039 Ok(Some(GotoDefinitionResponse::Array(locations)))
1040 }
1041
1042 async fn document_link(
1048 &self,
1049 params: DocumentLinkParams,
1050 ) -> JsonRpcResult<Option<Vec<DocumentLink>>> {
1051 let uri = params.text_document.uri;
1052 let (text, analysis) = {
1053 let s = self.state.read().await;
1054 (s.docs.get(&uri).map(|d| d.text.clone()), s.analysis.clone())
1055 };
1056 let (Some(text), Some(analysis)) = (text, analysis) else {
1057 return Ok(None);
1058 };
1059 let links: Vec<DocumentLink> = crate::symbols::unit_reference_spans(&text)
1060 .into_iter()
1061 .filter_map(|(unit, span)| {
1062 let rel = analysis.unit_sources.get(&unit)?.first()?;
1063 let target = Url::from_file_path(analysis.src_root.join(rel)).ok()?;
1064 Some(DocumentLink {
1065 range: crate::position::span_to_range(&text, span),
1066 target: Some(target),
1067 tooltip: Some(format!("Open unit `{unit}`")),
1068 data: None,
1069 })
1070 })
1071 .collect();
1072 Ok((!links.is_empty()).then_some(links))
1073 }
1074
1075 async fn completion(
1076 &self,
1077 params: CompletionParams,
1078 ) -> JsonRpcResult<Option<CompletionResponse>> {
1079 let uri = params.text_document_position.text_document.uri;
1080 let pos = params.text_document_position.position;
1081 let text = {
1082 let s = self.state.read().await;
1083 s.docs.get(&uri).map(|d| d.text.clone())
1084 };
1085 let Some(text) = text else { return Ok(None) };
1086 let line_prefix = text
1088 .lines()
1089 .nth(pos.line as usize)
1090 .map(|l| {
1091 let end = (pos.character as usize).min(l.len());
1092 l.get(..end).unwrap_or(l)
1093 })
1094 .unwrap_or("")
1095 .to_string();
1096 let src_root = self.project_src_root().await;
1097 let candidates = completion::complete(&line_prefix, &text, src_root.as_deref());
1098 let mut items: Vec<CompletionItem> =
1099 candidates.into_iter().map(to_completion_item).collect();
1100 if completion::is_keyword_position(&line_prefix)
1105 || completion::is_expression_position(&line_prefix)
1106 {
1107 items.extend(self.locals_completions(&uri, pos).await);
1108 }
1109 if items.is_empty() {
1110 let offset = cursor_byte_offset(&text, pos);
1114 let value_items = self.value_member_completions(&uri, &text, offset).await;
1115 return Ok((!value_items.is_empty()).then_some(CompletionResponse::Array(value_items)));
1116 }
1117 stamp_resolve_data(&mut items, &uri);
1119 Ok(Some(CompletionResponse::Array(items)))
1120 }
1121
1122 async fn completion_resolve(&self, mut item: CompletionItem) -> JsonRpcResult<CompletionItem> {
1129 if item.documentation.is_some() {
1130 return Ok(item);
1131 }
1132 let Some(uri) = item
1133 .data
1134 .as_ref()
1135 .and_then(|d| d.get("uri"))
1136 .and_then(serde_json::Value::as_str)
1137 .and_then(|s| Url::parse(s).ok())
1138 else {
1139 return Ok(item);
1140 };
1141 let local = {
1142 let s = self.state.read().await;
1143 s.docs.get(&uri).map(|d| d.text.clone())
1144 };
1145 let doc = match local
1146 .as_deref()
1147 .and_then(|t| crate::symbols::describe_symbol(t, &item.label))
1148 {
1149 Some(md) => Some(md),
1150 None => self
1151 .project_src_root()
1152 .await
1153 .and_then(|root| {
1154 crate::symbols::describe_symbol_cross_file(&root, &uri, &item.label)
1155 })
1156 .map(|(_uri, md)| md)
1157 .or_else(|| crate::symbols::describe_firstparty_symbol(&item.label)),
1160 };
1161 if let Some(md) = doc {
1162 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
1163 kind: MarkupKind::Markdown,
1164 value: md,
1165 }));
1166 }
1167 Ok(item)
1168 }
1169
1170 async fn goto_definition(
1171 &self,
1172 params: GotoDefinitionParams,
1173 ) -> JsonRpcResult<Option<GotoDefinitionResponse>> {
1174 let uri = params
1175 .text_document_position_params
1176 .text_document
1177 .uri
1178 .clone();
1179 let pos = params.text_document_position_params.position;
1180 if let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await {
1185 if let Some((_, def)) =
1186 crate::index_queries::definition_at(&analysis.index, &rel, offset)
1187 && let Some(location) = Self::site_to_location(&analysis, def)
1188 {
1189 return Ok(Some(GotoDefinitionResponse::Scalar(location)));
1190 }
1191 if let Some(text) = analysis.snapshots.get(&rel)
1194 && let Some(locals) = analysis.locals.get(&rel)
1195 && let Some(def) = crate::locals_nav::local_definition_at(locals, text, offset)
1196 && let Some(location) = self
1197 .local_locations(&analysis, &rel, &[def])
1198 .into_iter()
1199 .next()
1200 {
1201 return Ok(Some(GotoDefinitionResponse::Scalar(location)));
1202 }
1203 }
1204 if let Some(location) = self.unit_reference_definition(&uri, pos).await {
1209 return Ok(Some(GotoDefinitionResponse::Scalar(location)));
1210 }
1211 let Some((name, _span, text)) = self.identifier_at(&uri, pos).await else {
1212 return Ok(None);
1213 };
1214 if let Some(decl_span) = crate::symbols::find_declaration_span(&text, &name) {
1215 let range = crate::position::span_to_range(&text, decl_span);
1216 return Ok(Some(GotoDefinitionResponse::Scalar(Location {
1217 uri,
1218 range,
1219 })));
1220 }
1221 if let Some(root) = self.project_src_root().await
1223 && let Some(found) = crate::symbols::find_declaration_cross_file(&root, &uri, &name)
1224 {
1225 let range = crate::position::span_to_range(&found.source, found.span);
1226 return Ok(Some(GotoDefinitionResponse::Scalar(Location {
1227 uri: found.uri,
1228 range,
1229 })));
1230 }
1231 Ok(None)
1232 }
1233
1234 async fn formatting(
1235 &self,
1236 params: DocumentFormattingParams,
1237 ) -> JsonRpcResult<Option<Vec<TextEdit>>> {
1238 let uri = params.text_document.uri;
1239 let text = {
1240 let s = self.state.read().await;
1241 s.docs.get(&uri).map(|d| d.text.clone())
1242 };
1243 let Some(text) = text else { return Ok(None) };
1244 let opts = {
1245 let s = self.state.read().await;
1246 s.config.format_options()
1247 };
1248 match bynk_fmt::format_source(&text, &opts) {
1249 Ok(formatted) => {
1250 if formatted == text {
1251 Ok(Some(Vec::new()))
1252 } else {
1253 let end_pos = crate::position::end_position(&text);
1255 Ok(Some(vec![TextEdit {
1256 range: Range {
1257 start: Position::new(0, 0),
1258 end: end_pos,
1259 },
1260 new_text: formatted,
1261 }]))
1262 }
1263 }
1264 Err(_) => {
1265 Ok(Some(Vec::new()))
1268 }
1269 }
1270 }
1271
1272 async fn range_formatting(
1273 &self,
1274 params: DocumentRangeFormattingParams,
1275 ) -> JsonRpcResult<Option<Vec<TextEdit>>> {
1276 self.formatting(DocumentFormattingParams {
1279 text_document: params.text_document,
1280 options: params.options,
1281 work_done_progress_params: params.work_done_progress_params,
1282 })
1283 .await
1284 }
1285
1286 async fn document_symbol(
1287 &self,
1288 params: DocumentSymbolParams,
1289 ) -> JsonRpcResult<Option<DocumentSymbolResponse>> {
1290 let uri = params.text_document.uri;
1292 let text = {
1293 let s = self.state.read().await;
1294 s.docs.get(&uri).map(|d| d.text.clone())
1295 };
1296 let Some(text) = text else { return Ok(None) };
1297 let syms = crate::document_symbols::outline(&text);
1298 if syms.is_empty() {
1299 return Ok(None);
1300 }
1301 Ok(Some(DocumentSymbolResponse::Nested(syms)))
1302 }
1303
1304 async fn folding_range(
1307 &self,
1308 params: FoldingRangeParams,
1309 ) -> JsonRpcResult<Option<Vec<FoldingRange>>> {
1310 let uri = params.text_document.uri;
1311 let text = {
1312 let s = self.state.read().await;
1313 s.docs.get(&uri).map(|d| d.text.clone())
1314 };
1315 let Some(text) = text else { return Ok(None) };
1316 Ok(Some(crate::structure::folding_ranges(&text)))
1317 }
1318
1319 async fn selection_range(
1322 &self,
1323 params: SelectionRangeParams,
1324 ) -> JsonRpcResult<Option<Vec<SelectionRange>>> {
1325 let uri = params.text_document.uri;
1326 let text = {
1327 let s = self.state.read().await;
1328 s.docs.get(&uri).map(|d| d.text.clone())
1329 };
1330 let Some(text) = text else { return Ok(None) };
1331 Ok(Some(crate::structure::selection_ranges(
1332 &text,
1333 ¶ms.positions,
1334 )))
1335 }
1336
1337 async fn references(&self, params: ReferenceParams) -> JsonRpcResult<Option<Vec<Location>>> {
1338 let uri = params.text_document_position.text_document.uri;
1339 let pos = params.text_document_position.position;
1340 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
1341 return Ok(None);
1342 };
1343 let include_decl = params.context.include_declaration;
1344 if let Some(sites) =
1345 crate::index_queries::sites_for(&analysis.index, &rel, offset, include_decl)
1346 {
1347 let locations: Vec<Location> = sites
1348 .into_iter()
1349 .filter_map(|site| Self::site_to_location(&analysis, site))
1350 .collect();
1351 return Ok(Some(locations));
1352 }
1353 if let Some(spans) = self.local_sites(&analysis, &rel, offset) {
1355 let spans = if include_decl {
1356 &spans[..]
1357 } else {
1358 &spans[1..]
1359 }; let locations = self.local_locations(&analysis, &rel, spans);
1361 return Ok(Some(locations));
1362 }
1363 Ok(None)
1364 }
1365
1366 async fn code_action(
1372 &self,
1373 params: CodeActionParams,
1374 ) -> JsonRpcResult<Option<CodeActionResponse>> {
1375 let uri = params.text_document.uri;
1376 let analysis = { self.state.read().await.analysis.clone() };
1377 let Some(analysis) = analysis else {
1378 return Ok(Some(Vec::new()));
1379 };
1380 let Some(rel) = Self::uri_to_rel(&analysis, &uri) else {
1381 return Ok(Some(Vec::new()));
1382 };
1383 let (Some(text), Some(diags)) =
1384 (analysis.snapshots.get(&rel), analysis.diagnostics.get(&rel))
1385 else {
1386 return Ok(Some(Vec::new()));
1387 };
1388 let (Some(start), Some(end)) = (
1391 crate::position::position_to_offset(text, params.range.start),
1392 crate::position::position_to_offset(text, params.range.end),
1393 ) else {
1394 return Ok(Some(Vec::new()));
1395 };
1396 let actions = crate::code_actions::quick_fixes(
1397 text,
1398 diags,
1399 bynk_syntax::span::Span::new(start, end),
1400 &uri,
1401 analysis.versions.get(&rel).copied(),
1402 );
1403 Ok(Some(actions))
1404 }
1405
1406 async fn inlay_hint(&self, params: InlayHintParams) -> JsonRpcResult<Option<Vec<InlayHint>>> {
1411 let uri = params.text_document.uri;
1412 let analysis = { self.state.read().await.analysis.clone() };
1413 let Some(analysis) = analysis else {
1414 return Ok(Some(Vec::new()));
1415 };
1416 let Some(rel) = Self::uri_to_rel(&analysis, &uri) else {
1417 return Ok(Some(Vec::new()));
1418 };
1419 let Some(text) = analysis.snapshots.get(&rel) else {
1420 return Ok(Some(Vec::new()));
1421 };
1422 let (Some(start), Some(end)) = (
1425 crate::position::position_to_offset(text, params.range.start),
1426 crate::position::position_to_offset(text, params.range.end),
1427 ) else {
1428 return Ok(Some(Vec::new()));
1429 };
1430 let visible = bynk_syntax::span::Span::new(start, end);
1431 let mut hints = analysis
1435 .hints
1436 .get(&rel)
1437 .map(|h| crate::inlay_hints::inlay_hints(text, h, visible))
1438 .unwrap_or_default();
1439 if let Some(reqs) = analysis.requirements.get(&rel) {
1440 hints.extend(crate::inlay_hints::given_hints(text, reqs, visible));
1441 }
1442 Ok(Some(hints))
1443 }
1444
1445 async fn semantic_tokens_full(
1449 &self,
1450 params: SemanticTokensParams,
1451 ) -> JsonRpcResult<Option<SemanticTokensResult>> {
1452 let data = self
1453 .semantic_tokens_for(¶ms.text_document.uri, None)
1454 .await;
1455 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1456 result_id: None,
1457 data,
1458 })))
1459 }
1460
1461 async fn semantic_tokens_range(
1464 &self,
1465 params: SemanticTokensRangeParams,
1466 ) -> JsonRpcResult<Option<SemanticTokensRangeResult>> {
1467 let data = self
1468 .semantic_tokens_for(¶ms.text_document.uri, Some(params.range))
1469 .await;
1470 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1471 result_id: None,
1472 data,
1473 })))
1474 }
1475
1476 async fn symbol(
1479 &self,
1480 params: WorkspaceSymbolParams,
1481 ) -> JsonRpcResult<Option<Vec<SymbolInformation>>> {
1482 let Some(analysis) = self.ensure_analysis().await else {
1483 return Ok(None);
1484 };
1485 let matches = crate::index_queries::workspace_symbols(&analysis.index, ¶ms.query);
1486 let symbols: Vec<SymbolInformation> = matches
1487 .into_iter()
1488 .filter_map(|(key, def)| {
1489 let location = Self::site_to_location(&analysis, def)?;
1490 #[allow(deprecated)]
1491 Some(SymbolInformation {
1492 name: key.name.clone(),
1493 kind: lsp_symbol_kind(key.kind),
1494 tags: None,
1495 deprecated: None,
1496 location,
1497 container_name: Some(key.unit.clone()),
1498 })
1499 })
1500 .collect();
1501 Ok(Some(symbols))
1502 }
1503
1504 async fn document_highlight(
1508 &self,
1509 params: DocumentHighlightParams,
1510 ) -> JsonRpcResult<Option<Vec<DocumentHighlight>>> {
1511 let uri = params.text_document_position_params.text_document.uri;
1512 let pos = params.text_document_position_params.position;
1513 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
1514 return Ok(None);
1515 };
1516 let Some(text) = analysis.snapshots.get(&rel) else {
1517 return Ok(None);
1518 };
1519 if let Some(sites) =
1520 crate::index_queries::document_highlights(&analysis.index, &rel, offset)
1521 {
1522 let highlights: Vec<DocumentHighlight> = sites
1523 .into_iter()
1524 .map(|s| DocumentHighlight {
1525 range: crate::position::span_to_range(text, s.span),
1526 kind: None,
1527 })
1528 .collect();
1529 return Ok(Some(highlights));
1530 }
1531 if let Some(spans) = self.local_sites(&analysis, &rel, offset) {
1533 let highlights = spans
1534 .iter()
1535 .map(|s| DocumentHighlight {
1536 range: crate::position::span_to_range(text, *s),
1537 kind: None,
1538 })
1539 .collect();
1540 return Ok(Some(highlights));
1541 }
1542 Ok(None)
1543 }
1544
1545 async fn prepare_rename(
1546 &self,
1547 params: TextDocumentPositionParams,
1548 ) -> JsonRpcResult<Option<PrepareRenameResponse>> {
1549 let uri = params.text_document.uri;
1550 let pos = params.position;
1551 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, false).await else {
1555 return Ok(None);
1556 };
1557 let Some((key, site)) = crate::index_queries::prepare_rename(&analysis.index, &rel, offset)
1558 else {
1559 return Ok(None);
1560 };
1561 let Some(text) = analysis.snapshots.get(&rel) else {
1562 return Ok(None);
1563 };
1564 Ok(Some(PrepareRenameResponse::RangeWithPlaceholder {
1565 range: crate::position::span_to_range(text, site.span),
1566 placeholder: key.name.clone(),
1567 }))
1568 }
1569
1570 async fn rename(&self, params: RenameParams) -> JsonRpcResult<Option<WorkspaceEdit>> {
1571 let uri = params.text_document_position.text_document.uri;
1572 let pos = params.text_document_position.position;
1573 let new_name = params.new_name;
1574 let refused = |msg: String| tower_lsp::jsonrpc::Error {
1575 code: tower_lsp::jsonrpc::ErrorCode::InvalidParams,
1576 message: msg.into(),
1577 data: None,
1578 };
1579 let Some((analysis, rel, offset)) = self.index_position(&uri, pos, true).await else {
1582 return Err(refused("rename requires a project (bynk.toml)".into()));
1583 };
1584 let plan = crate::index_queries::plan_rename(&analysis.index, &rel, offset, &new_name)
1585 .map_err(refused)?;
1586
1587 let mut overlay = std::collections::HashMap::new();
1591 for (rel_path, text) in &analysis.snapshots {
1592 let edited = match plan.edits.get(rel_path) {
1593 Some(spans) => crate::index_queries::apply_edits(text, spans, &plan.new_name),
1594 None => text.clone(),
1595 };
1596 let abs = analysis.src_root.join(rel_path);
1597 let abs = abs.canonicalize().unwrap_or(abs);
1598 overlay.insert(abs, edited);
1599 }
1600 let analysis_root = analysis.src_root.clone();
1601 let Ok(post) = tokio::task::spawn_blocking(move || {
1602 bynk_ide::diagnose_project(&analysis_root, &overlay)
1603 })
1604 .await
1605 else {
1606 return Err(refused("rename validation failed to run".into()));
1607 };
1608
1609 let post_diags: Vec<(PathBuf, String)> = post
1611 .files
1612 .iter()
1613 .flat_map(|f| {
1614 f.diagnostics
1615 .iter()
1616 .map(|d| (f.source_path.clone(), d.error.category.to_string()))
1617 })
1618 .collect();
1619 crate::index_queries::no_new_diagnostics(&analysis.diag_categories(), &post_diags)
1620 .map_err(refused)?;
1621
1622 if !crate::index_queries::index_unchanged_modulo_rename(&analysis.index, &post.index, &plan)
1625 {
1626 return Err(refused(format!(
1627 "renaming `{}` to `{new_name}` would silently re-bind another name — refused",
1628 plan.key.name
1629 )));
1630 }
1631
1632 let mut document_edits: Vec<TextDocumentEdit> = Vec::new();
1635 for (rel_path, spans) in &plan.edits {
1636 let Some(text) = analysis.snapshots.get(rel_path) else {
1637 continue;
1638 };
1639 let abs = analysis.src_root.join(rel_path);
1640 let Ok(file_uri) = Url::from_file_path(&abs) else {
1641 continue;
1642 };
1643 let edits: Vec<OneOf<TextEdit, AnnotatedTextEdit>> = spans
1644 .iter()
1645 .map(|span| {
1646 OneOf::Left(TextEdit {
1647 range: crate::position::span_to_range(text, *span),
1648 new_text: plan.new_name.clone(),
1649 })
1650 })
1651 .collect();
1652 document_edits.push(TextDocumentEdit {
1653 text_document: OptionalVersionedTextDocumentIdentifier {
1654 uri: file_uri,
1655 version: analysis.versions.get(rel_path).copied(),
1656 },
1657 edits,
1658 });
1659 }
1660 Ok(Some(WorkspaceEdit {
1661 changes: None,
1662 document_changes: Some(DocumentChanges::Edits(document_edits)),
1663 change_annotations: None,
1664 }))
1665 }
1666
1667 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
1668 let mut uris_to_refresh = Vec::new();
1670 {
1671 let state = self.state.read().await;
1672 for ev in ¶ms.changes {
1673 if state.docs.contains_key(&ev.uri) {
1674 uris_to_refresh.push(ev.uri.clone());
1675 }
1676 }
1677 }
1678 for uri in uris_to_refresh {
1679 self.recompile_and_publish(&uri).await;
1680 }
1681 }
1682}
1683
1684fn server_capabilities() -> ServerCapabilities {
1687 ServerCapabilities {
1688 text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
1689 hover_provider: Some(HoverProviderCapability::Simple(true)),
1690 definition_provider: Some(OneOf::Left(true)),
1691 completion_provider: Some(CompletionOptions {
1696 trigger_characters: Some(vec![
1697 " ".to_string(),
1698 "{".to_string(),
1699 ",".to_string(),
1700 ".".to_string(),
1701 ]),
1702 resolve_provider: Some(true),
1705 ..Default::default()
1706 }),
1707 signature_help_provider: Some(SignatureHelpOptions {
1709 trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
1710 retrigger_characters: Some(vec![",".to_string()]),
1711 ..Default::default()
1712 }),
1713 code_lens_provider: Some(CodeLensOptions {
1715 resolve_provider: Some(false),
1716 }),
1717 call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
1719 implementation_provider: Some(ImplementationProviderCapability::Simple(true)),
1721 type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
1723 document_link_provider: Some(DocumentLinkOptions {
1725 resolve_provider: Some(false),
1726 work_done_progress_options: Default::default(),
1727 }),
1728 document_formatting_provider: Some(OneOf::Left(true)),
1729 document_range_formatting_provider: Some(OneOf::Left(true)),
1730 document_symbol_provider: Some(OneOf::Left(true)),
1731 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
1733 selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)),
1734 references_provider: Some(OneOf::Left(true)),
1737 rename_provider: Some(OneOf::Right(RenameOptions {
1738 prepare_provider: Some(true),
1739 work_done_progress_options: Default::default(),
1740 })),
1741 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
1744 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
1745 ..Default::default()
1746 })),
1747 inlay_hint_provider: Some(OneOf::Left(true)),
1750 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensOptions(
1754 SemanticTokensOptions {
1755 legend: crate::index_queries::semantic_tokens_legend(),
1756 full: Some(SemanticTokensFullOptions::Bool(true)),
1757 range: Some(true),
1758 ..Default::default()
1759 },
1760 )),
1761 workspace_symbol_provider: Some(OneOf::Left(true)),
1763 document_highlight_provider: Some(OneOf::Left(true)),
1764 workspace: Some(WorkspaceServerCapabilities {
1765 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
1766 supported: Some(true),
1767 change_notifications: Some(OneOf::Left(true)),
1768 }),
1769 file_operations: None,
1770 }),
1771 ..Default::default()
1772 }
1773}
1774
1775fn stamp_resolve_data(items: &mut [CompletionItem], uri: &Url) {
1783 let data = serde_json::json!({ "uri": uri.to_string() });
1784 for item in items.iter_mut() {
1785 item.data = Some(data.clone());
1786 }
1787}
1788
1789fn to_completion_item(c: completion::Completion) -> CompletionItem {
1790 CompletionItem {
1791 kind: Some(match c.kind {
1792 completion::CompletionKind::Unit => CompletionItemKind::MODULE,
1793 completion::CompletionKind::Capability => CompletionItemKind::INTERFACE,
1794 completion::CompletionKind::Type => CompletionItemKind::STRUCT,
1795 completion::CompletionKind::Keyword => CompletionItemKind::KEYWORD,
1796 completion::CompletionKind::Snippet => CompletionItemKind::SNIPPET,
1797 completion::CompletionKind::Variant => CompletionItemKind::ENUM_MEMBER,
1798 completion::CompletionKind::Member => CompletionItemKind::METHOD,
1799 completion::CompletionKind::Field => CompletionItemKind::FIELD,
1800 completion::CompletionKind::Constructor => CompletionItemKind::CONSTRUCTOR,
1801 completion::CompletionKind::Function => CompletionItemKind::FUNCTION,
1802 }),
1803 insert_text_format: c.insert_text.as_ref().map(|_| InsertTextFormat::SNIPPET),
1806 insert_text: c.insert_text,
1807 label: c.label,
1808 detail: c.detail,
1809 ..Default::default()
1810 }
1811}
1812
1813fn cursor_byte_offset(text: &str, pos: Position) -> usize {
1816 let mut offset = 0;
1817 for (i, line) in text.split_inclusive('\n').enumerate() {
1818 if i == pos.line as usize {
1819 let bare = line.strip_suffix('\n').unwrap_or(line);
1820 return offset + (pos.character as usize).min(bare.len());
1821 }
1822 offset += line.len();
1823 }
1824 offset.min(text.len())
1825}
1826
1827#[derive(serde::Serialize, serde::Deserialize)]
1831struct SerKey {
1832 unit: String,
1833 kind: String,
1834 name: String,
1835}
1836
1837impl From<&bynk_check::index::SymbolKey> for SerKey {
1838 fn from(k: &bynk_check::index::SymbolKey) -> Self {
1839 SerKey {
1840 unit: k.unit.clone(),
1841 kind: k.kind.display().to_string(),
1842 name: k.name.clone(),
1843 }
1844 }
1845}
1846
1847impl SerKey {
1848 fn read(data: &Option<serde_json::Value>) -> Option<bynk_check::index::SymbolKey> {
1852 let sk: SerKey = serde_json::from_value(data.as_ref()?.clone()).ok()?;
1853 let kind = match sk.kind.as_str() {
1854 "type" => bynk_check::index::SymbolKind::Type,
1855 "fn" => bynk_check::index::SymbolKind::Fn,
1856 "capability" => bynk_check::index::SymbolKind::Capability,
1857 "service" => bynk_check::index::SymbolKind::Service,
1858 "agent" => bynk_check::index::SymbolKind::Agent,
1859 "provider" => bynk_check::index::SymbolKind::Provider,
1860 _ => return None,
1861 };
1862 Some(bynk_check::index::SymbolKey {
1863 unit: sk.unit,
1864 kind,
1865 name: sk.name,
1866 })
1867 }
1868}
1869
1870fn lsp_symbol_kind(kind: bynk_check::index::SymbolKind) -> SymbolKind {
1871 match kind {
1872 bynk_check::index::SymbolKind::Type => SymbolKind::STRUCT,
1873 bynk_check::index::SymbolKind::Fn => SymbolKind::FUNCTION,
1874 bynk_check::index::SymbolKind::Capability => SymbolKind::INTERFACE,
1875 bynk_check::index::SymbolKind::Service | bynk_check::index::SymbolKind::Agent => {
1876 SymbolKind::CLASS
1877 }
1878 bynk_check::index::SymbolKind::Provider => SymbolKind::OBJECT,
1879 bynk_check::index::SymbolKind::Method => SymbolKind::METHOD,
1880 bynk_check::index::SymbolKind::CapabilityOp => SymbolKind::METHOD,
1881 bynk_check::index::SymbolKind::Field => SymbolKind::FIELD,
1882 bynk_check::index::SymbolKind::Actor => SymbolKind::INTERFACE,
1883 }
1884}
1885
1886fn make_diagnostic(d: &bynk_ide::Diagnostic, text: &str, uri: &Url) -> Diagnostic {
1887 let range = crate::position::span_to_range(text, d.error.span);
1888 let severity = match d.severity {
1889 bynk_syntax::Severity::Error => DiagnosticSeverity::ERROR,
1890 bynk_syntax::Severity::Warning => DiagnosticSeverity::WARNING,
1891 };
1892 let related_information: Vec<DiagnosticRelatedInformation> = d
1893 .error
1894 .labels
1895 .iter()
1896 .map(|(span, msg)| DiagnosticRelatedInformation {
1897 location: Location {
1898 uri: uri.clone(),
1902 range: crate::position::span_to_range(text, *span),
1903 },
1904 message: msg.clone(),
1905 })
1906 .collect();
1907 let mut message = d.error.message.clone();
1908 for note in &d.error.notes {
1909 message.push_str("\n\n");
1910 message.push_str("note: ");
1911 message.push_str(note);
1912 }
1913 Diagnostic {
1914 range,
1915 severity: Some(severity),
1916 code: Some(NumberOrString::String(d.error.category.to_string())),
1917 code_description: None,
1918 source: Some(SERVER_NAME.to_string()),
1919 message,
1920 related_information: if related_information.is_empty() {
1921 None
1922 } else {
1923 Some(related_information)
1924 },
1925 tags: None,
1926 data: None,
1927 }
1928}
1929
1930#[tokio::main]
1931async fn main() {
1932 if std::env::args()
1936 .skip(1)
1937 .any(|a| a == "--version" || a == "-V")
1938 {
1939 println!("{SERVER_NAME} {SERVER_VERSION}");
1940 return;
1941 }
1942 if let Some(home) = std::env::var_os("HOME") {
1945 let path: PathBuf = PathBuf::from(home).join(".bynk-lsp.log");
1946 if let Ok(file) = std::fs::OpenOptions::new()
1947 .create(true)
1948 .append(true)
1949 .open(&path)
1950 {
1951 use tracing_subscriber::prelude::*;
1952 let env_filter = tracing_subscriber::EnvFilter::try_from_env("BYNK_LSP_LOG")
1953 .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
1954 let file_layer = tracing_subscriber::fmt::layer()
1955 .with_writer(std::sync::Mutex::new(file))
1956 .with_ansi(false);
1957 tracing_subscriber::registry()
1958 .with(env_filter)
1959 .with(file_layer)
1960 .try_init()
1961 .ok();
1962 }
1963 }
1964 tracing::info!("bynkc-lsp v{} starting", SERVER_VERSION);
1965 let stdin = tokio::io::stdin();
1966 let stdout = tokio::io::stdout();
1967 let (service, socket) = LspService::new(Backend::new);
1968 Server::new(stdin, stdout, socket).serve(service).await;
1969}
1970
1971#[cfg(test)]
1972mod tests {
1973 use super::*;
1974
1975 #[test]
1978 fn advertises_code_actions_and_the_index_riders() {
1979 let caps = server_capabilities();
1980 let Some(CodeActionProviderCapability::Options(opts)) = caps.code_action_provider else {
1981 panic!("codeActionProvider not advertised with options");
1982 };
1983 assert_eq!(opts.code_action_kinds, Some(vec![CodeActionKind::QUICKFIX]));
1984 assert!(matches!(
1985 caps.workspace_symbol_provider,
1986 Some(OneOf::Left(true))
1987 ));
1988 assert!(matches!(
1989 caps.document_highlight_provider,
1990 Some(OneOf::Left(true))
1991 ));
1992 }
1993
1994 #[test]
1997 fn advertises_inlay_hints() {
1998 let caps = server_capabilities();
1999 assert!(matches!(caps.inlay_hint_provider, Some(OneOf::Left(true))));
2000 }
2001
2002 #[test]
2004 fn advertises_type_definition() {
2005 let caps = server_capabilities();
2006 assert!(matches!(
2007 caps.type_definition_provider,
2008 Some(TypeDefinitionProviderCapability::Simple(true))
2009 ));
2010 }
2011
2012 #[test]
2014 fn advertises_document_links() {
2015 let caps = server_capabilities();
2016 assert!(caps.document_link_provider.is_some());
2017 }
2018
2019 #[test]
2021 fn advertises_completion_with_dot_trigger_and_resolve() {
2022 let caps = server_capabilities();
2023 let opts = caps.completion_provider.expect("completion advertised");
2024 assert_eq!(opts.resolve_provider, Some(true), "resolve_provider");
2025 assert!(
2026 opts.trigger_characters
2027 .as_deref()
2028 .is_some_and(|t| t.iter().any(|c| c == ".")),
2029 "`.` trigger char"
2030 );
2031 }
2032
2033 #[test]
2036 fn advertises_semantic_tokens() {
2037 let caps = server_capabilities();
2038 let Some(SemanticTokensServerCapabilities::SemanticTokensOptions(opts)) =
2039 caps.semantic_tokens_provider
2040 else {
2041 panic!("semanticTokensProvider not advertised with options");
2042 };
2043 assert_eq!(opts.full, Some(SemanticTokensFullOptions::Bool(true)));
2044 assert_eq!(opts.range, Some(true));
2045 assert_eq!(opts.legend, crate::index_queries::semantic_tokens_legend());
2046 }
2047}