1use crate::{Envelope, PeerId};
2use anyhow::{Context as _, Result};
3use serde::Serialize;
4use std::{
5 any::{Any, TypeId},
6 cmp,
7 fmt::{self, Debug},
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11use std::{marker::PhantomData, time::Instant};
12
13pub trait EnvelopedMessage: Clone + Debug + Serialize + Sized + Send + Sync + 'static {
14 const NAME: &'static str;
15 const PRIORITY: MessagePriority;
16 fn into_envelope(
17 self,
18 id: u32,
19 responding_to: Option<u32>,
20 original_sender_id: Option<PeerId>,
21 ) -> Envelope;
22 fn from_envelope(envelope: Envelope) -> Option<Self>;
23}
24
25pub trait EntityMessage: EnvelopedMessage {
26 type Entity;
27 fn remote_entity_id(&self) -> u64;
28}
29
30pub trait RequestMessage: EnvelopedMessage {
31 type Response: EnvelopedMessage;
32}
33
34/// A trait to bind LSP request and responses for the proto layer.
35/// Should be used for every LSP request that has to traverse through the proto layer.
36///
37/// `lsp_messages` macro in the same crate provides a convenient way to implement this.
38pub trait LspRequestMessage: EnvelopedMessage {
39 type Response: EnvelopedMessage;
40
41 fn to_proto_query(self) -> crate::lsp_query::Request;
42
43 fn response_to_proto_query(response: Self::Response) -> crate::lsp_response::Response;
44
45 fn buffer_id(&self) -> u64;
46
47 fn buffer_version(&self) -> &[crate::VectorClockEntry];
48
49 /// Whether to deduplicate the requests, or keep the previous ones running when another
50 /// request of the same kind is processed.
51 fn stop_previous_requests() -> bool;
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub struct LspRequestId(pub u64);
56
57/// A response from a single language server.
58/// There could be multiple responses for a single LSP request,
59/// from different servers.
60pub struct ProtoLspResponse<R> {
61 pub server_id: u64,
62 pub response: R,
63}
64
65impl ProtoLspResponse<Box<dyn AnyTypedEnvelope>> {
66 pub fn into_response<T: LspRequestMessage>(self) -> Result<ProtoLspResponse<T::Response>> {
67 let envelope = self
68 .response
69 .into_any()
70 .downcast::<TypedEnvelope<T::Response>>()
71 .map_err(|_| {
72 anyhow::anyhow!(
73 "cannot downcast LspResponse to {} for message {}",
74 T::Response::NAME,
75 T::NAME,
76 )
77 })?;
78
79 Ok(ProtoLspResponse {
80 server_id: self.server_id,
81 response: envelope.payload,
82 })
83 }
84}
85
86pub trait AnyTypedEnvelope: Any + Send + Sync {
87 fn payload_type_id(&self) -> TypeId;
88 fn payload_type_name(&self) -> &'static str;
89 fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
90 fn is_background(&self) -> bool;
91 fn original_sender_id(&self) -> Option<PeerId>;
92 fn sender_id(&self) -> PeerId;
93 fn message_id(&self) -> u32;
94}
95
96pub enum MessagePriority {
97 Foreground,
98 Background,
99}
100
101impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
102 fn payload_type_id(&self) -> TypeId {
103 TypeId::of::<T>()
104 }
105
106 fn payload_type_name(&self) -> &'static str {
107 T::NAME
108 }
109
110 fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
111 self
112 }
113
114 fn is_background(&self) -> bool {
115 matches!(T::PRIORITY, MessagePriority::Background)
116 }
117
118 fn original_sender_id(&self) -> Option<PeerId> {
119 self.original_sender_id
120 }
121
122 fn sender_id(&self) -> PeerId {
123 self.sender_id
124 }
125
126 fn message_id(&self) -> u32 {
127 self.message_id
128 }
129}
130
131impl PeerId {
132 pub fn from_u64(peer_id: u64) -> Self {
133 let owner_id = (peer_id >> 32) as u32;
134 let id = peer_id as u32;
135 Self { owner_id, id }
136 }
137
138 pub fn as_u64(self) -> u64 {
139 ((self.owner_id as u64) << 32) | (self.id as u64)
140 }
141}
142
143impl Copy for PeerId {}
144
145impl Eq for PeerId {}
146
147impl Ord for PeerId {
148 fn cmp(&self, other: &Self) -> cmp::Ordering {
149 self.owner_id
150 .cmp(&other.owner_id)
151 .then_with(|| self.id.cmp(&other.id))
152 }
153}
154
155impl PartialOrd for PeerId {
156 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
157 Some(self.cmp(other))
158 }
159}
160
161impl std::hash::Hash for PeerId {
162 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
163 self.owner_id.hash(state);
164 self.id.hash(state);
165 }
166}
167
168impl fmt::Display for PeerId {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 write!(f, "{}/{}", self.owner_id, self.id)
171 }
172}
173
174pub trait FromProto {
175 fn from_proto(proto: String) -> Self;
176}
177
178pub trait ToProto {
179 fn to_proto(self) -> String;
180}
181
182#[inline]
183fn from_proto_path(proto: String) -> PathBuf {
184 #[cfg(target_os = "windows")]
185 let proto = proto.replace('/', "\\");
186
187 PathBuf::from(proto)
188}
189
190#[inline]
191fn to_proto_path(path: &Path) -> String {
192 #[cfg(target_os = "windows")]
193 let proto = path.to_string_lossy().replace('\\', "/");
194
195 #[cfg(not(target_os = "windows"))]
196 let proto = path.to_string_lossy().to_string();
197
198 proto
199}
200
201impl FromProto for PathBuf {
202 fn from_proto(proto: String) -> Self {
203 from_proto_path(proto)
204 }
205}
206
207impl FromProto for Arc<Path> {
208 fn from_proto(proto: String) -> Self {
209 from_proto_path(proto).into()
210 }
211}
212
213impl ToProto for PathBuf {
214 fn to_proto(self) -> String {
215 to_proto_path(&self)
216 }
217}
218
219impl ToProto for &Path {
220 fn to_proto(self) -> String {
221 to_proto_path(self)
222 }
223}
224
225pub struct Receipt<T> {
226 pub sender_id: PeerId,
227 pub message_id: u32,
228 payload_type: PhantomData<T>,
229}
230
231impl<T> Clone for Receipt<T> {
232 fn clone(&self) -> Self {
233 *self
234 }
235}
236
237impl<T> Copy for Receipt<T> {}
238
239#[derive(Clone, Debug)]
240pub struct TypedEnvelope<T> {
241 pub sender_id: PeerId,
242 pub original_sender_id: Option<PeerId>,
243 pub message_id: u32,
244 pub payload: T,
245 pub received_at: Instant,
246}
247
248impl<T> TypedEnvelope<T> {
249 pub fn original_sender_id(&self) -> Result<PeerId> {
250 self.original_sender_id
251 .context("missing original_sender_id")
252 }
253}
254
255impl<T: RequestMessage> TypedEnvelope<T> {
256 pub fn receipt(&self) -> Receipt<T> {
257 Receipt {
258 sender_id: self.sender_id,
259 message_id: self.message_id,
260 payload_type: PhantomData,
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use typed_path::{UnixPath, UnixPathBuf, WindowsPath, WindowsPathBuf};
268
269 fn windows_path_from_proto(proto: String) -> WindowsPathBuf {
270 let proto = proto.replace('/', "\\");
271 WindowsPathBuf::from(proto)
272 }
273
274 fn unix_path_from_proto(proto: String) -> UnixPathBuf {
275 UnixPathBuf::from(proto)
276 }
277
278 fn windows_path_to_proto(path: &WindowsPath) -> String {
279 path.to_string_lossy().replace('\\', "/")
280 }
281
282 fn unix_path_to_proto(path: &UnixPath) -> String {
283 path.to_string_lossy().to_string()
284 }
285
286 #[test]
287 fn test_path_proto_interop() {
288 const WINDOWS_PATHS: &[&str] = &[
289 "C:\\Users\\User\\Documents\\file.txt",
290 "C:/Program Files/App/app.exe",
291 "projects\\zed\\crates\\proto\\src\\typed_envelope.rs",
292 "projects/my project/src/main.rs",
293 ];
294 const UNIX_PATHS: &[&str] = &[
295 "/home/user/documents/file.txt",
296 "/usr/local/bin/my app/app",
297 "projects/zed/crates/proto/src/typed_envelope.rs",
298 "projects/my project/src/main.rs",
299 ];
300
301 // Windows path to proto and back
302 for &windows_path_str in WINDOWS_PATHS {
303 let windows_path = WindowsPathBuf::from(windows_path_str);
304 let proto = windows_path_to_proto(&windows_path);
305 let recovered_path = windows_path_from_proto(proto);
306 assert_eq!(windows_path, recovered_path);
307 assert_eq!(
308 recovered_path.to_string_lossy(),
309 windows_path_str.replace('/', "\\")
310 );
311 }
312 // Unix path to proto and back
313 for &unix_path_str in UNIX_PATHS {
314 let unix_path = UnixPathBuf::from(unix_path_str);
315 let proto = unix_path_to_proto(&unix_path);
316 let recovered_path = unix_path_from_proto(proto);
317 assert_eq!(unix_path, recovered_path);
318 assert_eq!(recovered_path.to_string_lossy(), unix_path_str);
319 }
320 // Windows host, Unix client, host sends Windows path to client
321 for &windows_path_str in WINDOWS_PATHS {
322 let windows_host_path = WindowsPathBuf::from(windows_path_str);
323 let proto = windows_path_to_proto(&windows_host_path);
324 let unix_client_received_path = unix_path_from_proto(proto);
325 let proto = unix_path_to_proto(&unix_client_received_path);
326 let windows_host_recovered_path = windows_path_from_proto(proto);
327 assert_eq!(windows_host_path, windows_host_recovered_path);
328 assert_eq!(
329 windows_host_recovered_path.to_string_lossy(),
330 windows_path_str.replace('/', "\\")
331 );
332 }
333 // Unix host, Windows client, host sends Unix path to client
334 for &unix_path_str in UNIX_PATHS {
335 let unix_host_path = UnixPathBuf::from(unix_path_str);
336 let proto = unix_path_to_proto(&unix_host_path);
337 let windows_client_received_path = windows_path_from_proto(proto);
338 let proto = windows_path_to_proto(&windows_client_received_path);
339 let unix_host_recovered_path = unix_path_from_proto(proto);
340 assert_eq!(unix_host_path, unix_host_recovered_path);
341 assert_eq!(unix_host_recovered_path.to_string_lossy(), unix_path_str);
342 }
343 }
344
345 // todo(zjk)
346 #[test]
347 fn test_unsolved_case() {
348 // Unix host, Windows client
349 // The Windows client receives a Unix path with backslashes in it, then
350 // sends it back to the host.
351 // This currently fails.
352 let unix_path = UnixPathBuf::from("/home/user/projects/my\\project/src/main.rs");
353 let proto = unix_path_to_proto(&unix_path);
354 let windows_client_received_path = windows_path_from_proto(proto);
355 let proto = windows_path_to_proto(&windows_client_received_path);
356 let unix_host_recovered_path = unix_path_from_proto(proto);
357 assert_ne!(unix_path, unix_host_recovered_path);
358 assert_eq!(
359 unix_host_recovered_path.to_string_lossy(),
360 "/home/user/projects/my/project/src/main.rs"
361 );
362 }
363}