solidity_language_server/
links.rs1use crate::goto::{CachedBuild, bytes_to_pos};
2use crate::types::SourceLoc;
3use crate::utils;
4use tower_lsp::lsp_types::{DocumentLink, Range, Url};
5use tree_sitter::Parser;
6
7pub 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 for (_id, node_info) in file_nodes.iter() {
39 if node_info.node_type.as_deref() == Some("ImportDirective")
40 && let Some(link) = import_link(node_info, source_bytes)
41 {
42 links.push(link);
43 }
44 }
45
46 links.sort_by(|a, b| {
47 a.range
48 .start
49 .line
50 .cmp(&b.range.start.line)
51 .then(a.range.start.character.cmp(&b.range.start.character))
52 });
53
54 links
55}
56
57pub fn import_path_range(node_info: &crate::goto::NodeInfo, source_bytes: &[u8]) -> Option<Range> {
63 let src_loc = SourceLoc::parse(&node_info.src)?;
64 let (start_byte, length) = (src_loc.offset, src_loc.length);
65 let end_byte = start_byte + length;
66
67 if end_byte > source_bytes.len() || end_byte < 3 {
68 return None;
69 }
70
71 let close_quote = end_byte - 2;
73 let open_quote = (start_byte..close_quote)
74 .rev()
75 .find(|&i| source_bytes[i] == b'"' || source_bytes[i] == b'\'')?;
76
77 let start_pos = bytes_to_pos(source_bytes, open_quote + 1)?;
78 let end_pos = bytes_to_pos(source_bytes, close_quote)?;
79
80 Some(Range {
81 start: start_pos,
82 end: end_pos,
83 })
84}
85
86fn import_link(node_info: &crate::goto::NodeInfo, source_bytes: &[u8]) -> Option<DocumentLink> {
89 let absolute_path = node_info.absolute_path.as_deref()?;
90 let range = import_path_range(node_info, source_bytes)?;
91
92 let target_path = std::path::Path::new(absolute_path);
93 let full_path = if target_path.is_absolute() {
94 target_path.to_path_buf()
95 } else {
96 std::env::current_dir().ok()?.join(target_path)
97 };
98 let target_uri = Url::from_file_path(&full_path).ok()?;
99
100 Some(DocumentLink {
101 range,
102 target: Some(target_uri),
103 tooltip: Some(absolute_path.to_string()),
104 data: None,
105 })
106}
107
108pub struct TsImport {
111 pub path: String,
113 pub inner_range: Range,
115}
116
117pub fn ts_find_imports(source_bytes: &[u8]) -> Vec<TsImport> {
122 let source = match std::str::from_utf8(source_bytes) {
123 Ok(s) => s,
124 Err(_) => return vec![],
125 };
126 let mut parser = Parser::new();
127 if parser
128 .set_language(&tree_sitter_solidity::LANGUAGE.into())
129 .is_err()
130 {
131 return vec![];
132 }
133 let tree = match parser.parse(source, None) {
134 Some(t) => t,
135 None => return vec![],
136 };
137
138 let mut imports = Vec::new();
139 collect_imports(tree.root_node(), source_bytes, &mut imports);
140 imports
141}
142
143fn collect_imports(node: tree_sitter::Node, source_bytes: &[u8], out: &mut Vec<TsImport>) {
145 if node.kind() == "import_directive" {
146 for i in 0..node.named_child_count() {
149 if let Some(child) = node.named_child(i as u32) {
150 if child.kind() == "string" {
151 let start = child.start_byte();
152 let end = child.end_byte();
153 if end > start + 2 && end <= source_bytes.len() {
154 let inner_start = start + 1;
156 let inner_end = end - 1;
157 let path = String::from_utf8_lossy(&source_bytes[inner_start..inner_end])
158 .to_string();
159
160 let source_str = std::str::from_utf8(source_bytes).unwrap_or("");
165 let start_pos = utils::byte_offset_to_position(source_str, inner_start);
166 let end_pos = utils::byte_offset_to_position(source_str, inner_end);
167
168 out.push(TsImport {
169 path,
170 inner_range: Range {
171 start: start_pos,
172 end: end_pos,
173 },
174 });
175 }
176 break; }
178 }
179 }
180 return; }
182
183 for i in 0..node.child_count() {
184 if let Some(child) = node.child(i as u32) {
185 collect_imports(child, source_bytes, out);
186 }
187 }
188}