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