1use crate::{Location, Project, ProjectTransaction};
2use anyhow::{anyhow, Result};
3use async_trait::async_trait;
4use client::{proto, PeerId};
5use gpui::{AppContext, AsyncAppContext, ModelHandle};
6use language::{
7 point_from_lsp,
8 proto::{deserialize_anchor, serialize_anchor},
9 range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToLspPosition, ToPointUtf16,
10};
11use std::{ops::Range, path::Path};
12
13#[async_trait(?Send)]
14pub(crate) trait LspCommand: 'static + Sized {
15 type Response: 'static + Default + Send;
16 type LspRequest: 'static + Send + lsp::request::Request;
17 type ProtoRequest: 'static + Send + proto::RequestMessage;
18
19 fn to_lsp(
20 &self,
21 path: &Path,
22 cx: &AppContext,
23 ) -> <Self::LspRequest as lsp::request::Request>::Params;
24 async fn response_from_lsp(
25 self,
26 message: <Self::LspRequest as lsp::request::Request>::Result,
27 project: ModelHandle<Project>,
28 buffer: ModelHandle<Buffer>,
29 cx: AsyncAppContext,
30 ) -> Result<Self::Response>;
31
32 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> Self::ProtoRequest;
33 fn from_proto(
34 message: Self::ProtoRequest,
35 project: &mut Project,
36 buffer: &Buffer,
37 ) -> Result<Self>;
38 fn response_to_proto(
39 response: Self::Response,
40 project: &mut Project,
41 peer_id: PeerId,
42 buffer_version: &clock::Global,
43 cx: &AppContext,
44 ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
45 async fn response_from_proto(
46 self,
47 message: <Self::ProtoRequest as proto::RequestMessage>::Response,
48 project: ModelHandle<Project>,
49 buffer: ModelHandle<Buffer>,
50 cx: AsyncAppContext,
51 ) -> Result<Self::Response>;
52 fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
53}
54
55pub(crate) struct PrepareRename {
56 pub position: PointUtf16,
57}
58
59pub(crate) struct PerformRename {
60 pub position: PointUtf16,
61 pub new_name: String,
62 pub push_to_history: bool,
63}
64
65pub(crate) struct GetDefinition {
66 pub position: PointUtf16,
67}
68
69pub(crate) struct GetReferences {
70 pub position: PointUtf16,
71}
72
73#[async_trait(?Send)]
74impl LspCommand for PrepareRename {
75 type Response = Option<Range<Anchor>>;
76 type LspRequest = lsp::request::PrepareRenameRequest;
77 type ProtoRequest = proto::PrepareRename;
78
79 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
80 lsp::TextDocumentPositionParams {
81 text_document: lsp::TextDocumentIdentifier {
82 uri: lsp::Url::from_file_path(path).unwrap(),
83 },
84 position: self.position.to_lsp_position(),
85 }
86 }
87
88 async fn response_from_lsp(
89 self,
90 message: Option<lsp::PrepareRenameResponse>,
91 _: ModelHandle<Project>,
92 buffer: ModelHandle<Buffer>,
93 cx: AsyncAppContext,
94 ) -> Result<Option<Range<Anchor>>> {
95 buffer.read_with(&cx, |buffer, _| {
96 if let Some(
97 lsp::PrepareRenameResponse::Range(range)
98 | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
99 ) = message
100 {
101 let Range { start, end } = range_from_lsp(range);
102 if buffer.clip_point_utf16(start, Bias::Left) == start
103 && buffer.clip_point_utf16(end, Bias::Left) == end
104 {
105 return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
106 }
107 }
108 Ok(None)
109 })
110 }
111
112 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
113 proto::PrepareRename {
114 project_id,
115 buffer_id: buffer.remote_id(),
116 position: Some(language::proto::serialize_anchor(
117 &buffer.anchor_before(self.position),
118 )),
119 }
120 }
121
122 fn from_proto(message: proto::PrepareRename, _: &mut Project, buffer: &Buffer) -> Result<Self> {
123 let position = message
124 .position
125 .and_then(deserialize_anchor)
126 .ok_or_else(|| anyhow!("invalid position"))?;
127 if !buffer.can_resolve(&position) {
128 Err(anyhow!("cannot resolve position"))?;
129 }
130 Ok(Self {
131 position: position.to_point_utf16(buffer),
132 })
133 }
134
135 fn response_to_proto(
136 range: Option<Range<Anchor>>,
137 _: &mut Project,
138 _: PeerId,
139 buffer_version: &clock::Global,
140 _: &AppContext,
141 ) -> proto::PrepareRenameResponse {
142 proto::PrepareRenameResponse {
143 can_rename: range.is_some(),
144 start: range
145 .as_ref()
146 .map(|range| language::proto::serialize_anchor(&range.start)),
147 end: range
148 .as_ref()
149 .map(|range| language::proto::serialize_anchor(&range.end)),
150 version: buffer_version.into(),
151 }
152 }
153
154 async fn response_from_proto(
155 self,
156 message: proto::PrepareRenameResponse,
157 _: ModelHandle<Project>,
158 buffer: ModelHandle<Buffer>,
159 mut cx: AsyncAppContext,
160 ) -> Result<Option<Range<Anchor>>> {
161 if message.can_rename {
162 buffer
163 .update(&mut cx, |buffer, _| {
164 buffer.wait_for_version(message.version.into())
165 })
166 .await;
167 let start = message.start.and_then(deserialize_anchor);
168 let end = message.end.and_then(deserialize_anchor);
169 Ok(start.zip(end).map(|(start, end)| start..end))
170 } else {
171 Ok(None)
172 }
173 }
174
175 fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
176 message.buffer_id
177 }
178}
179
180#[async_trait(?Send)]
181impl LspCommand for PerformRename {
182 type Response = ProjectTransaction;
183 type LspRequest = lsp::request::Rename;
184 type ProtoRequest = proto::PerformRename;
185
186 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
187 lsp::RenameParams {
188 text_document_position: lsp::TextDocumentPositionParams {
189 text_document: lsp::TextDocumentIdentifier {
190 uri: lsp::Url::from_file_path(path).unwrap(),
191 },
192 position: self.position.to_lsp_position(),
193 },
194 new_name: self.new_name.clone(),
195 work_done_progress_params: Default::default(),
196 }
197 }
198
199 async fn response_from_lsp(
200 self,
201 message: Option<lsp::WorkspaceEdit>,
202 project: ModelHandle<Project>,
203 buffer: ModelHandle<Buffer>,
204 mut cx: AsyncAppContext,
205 ) -> Result<ProjectTransaction> {
206 if let Some(edit) = message {
207 let (language_name, language_server) = buffer.read_with(&cx, |buffer, _| {
208 let language = buffer
209 .language()
210 .ok_or_else(|| anyhow!("buffer's language was removed"))?;
211 let language_server = buffer
212 .language_server()
213 .cloned()
214 .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
215 Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
216 })?;
217 Project::deserialize_workspace_edit(
218 project,
219 edit,
220 self.push_to_history,
221 language_name,
222 language_server,
223 &mut cx,
224 )
225 .await
226 } else {
227 Ok(ProjectTransaction::default())
228 }
229 }
230
231 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
232 proto::PerformRename {
233 project_id,
234 buffer_id: buffer.remote_id(),
235 position: Some(language::proto::serialize_anchor(
236 &buffer.anchor_before(self.position),
237 )),
238 new_name: self.new_name.clone(),
239 }
240 }
241
242 fn from_proto(message: proto::PerformRename, _: &mut Project, buffer: &Buffer) -> Result<Self> {
243 let position = message
244 .position
245 .and_then(deserialize_anchor)
246 .ok_or_else(|| anyhow!("invalid position"))?;
247 if !buffer.can_resolve(&position) {
248 Err(anyhow!("cannot resolve position"))?;
249 }
250 Ok(Self {
251 position: position.to_point_utf16(buffer),
252 new_name: message.new_name,
253 push_to_history: false,
254 })
255 }
256
257 fn response_to_proto(
258 response: ProjectTransaction,
259 project: &mut Project,
260 peer_id: PeerId,
261 _: &clock::Global,
262 cx: &AppContext,
263 ) -> proto::PerformRenameResponse {
264 let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
265 proto::PerformRenameResponse {
266 transaction: Some(transaction),
267 }
268 }
269
270 async fn response_from_proto(
271 self,
272 message: proto::PerformRenameResponse,
273 project: ModelHandle<Project>,
274 _: ModelHandle<Buffer>,
275 mut cx: AsyncAppContext,
276 ) -> Result<ProjectTransaction> {
277 let message = message
278 .transaction
279 .ok_or_else(|| anyhow!("missing transaction"))?;
280 project
281 .update(&mut cx, |project, cx| {
282 project.deserialize_project_transaction(message, self.push_to_history, cx)
283 })
284 .await
285 }
286
287 fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
288 message.buffer_id
289 }
290}
291
292#[async_trait(?Send)]
293impl LspCommand for GetDefinition {
294 type Response = Vec<Location>;
295 type LspRequest = lsp::request::GotoDefinition;
296 type ProtoRequest = proto::GetDefinition;
297
298 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoDefinitionParams {
299 lsp::GotoDefinitionParams {
300 text_document_position_params: lsp::TextDocumentPositionParams {
301 text_document: lsp::TextDocumentIdentifier {
302 uri: lsp::Url::from_file_path(path).unwrap(),
303 },
304 position: self.position.to_lsp_position(),
305 },
306 work_done_progress_params: Default::default(),
307 partial_result_params: Default::default(),
308 }
309 }
310
311 async fn response_from_lsp(
312 self,
313 message: Option<lsp::GotoDefinitionResponse>,
314 project: ModelHandle<Project>,
315 buffer: ModelHandle<Buffer>,
316 mut cx: AsyncAppContext,
317 ) -> Result<Vec<Location>> {
318 let mut definitions = Vec::new();
319 let (language, language_server) = buffer
320 .read_with(&cx, |buffer, _| {
321 buffer
322 .language()
323 .cloned()
324 .zip(buffer.language_server().cloned())
325 })
326 .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
327
328 if let Some(message) = message {
329 let mut unresolved_locations = Vec::new();
330 match message {
331 lsp::GotoDefinitionResponse::Scalar(loc) => {
332 unresolved_locations.push((loc.uri, loc.range));
333 }
334 lsp::GotoDefinitionResponse::Array(locs) => {
335 unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
336 }
337 lsp::GotoDefinitionResponse::Link(links) => {
338 unresolved_locations.extend(
339 links
340 .into_iter()
341 .map(|l| (l.target_uri, l.target_selection_range)),
342 );
343 }
344 }
345
346 for (target_uri, target_range) in unresolved_locations {
347 let target_buffer_handle = project
348 .update(&mut cx, |this, cx| {
349 this.open_local_buffer_via_lsp(
350 target_uri,
351 language.name().to_string(),
352 language_server.clone(),
353 cx,
354 )
355 })
356 .await?;
357
358 cx.read(|cx| {
359 let target_buffer = target_buffer_handle.read(cx);
360 let target_start = target_buffer
361 .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
362 let target_end = target_buffer
363 .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
364 definitions.push(Location {
365 buffer: target_buffer_handle,
366 range: target_buffer.anchor_after(target_start)
367 ..target_buffer.anchor_before(target_end),
368 });
369 });
370 }
371 }
372
373 Ok(definitions)
374 }
375
376 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
377 proto::GetDefinition {
378 project_id,
379 buffer_id: buffer.remote_id(),
380 position: Some(language::proto::serialize_anchor(
381 &buffer.anchor_before(self.position),
382 )),
383 }
384 }
385
386 fn from_proto(message: proto::GetDefinition, _: &mut Project, buffer: &Buffer) -> Result<Self> {
387 let position = message
388 .position
389 .and_then(deserialize_anchor)
390 .ok_or_else(|| anyhow!("invalid position"))?;
391 if !buffer.can_resolve(&position) {
392 Err(anyhow!("cannot resolve position"))?;
393 }
394 Ok(Self {
395 position: position.to_point_utf16(buffer),
396 })
397 }
398
399 fn response_to_proto(
400 response: Vec<Location>,
401 project: &mut Project,
402 peer_id: PeerId,
403 _: &clock::Global,
404 cx: &AppContext,
405 ) -> proto::GetDefinitionResponse {
406 let locations = response
407 .into_iter()
408 .map(|definition| {
409 let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
410 proto::Location {
411 start: Some(serialize_anchor(&definition.range.start)),
412 end: Some(serialize_anchor(&definition.range.end)),
413 buffer: Some(buffer),
414 }
415 })
416 .collect();
417 proto::GetDefinitionResponse { locations }
418 }
419
420 async fn response_from_proto(
421 self,
422 message: proto::GetDefinitionResponse,
423 project: ModelHandle<Project>,
424 _: ModelHandle<Buffer>,
425 mut cx: AsyncAppContext,
426 ) -> Result<Vec<Location>> {
427 let mut locations = Vec::new();
428 for location in message.locations {
429 let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
430 let buffer = project
431 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
432 .await?;
433 let start = location
434 .start
435 .and_then(deserialize_anchor)
436 .ok_or_else(|| anyhow!("missing target start"))?;
437 let end = location
438 .end
439 .and_then(deserialize_anchor)
440 .ok_or_else(|| anyhow!("missing target end"))?;
441 locations.push(Location {
442 buffer,
443 range: start..end,
444 })
445 }
446 Ok(locations)
447 }
448
449 fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
450 message.buffer_id
451 }
452}
453
454#[async_trait(?Send)]
455impl LspCommand for GetReferences {
456 type Response = Vec<Location>;
457 type LspRequest = lsp::request::References;
458 type ProtoRequest = proto::GetReferences;
459
460 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::ReferenceParams {
461 lsp::ReferenceParams {
462 text_document_position: lsp::TextDocumentPositionParams {
463 text_document: lsp::TextDocumentIdentifier {
464 uri: lsp::Url::from_file_path(path).unwrap(),
465 },
466 position: self.position.to_lsp_position(),
467 },
468 work_done_progress_params: Default::default(),
469 partial_result_params: Default::default(),
470 context: lsp::ReferenceContext {
471 include_declaration: true,
472 },
473 }
474 }
475
476 async fn response_from_lsp(
477 self,
478 locations: Option<Vec<lsp::Location>>,
479 project: ModelHandle<Project>,
480 buffer: ModelHandle<Buffer>,
481 mut cx: AsyncAppContext,
482 ) -> Result<Vec<Location>> {
483 let mut references = Vec::new();
484 let (language, language_server) = buffer
485 .read_with(&cx, |buffer, _| {
486 buffer
487 .language()
488 .cloned()
489 .zip(buffer.language_server().cloned())
490 })
491 .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
492
493 if let Some(locations) = locations {
494 for lsp_location in locations {
495 let target_buffer_handle = project
496 .update(&mut cx, |this, cx| {
497 this.open_local_buffer_via_lsp(
498 lsp_location.uri,
499 language.name().to_string(),
500 language_server.clone(),
501 cx,
502 )
503 })
504 .await?;
505
506 cx.read(|cx| {
507 let target_buffer = target_buffer_handle.read(cx);
508 let target_start = target_buffer
509 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
510 let target_end = target_buffer
511 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
512 references.push(Location {
513 buffer: target_buffer_handle,
514 range: target_buffer.anchor_after(target_start)
515 ..target_buffer.anchor_before(target_end),
516 });
517 });
518 }
519 }
520
521 Ok(references)
522 }
523
524 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetReferences {
525 proto::GetReferences {
526 project_id,
527 buffer_id: buffer.remote_id(),
528 position: Some(language::proto::serialize_anchor(
529 &buffer.anchor_before(self.position),
530 )),
531 }
532 }
533
534 fn from_proto(message: proto::GetReferences, _: &mut Project, buffer: &Buffer) -> Result<Self> {
535 let position = message
536 .position
537 .and_then(deserialize_anchor)
538 .ok_or_else(|| anyhow!("invalid position"))?;
539 if !buffer.can_resolve(&position) {
540 Err(anyhow!("cannot resolve position"))?;
541 }
542 Ok(Self {
543 position: position.to_point_utf16(buffer),
544 })
545 }
546
547 fn response_to_proto(
548 response: Vec<Location>,
549 project: &mut Project,
550 peer_id: PeerId,
551 _: &clock::Global,
552 cx: &AppContext,
553 ) -> proto::GetReferencesResponse {
554 let locations = response
555 .into_iter()
556 .map(|definition| {
557 let buffer = project.serialize_buffer_for_peer(&definition.buffer, peer_id, cx);
558 proto::Location {
559 start: Some(serialize_anchor(&definition.range.start)),
560 end: Some(serialize_anchor(&definition.range.end)),
561 buffer: Some(buffer),
562 }
563 })
564 .collect();
565 proto::GetReferencesResponse { locations }
566 }
567
568 async fn response_from_proto(
569 self,
570 message: proto::GetReferencesResponse,
571 project: ModelHandle<Project>,
572 _: ModelHandle<Buffer>,
573 mut cx: AsyncAppContext,
574 ) -> Result<Vec<Location>> {
575 let mut locations = Vec::new();
576 for location in message.locations {
577 let buffer = location.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
578 let target_buffer = project
579 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
580 .await?;
581 let start = location
582 .start
583 .and_then(deserialize_anchor)
584 .ok_or_else(|| anyhow!("missing target start"))?;
585 let end = location
586 .end
587 .and_then(deserialize_anchor)
588 .ok_or_else(|| anyhow!("missing target end"))?;
589 locations.push(Location {
590 buffer: target_buffer,
591 range: start..end,
592 })
593 }
594 Ok(locations)
595 }
596
597 fn buffer_id_from_proto(message: &proto::GetReferences) -> u64 {
598 message.buffer_id
599 }
600}