1use crate::{Definition, 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
69#[async_trait(?Send)]
70impl LspCommand for PrepareRename {
71 type Response = Option<Range<Anchor>>;
72 type LspRequest = lsp::request::PrepareRenameRequest;
73 type ProtoRequest = proto::PrepareRename;
74
75 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
76 lsp::TextDocumentPositionParams {
77 text_document: lsp::TextDocumentIdentifier {
78 uri: lsp::Url::from_file_path(path).unwrap(),
79 },
80 position: self.position.to_lsp_position(),
81 }
82 }
83
84 async fn response_from_lsp(
85 self,
86 message: Option<lsp::PrepareRenameResponse>,
87 _: ModelHandle<Project>,
88 buffer: ModelHandle<Buffer>,
89 cx: AsyncAppContext,
90 ) -> Result<Option<Range<Anchor>>> {
91 buffer.read_with(&cx, |buffer, _| {
92 if let Some(
93 lsp::PrepareRenameResponse::Range(range)
94 | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
95 ) = message
96 {
97 let Range { start, end } = range_from_lsp(range);
98 if buffer.clip_point_utf16(start, Bias::Left) == start
99 && buffer.clip_point_utf16(end, Bias::Left) == end
100 {
101 return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
102 }
103 }
104 Ok(None)
105 })
106 }
107
108 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PrepareRename {
109 proto::PrepareRename {
110 project_id,
111 buffer_id: buffer.remote_id(),
112 position: Some(language::proto::serialize_anchor(
113 &buffer.anchor_before(self.position),
114 )),
115 }
116 }
117
118 fn from_proto(message: proto::PrepareRename, _: &mut Project, buffer: &Buffer) -> Result<Self> {
119 let position = message
120 .position
121 .and_then(deserialize_anchor)
122 .ok_or_else(|| anyhow!("invalid position"))?;
123 if !buffer.can_resolve(&position) {
124 Err(anyhow!("cannot resolve position"))?;
125 }
126 Ok(Self {
127 position: position.to_point_utf16(buffer),
128 })
129 }
130
131 fn response_to_proto(
132 range: Option<Range<Anchor>>,
133 _: &mut Project,
134 _: PeerId,
135 buffer_version: &clock::Global,
136 _: &AppContext,
137 ) -> proto::PrepareRenameResponse {
138 proto::PrepareRenameResponse {
139 can_rename: range.is_some(),
140 start: range
141 .as_ref()
142 .map(|range| language::proto::serialize_anchor(&range.start)),
143 end: range
144 .as_ref()
145 .map(|range| language::proto::serialize_anchor(&range.end)),
146 version: buffer_version.into(),
147 }
148 }
149
150 async fn response_from_proto(
151 self,
152 message: proto::PrepareRenameResponse,
153 _: ModelHandle<Project>,
154 buffer: ModelHandle<Buffer>,
155 mut cx: AsyncAppContext,
156 ) -> Result<Option<Range<Anchor>>> {
157 if message.can_rename {
158 buffer
159 .update(&mut cx, |buffer, _| {
160 buffer.wait_for_version(message.version.into())
161 })
162 .await;
163 let start = message.start.and_then(deserialize_anchor);
164 let end = message.end.and_then(deserialize_anchor);
165 Ok(start.zip(end).map(|(start, end)| start..end))
166 } else {
167 Ok(None)
168 }
169 }
170
171 fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
172 message.buffer_id
173 }
174}
175
176#[async_trait(?Send)]
177impl LspCommand for PerformRename {
178 type Response = ProjectTransaction;
179 type LspRequest = lsp::request::Rename;
180 type ProtoRequest = proto::PerformRename;
181
182 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
183 lsp::RenameParams {
184 text_document_position: lsp::TextDocumentPositionParams {
185 text_document: lsp::TextDocumentIdentifier {
186 uri: lsp::Url::from_file_path(path).unwrap(),
187 },
188 position: self.position.to_lsp_position(),
189 },
190 new_name: self.new_name.clone(),
191 work_done_progress_params: Default::default(),
192 }
193 }
194
195 async fn response_from_lsp(
196 self,
197 message: Option<lsp::WorkspaceEdit>,
198 project: ModelHandle<Project>,
199 buffer: ModelHandle<Buffer>,
200 mut cx: AsyncAppContext,
201 ) -> Result<ProjectTransaction> {
202 if let Some(edit) = message {
203 let (language_name, language_server) = buffer.read_with(&cx, |buffer, _| {
204 let language = buffer
205 .language()
206 .ok_or_else(|| anyhow!("buffer's language was removed"))?;
207 let language_server = buffer
208 .language_server()
209 .cloned()
210 .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
211 Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
212 })?;
213 Project::deserialize_workspace_edit(
214 project,
215 edit,
216 self.push_to_history,
217 language_name,
218 language_server,
219 &mut cx,
220 )
221 .await
222 } else {
223 Ok(ProjectTransaction::default())
224 }
225 }
226
227 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::PerformRename {
228 proto::PerformRename {
229 project_id,
230 buffer_id: buffer.remote_id(),
231 position: Some(language::proto::serialize_anchor(
232 &buffer.anchor_before(self.position),
233 )),
234 new_name: self.new_name.clone(),
235 }
236 }
237
238 fn from_proto(message: proto::PerformRename, _: &mut Project, buffer: &Buffer) -> Result<Self> {
239 let position = message
240 .position
241 .and_then(deserialize_anchor)
242 .ok_or_else(|| anyhow!("invalid position"))?;
243 if !buffer.can_resolve(&position) {
244 Err(anyhow!("cannot resolve position"))?;
245 }
246 Ok(Self {
247 position: position.to_point_utf16(buffer),
248 new_name: message.new_name,
249 push_to_history: false,
250 })
251 }
252
253 fn response_to_proto(
254 response: ProjectTransaction,
255 project: &mut Project,
256 peer_id: PeerId,
257 _: &clock::Global,
258 cx: &AppContext,
259 ) -> proto::PerformRenameResponse {
260 let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
261 proto::PerformRenameResponse {
262 transaction: Some(transaction),
263 }
264 }
265
266 async fn response_from_proto(
267 self,
268 message: proto::PerformRenameResponse,
269 project: ModelHandle<Project>,
270 _: ModelHandle<Buffer>,
271 mut cx: AsyncAppContext,
272 ) -> Result<ProjectTransaction> {
273 let message = message
274 .transaction
275 .ok_or_else(|| anyhow!("missing transaction"))?;
276 project
277 .update(&mut cx, |project, cx| {
278 project.deserialize_project_transaction(message, self.push_to_history, cx)
279 })
280 .await
281 }
282
283 fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
284 message.buffer_id
285 }
286}
287
288#[async_trait(?Send)]
289impl LspCommand for GetDefinition {
290 type Response = Vec<Definition>;
291 type LspRequest = lsp::request::GotoDefinition;
292 type ProtoRequest = proto::GetDefinition;
293
294 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::GotoDefinitionParams {
295 lsp::GotoDefinitionParams {
296 text_document_position_params: lsp::TextDocumentPositionParams {
297 text_document: lsp::TextDocumentIdentifier {
298 uri: lsp::Url::from_file_path(path).unwrap(),
299 },
300 position: self.position.to_lsp_position(),
301 },
302 work_done_progress_params: Default::default(),
303 partial_result_params: Default::default(),
304 }
305 }
306
307 async fn response_from_lsp(
308 self,
309 message: Option<lsp::GotoDefinitionResponse>,
310 project: ModelHandle<Project>,
311 buffer: ModelHandle<Buffer>,
312 mut cx: AsyncAppContext,
313 ) -> Result<Vec<Definition>> {
314 let mut definitions = Vec::new();
315 let (language, language_server) = buffer
316 .read_with(&cx, |buffer, _| {
317 buffer
318 .language()
319 .cloned()
320 .zip(buffer.language_server().cloned())
321 })
322 .ok_or_else(|| anyhow!("buffer no longer has language server"))?;
323
324 if let Some(message) = message {
325 let mut unresolved_locations = Vec::new();
326 match message {
327 lsp::GotoDefinitionResponse::Scalar(loc) => {
328 unresolved_locations.push((loc.uri, loc.range));
329 }
330 lsp::GotoDefinitionResponse::Array(locs) => {
331 unresolved_locations.extend(locs.into_iter().map(|l| (l.uri, l.range)));
332 }
333 lsp::GotoDefinitionResponse::Link(links) => {
334 unresolved_locations.extend(
335 links
336 .into_iter()
337 .map(|l| (l.target_uri, l.target_selection_range)),
338 );
339 }
340 }
341
342 for (target_uri, target_range) in unresolved_locations {
343 let target_buffer_handle = project
344 .update(&mut cx, |this, cx| {
345 this.open_local_buffer_from_lsp_path(
346 target_uri,
347 language.name().to_string(),
348 language_server.clone(),
349 cx,
350 )
351 })
352 .await?;
353
354 cx.read(|cx| {
355 let target_buffer = target_buffer_handle.read(cx);
356 let target_start = target_buffer
357 .clip_point_utf16(point_from_lsp(target_range.start), Bias::Left);
358 let target_end = target_buffer
359 .clip_point_utf16(point_from_lsp(target_range.end), Bias::Left);
360 definitions.push(Definition {
361 target_buffer: target_buffer_handle,
362 target_range: target_buffer.anchor_after(target_start)
363 ..target_buffer.anchor_before(target_end),
364 });
365 });
366 }
367 }
368
369 Ok(definitions)
370 }
371
372 fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetDefinition {
373 proto::GetDefinition {
374 project_id,
375 buffer_id: buffer.remote_id(),
376 position: Some(language::proto::serialize_anchor(
377 &buffer.anchor_before(self.position),
378 )),
379 }
380 }
381
382 fn from_proto(message: proto::GetDefinition, _: &mut Project, buffer: &Buffer) -> Result<Self> {
383 let position = message
384 .position
385 .and_then(deserialize_anchor)
386 .ok_or_else(|| anyhow!("invalid position"))?;
387 if !buffer.can_resolve(&position) {
388 Err(anyhow!("cannot resolve position"))?;
389 }
390 Ok(Self {
391 position: position.to_point_utf16(buffer),
392 })
393 }
394
395 fn response_to_proto(
396 response: Vec<Definition>,
397 project: &mut Project,
398 peer_id: PeerId,
399 _: &clock::Global,
400 cx: &AppContext,
401 ) -> proto::GetDefinitionResponse {
402 let definitions = response
403 .into_iter()
404 .map(|definition| {
405 let buffer =
406 project.serialize_buffer_for_peer(&definition.target_buffer, peer_id, cx);
407 proto::Definition {
408 target_start: Some(serialize_anchor(&definition.target_range.start)),
409 target_end: Some(serialize_anchor(&definition.target_range.end)),
410 buffer: Some(buffer),
411 }
412 })
413 .collect();
414 proto::GetDefinitionResponse { definitions }
415 }
416
417 async fn response_from_proto(
418 self,
419 message: proto::GetDefinitionResponse,
420 project: ModelHandle<Project>,
421 _: ModelHandle<Buffer>,
422 mut cx: AsyncAppContext,
423 ) -> Result<Vec<Definition>> {
424 let mut definitions = Vec::new();
425 for definition in message.definitions {
426 let buffer = definition.buffer.ok_or_else(|| anyhow!("missing buffer"))?;
427 let target_buffer = project
428 .update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
429 .await?;
430 let target_start = definition
431 .target_start
432 .and_then(deserialize_anchor)
433 .ok_or_else(|| anyhow!("missing target start"))?;
434 let target_end = definition
435 .target_end
436 .and_then(deserialize_anchor)
437 .ok_or_else(|| anyhow!("missing target end"))?;
438 definitions.push(Definition {
439 target_buffer,
440 target_range: target_start..target_end,
441 })
442 }
443 Ok(definitions)
444 }
445
446 fn buffer_id_from_proto(message: &proto::GetDefinition) -> u64 {
447 message.buffer_id
448 }
449}