bynk_lsp/symbols.rs
1//! Re-exports `bynk-ide`'s symbol logic (#808) — moved there so it's
2//! reachable from `bynk-wasm` too, which can't depend on this crate's
3//! `tower-lsp`/`tokio` stack. See `bynk_ide::symbols` for the real module.
4//!
5//! The one exception is the cross-file lookup trio below: `bynk-ide`'s
6//! versions are `PathBuf`-keyed (wasm has no `Url`), so this crate re-wraps
7//! them in the `Url` shape the LSP handlers actually use. Rust's shadowing
8//! rule lets these locally-defined names win over the glob import.
9pub use bynk_ide::symbols::*;
10
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13
14use bynk_syntax::span::Span;
15use tower_lsp::lsp_types::Url;
16
17/// A cross-file declaration lookup result: the URI of the file containing
18/// the declaration, the declaration's source span, and the full source
19/// text of that file (returned because callers need it to convert the
20/// span to an LSP range and to build hover content).
21pub struct CrossFileSymbol {
22 pub uri: Url,
23 pub span: Span,
24 pub source: String,
25}
26
27/// Find `name`'s declaration in any project file other than `current_uri`.
28/// `files` is a pre-read `(path, content)` map (content-ownership track,
29/// #1086, slice 1) — see `bynk_ide::symbols::find_declaration_cross_file`
30/// for the pure logic.
31pub fn find_declaration_cross_file(
32 files: &HashMap<PathBuf, String>,
33 current_uri: &Url,
34 name: &str,
35) -> Option<CrossFileSymbol> {
36 let current_path = current_uri.to_file_path().ok()?;
37 let found = bynk_ide::symbols::find_declaration_cross_file(files, ¤t_path, name)?;
38 Some(CrossFileSymbol {
39 uri: Url::from_file_path(&found.path).ok()?,
40 span: found.span,
41 source: found.source,
42 })
43}
44
45/// Markdown hover content for `name` from any project file other than
46/// `current_uri`, plus the URI of the file that contributed it. See
47/// `bynk_ide::symbols::describe_symbol_cross_file` for the pure logic.
48pub fn describe_symbol_cross_file(
49 files: &HashMap<PathBuf, String>,
50 current_uri: &Url,
51 name: &str,
52) -> Option<(Url, String)> {
53 let current_path = current_uri.to_file_path().ok()?;
54 let (path, desc) = bynk_ide::symbols::describe_symbol_cross_file(files, ¤t_path, name)?;
55 Some((Url::from_file_path(&path).ok()?, desc))
56}
57
58/// #848: rewrite every resolvable intra-doc-link candidate in `content`
59/// (hover Markdown — a ```bynk fenced signature, optionally followed by the
60/// declaration's doc-comment prose) into a Markdown link, resolved against
61/// `owner_unit`'s doc-link scope order (`crate::index_queries::resolve_doc_link`).
62/// The fenced signature block (and any author example fence) is never
63/// touched — `scan_doc_link_candidates` skips fenced regions itself. An
64/// unresolved candidate is left exactly as authored — no diagnostic,
65/// matching the render-only decision.
66pub fn linkify_doc_links(
67 content: &str,
68 index: &bynk_check::index::ProjectIndex,
69 doc_scope: &HashMap<String, Vec<String>>,
70 project_root: &Path,
71 owner_unit: &str,
72) -> String {
73 let candidates = bynk_ide::symbols::scan_doc_link_candidates(content);
74 if candidates.is_empty() {
75 return content.to_string();
76 }
77 let mut out = String::with_capacity(content.len());
78 let mut last = 0;
79 for cand in candidates {
80 let Some(def) =
81 crate::index_queries::resolve_doc_link(index, doc_scope, owner_unit, &cand.name)
82 else {
83 continue;
84 };
85 let Ok(url) = Url::from_file_path(project_root.join(&def.path)) else {
86 continue;
87 };
88 out.push_str(&content[last..cand.span.start]);
89 out.push_str(&format!("[{}]({url})", cand.display));
90 last = cand.span.end;
91 }
92 out.push_str(&content[last..]);
93 out
94}