solidity_language_server/
links.rs1use crate::goto::{bytes_to_pos, CachedBuild};
2use crate::references::id_to_location;
3use tower_lsp::lsp_types::{DocumentLink, Range, Url};
4
5pub fn document_links(
13 build: &CachedBuild,
14 file_uri: &Url,
15 source_bytes: &[u8],
16) -> Vec<DocumentLink> {
17 let mut links = Vec::new();
18
19 let file_path = match file_uri.to_file_path() {
20 Ok(p) => p,
21 Err(_) => return links,
22 };
23 let file_path_str = match file_path.to_str() {
24 Some(s) => s,
25 None => return links,
26 };
27
28 let abs_path = match build.path_to_abs.get(file_path_str) {
29 Some(a) => a.as_str(),
30 None => return links,
31 };
32
33 let file_nodes = match build.nodes.get(abs_path) {
34 Some(n) => n,
35 None => return links,
36 };
37
38 let tmp = file_nodes.iter();
39 for (_id, node_info) in tmp {
40 if node_info.node_type.as_deref() == Some("ImportDirective") {
42 if let Some(link) = import_link(node_info, source_bytes) {
43 links.push(link);
44 }
45 continue;
46 }
47
48 let ref_id = match node_info.referenced_declaration {
50 Some(id) => id,
51 None => continue,
52 };
53
54 let loc_str = node_info.name_location.as_deref().unwrap_or(&node_info.src);
56 let (start_byte, length) = match parse_src(loc_str) {
57 Some((s, l, _)) => (s, l),
58 None => continue,
59 };
60
61 let start_pos = match bytes_to_pos(source_bytes, start_byte) {
62 Some(p) => p,
63 None => continue,
64 };
65 let end_pos = match bytes_to_pos(source_bytes, start_byte + length) {
66 Some(p) => p,
67 None => continue,
68 };
69
70 let target_location = match id_to_location(&build.nodes, &build.id_to_path_map, ref_id) {
72 Some(loc) => loc,
73 None => continue,
74 };
75
76 links.push(DocumentLink {
77 range: Range {
78 start: start_pos,
79 end: end_pos,
80 },
81 target: Some(target_location.uri),
82 tooltip: None,
83 data: None,
84 });
85 }
86
87 links.sort_by(|a, b| {
88 a.range
89 .start
90 .line
91 .cmp(&b.range.start.line)
92 .then(a.range.start.character.cmp(&b.range.start.character))
93 });
94
95 links
96}
97
98fn import_link(node_info: &crate::goto::NodeInfo, source_bytes: &[u8]) -> Option<DocumentLink> {
101 let absolute_path = node_info.absolute_path.as_deref()?;
102 let (start_byte, length, _) = parse_src(&node_info.src)?;
103 let end_byte = start_byte + length;
104
105 if end_byte > source_bytes.len() || end_byte < 3 {
106 return None;
107 }
108
109 let close_quote = end_byte - 2;
111 let open_quote = (start_byte..close_quote)
112 .rev()
113 .find(|&i| source_bytes[i] == b'"' || source_bytes[i] == b'\'')?;
114
115 let start_pos = bytes_to_pos(source_bytes, open_quote + 1)?;
116 let end_pos = bytes_to_pos(source_bytes, close_quote)?;
117
118 let target_path = std::path::Path::new(absolute_path);
119 let full_path = if target_path.is_absolute() {
120 target_path.to_path_buf()
121 } else {
122 std::env::current_dir().ok()?.join(target_path)
123 };
124 let target_uri = Url::from_file_path(&full_path).ok()?;
125
126 Some(DocumentLink {
127 range: Range {
128 start: start_pos,
129 end: end_pos,
130 },
131 target: Some(target_uri),
132 tooltip: Some(absolute_path.to_string()),
133 data: None,
134 })
135}
136
137fn parse_src(src: &str) -> Option<(usize, usize, &str)> {
139 let parts: Vec<&str> = src.split(':').collect();
140 if parts.len() != 3 {
141 return None;
142 }
143 let offset: usize = parts[0].parse().ok()?;
144 let length: usize = parts[1].parse().ok()?;
145 Some((offset, length, parts[2]))
146}