1use std::{collections::HashMap, fmt::Display, path::PathBuf, pin::Pin};
2
3use bevy_ecs::system::Resource;
4use futures::FutureExt;
5use lsp_core::{
6 client::{Client, ClientSync, Resp},
7 prelude::FsTrait,
8};
9use lsp_types::{Diagnostic, MessageType, Url};
10use tokio::fs::{self, read_to_string, write};
11use tracing::info;
12
13pub mod reqwest {
14 pub use reqwest::{
15 header::{HeaderMap, HeaderName, HeaderValue},
16 Error, StatusCode, Url,
17 };
18}
19
20#[derive(Debug)]
22pub struct BinFs(PathBuf);
23impl BinFs {
24 pub fn new() -> Self {
25 let mut tmp_dir = std::env::temp_dir();
26 tmp_dir.push("swls");
27 let mut c = 0;
28 tmp_dir.push(c.to_string());
29 while std::fs::exists(&tmp_dir).unwrap_or(false) {
30 tmp_dir.pop();
31 c += 1;
32 tmp_dir.push(c.to_string());
33 }
34 Self(tmp_dir)
35 }
36}
37
38#[tower_lsp::async_trait]
39impl FsTrait for BinFs {
40 fn virtual_url(&self, url: &str) -> Option<lsp_types::Url> {
41 let mut pb = self.0.clone();
42 if let Ok(url) = lsp_types::Url::parse(url) {
43 pb.push(url.path());
44 } else {
45 pb.push(url);
46 }
47 lsp_types::Url::from_file_path(pb).ok()
48 }
49
50 async fn read_file(&self, url: &lsp_types::Url) -> Option<String> {
51 let fp = url.to_file_path().ok()?;
52 let content = read_to_string(fp).await.ok()?;
53 Some(content)
54 }
55
56 async fn write_file(&self, url: &lsp_types::Url, content: &str) -> Option<()> {
57 let fp = url.to_file_path().ok()?;
58 if let Some(parent) = fp.parent() {
59 fs::create_dir_all(parent).await.ok()?;
60 }
61 write(fp, content.as_bytes()).await.ok()
62 }
63}
64
65#[derive(Resource, Clone)]
66pub struct TowerClient {
67 client: tower_lsp::Client,
68 handle: tokio::runtime::Handle,
69}
70impl TowerClient {
71 pub fn new(client: tower_lsp::Client) -> Self {
72 Self {
73 client,
74 handle: tokio::runtime::Handle::current(),
75 }
76 }
77}
78
79impl ClientSync for TowerClient {
80 fn spawn<F: std::future::Future<Output = ()> + Send + 'static>(&self, fut: F) {
81 let handle = std::thread::current();
82 info!("Spawn threaad name {:?}", handle.id());
83 self.handle.spawn(fut);
84 }
86
87 fn fetch(
88 &self,
89 url: &str,
90 headers: &HashMap<String, String>,
91 ) -> Pin<Box<dyn Send + std::future::Future<Output = Result<Resp, String>>>> {
92 use tokio::{fs::File, io::AsyncReadExt};
93 use tracing::{debug, error, info};
94 info!("Should fetch, fetching!");
95
96 let m_url = reqwest::Url::parse(url);
97
98 let client = ::reqwest::Client::new();
99 let builder = client.get(url);
100 let builder = headers
101 .into_iter()
102 .fold(builder, |builder, (k, v)| builder.header(k, v));
103
104 return async {
105 let url = m_url.map_err(|_| String::from("invalid url!"))?;
106 info!("Found url {} {}", url.scheme(), url);
107 if url.scheme() == "file" {
108 let mut file = File::open(url.path())
109 .await
110 .map_err(|_| format!("File not found {}", url.path()))?;
111 let mut body = String::new();
112 file.read_to_string(&mut body)
113 .await
114 .map_err(|_| format!("Failed to read file"))?;
115 let status = 200;
116 let headers = Vec::new();
117 return Ok(Resp {
118 headers,
119 body,
120 status,
121 });
122 }
123
124 debug!("sending blocking");
125 let resp = match builder.send().await {
126 Ok(x) => x,
127 Err(e) => {
128 error!(error = ?e);
129 return Err(e.to_string());
130 }
131 };
132
133 let status = resp.status().as_u16();
134 let headers: Vec<_> = resp
135 .headers()
136 .iter()
137 .flat_map(|(h, v)| {
138 if let Ok(value) = v.to_str() {
139 Some((h.to_string(), value.to_string()))
140 } else {
141 None
142 }
143 })
144 .collect();
145 debug!("got resp");
146 let body = resp.text().await.unwrap();
147
148 Ok(Resp {
149 headers,
150 body,
151 status,
152 })
153 }
154 .boxed();
155 }
156}
157
158#[tower_lsp::async_trait]
159impl Client for TowerClient {
160 async fn log_message<M: Display + Sync + Send + 'static>(&self, ty: MessageType, msg: M) -> () {
161 self.client.log_message(ty, msg).await;
162 }
163
164 async fn publish_diagnostics(
165 &self,
166 uri: Url,
167 diags: Vec<Diagnostic>,
168 version: Option<i32>,
169 ) -> () {
170 self.client.publish_diagnostics(uri, diags, version).await;
171 }
172}