1use crate::{Project, ProjectTransaction};
2use anyhow::{anyhow, Result};
3use async_trait::async_trait;
4use client::{proto, PeerId};
5use gpui::{AppContext, AsyncAppContext, ModelContext, ModelHandle};
6use language::{
7 proto::deserialize_anchor, range_from_lsp, Anchor, Bias, Buffer, PointUtf16, ToLspPosition,
8 ToPointUtf16,
9};
10use std::{ops::Range, path::Path};
11
12#[async_trait(?Send)]
13pub(crate) trait LspCommand: 'static + Sized {
14 type Response: 'static + Default + Send;
15 type LspRequest: 'static + Send + lsp::request::Request;
16 type ProtoRequest: 'static + Send + proto::RequestMessage;
17
18 fn to_lsp(
19 &self,
20 path: &Path,
21 cx: &AppContext,
22 ) -> <Self::LspRequest as lsp::request::Request>::Params;
23 async fn response_from_lsp(
24 self,
25 message: <Self::LspRequest as lsp::request::Request>::Result,
26 project: ModelHandle<Project>,
27 buffer: ModelHandle<Buffer>,
28 cx: AsyncAppContext,
29 ) -> Result<Self::Response>;
30
31 fn to_proto(
32 &self,
33 project_id: u64,
34 buffer: &ModelHandle<Buffer>,
35 cx: &AppContext,
36 ) -> Self::ProtoRequest;
37 fn from_proto(
38 message: Self::ProtoRequest,
39 project: &mut Project,
40 buffer: &ModelHandle<Buffer>,
41 cx: &mut ModelContext<Project>,
42 ) -> Result<Self>;
43 fn buffer_id_from_proto(message: &Self::ProtoRequest) -> u64;
44
45 fn response_to_proto(
46 response: Self::Response,
47 project: &mut Project,
48 peer_id: PeerId,
49 buffer_version: &clock::Global,
50 cx: &mut ModelContext<Project>,
51 ) -> <Self::ProtoRequest as proto::RequestMessage>::Response;
52 async fn response_from_proto(
53 self,
54 message: <Self::ProtoRequest as proto::RequestMessage>::Response,
55 project: ModelHandle<Project>,
56 buffer: ModelHandle<Buffer>,
57 cx: AsyncAppContext,
58 ) -> Result<Self::Response>;
59}
60
61pub(crate) struct PrepareRename {
62 pub position: PointUtf16,
63}
64
65pub(crate) struct PerformRename {
66 pub position: PointUtf16,
67 pub new_name: String,
68 pub push_to_history: bool,
69}
70
71#[async_trait(?Send)]
72impl LspCommand for PrepareRename {
73 type Response = Option<Range<Anchor>>;
74 type LspRequest = lsp::request::PrepareRenameRequest;
75 type ProtoRequest = proto::PrepareRename;
76
77 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
78 lsp::TextDocumentPositionParams {
79 text_document: lsp::TextDocumentIdentifier {
80 uri: lsp::Url::from_file_path(path).unwrap(),
81 },
82 position: self.position.to_lsp_position(),
83 }
84 }
85
86 async fn response_from_lsp(
87 self,
88 message: Option<lsp::PrepareRenameResponse>,
89 _: ModelHandle<Project>,
90 buffer: ModelHandle<Buffer>,
91 cx: AsyncAppContext,
92 ) -> Result<Option<Range<Anchor>>> {
93 buffer.read_with(&cx, |buffer, _| {
94 if let Some(
95 lsp::PrepareRenameResponse::Range(range)
96 | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. },
97 ) = message
98 {
99 let Range { start, end } = range_from_lsp(range);
100 if buffer.clip_point_utf16(start, Bias::Left) == start
101 && buffer.clip_point_utf16(end, Bias::Left) == end
102 {
103 return Ok(Some(buffer.anchor_after(start)..buffer.anchor_before(end)));
104 }
105 }
106 Ok(None)
107 })
108 }
109
110 fn to_proto(
111 &self,
112 project_id: u64,
113 buffer: &ModelHandle<Buffer>,
114 cx: &AppContext,
115 ) -> proto::PrepareRename {
116 proto::PrepareRename {
117 project_id,
118 buffer_id: buffer.read(cx).remote_id(),
119 position: Some(language::proto::serialize_anchor(
120 &buffer.read(cx).anchor_before(self.position),
121 )),
122 }
123 }
124
125 fn buffer_id_from_proto(message: &proto::PrepareRename) -> u64 {
126 message.buffer_id
127 }
128
129 fn from_proto(
130 message: proto::PrepareRename,
131 _: &mut Project,
132 buffer: &ModelHandle<Buffer>,
133 cx: &mut ModelContext<Project>,
134 ) -> Result<Self> {
135 let buffer = buffer.read(cx);
136 let position = message
137 .position
138 .and_then(deserialize_anchor)
139 .ok_or_else(|| anyhow!("invalid position"))?;
140 if !buffer.can_resolve(&position) {
141 Err(anyhow!("cannot resolve position"))?;
142 }
143 Ok(Self {
144 position: position.to_point_utf16(buffer),
145 })
146 }
147
148 fn response_to_proto(
149 range: Option<Range<Anchor>>,
150 _: &mut Project,
151 _: PeerId,
152 buffer_version: &clock::Global,
153 _: &mut ModelContext<Project>,
154 ) -> proto::PrepareRenameResponse {
155 proto::PrepareRenameResponse {
156 can_rename: range.is_some(),
157 start: range
158 .as_ref()
159 .map(|range| language::proto::serialize_anchor(&range.start)),
160 end: range
161 .as_ref()
162 .map(|range| language::proto::serialize_anchor(&range.end)),
163 version: buffer_version.into(),
164 }
165 }
166
167 async fn response_from_proto(
168 self,
169 message: proto::PrepareRenameResponse,
170 _: ModelHandle<Project>,
171 buffer: ModelHandle<Buffer>,
172 mut cx: AsyncAppContext,
173 ) -> Result<Option<Range<Anchor>>> {
174 if message.can_rename {
175 buffer
176 .update(&mut cx, |buffer, _| {
177 buffer.wait_for_version(message.version.into())
178 })
179 .await;
180 let start = message.start.and_then(deserialize_anchor);
181 let end = message.end.and_then(deserialize_anchor);
182 Ok(start.zip(end).map(|(start, end)| start..end))
183 } else {
184 Ok(None)
185 }
186 }
187}
188
189#[async_trait(?Send)]
190impl LspCommand for PerformRename {
191 type Response = ProjectTransaction;
192 type LspRequest = lsp::request::Rename;
193 type ProtoRequest = proto::PerformRename;
194
195 fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
196 lsp::RenameParams {
197 text_document_position: lsp::TextDocumentPositionParams {
198 text_document: lsp::TextDocumentIdentifier {
199 uri: lsp::Url::from_file_path(path).unwrap(),
200 },
201 position: self.position.to_lsp_position(),
202 },
203 new_name: self.new_name.clone(),
204 work_done_progress_params: Default::default(),
205 }
206 }
207
208 async fn response_from_lsp(
209 self,
210 message: Option<lsp::WorkspaceEdit>,
211 project: ModelHandle<Project>,
212 buffer: ModelHandle<Buffer>,
213 mut cx: AsyncAppContext,
214 ) -> Result<ProjectTransaction> {
215 if let Some(edit) = message {
216 let (language_name, language_server) = buffer.read_with(&cx, |buffer, _| {
217 let language = buffer
218 .language()
219 .ok_or_else(|| anyhow!("buffer's language was removed"))?;
220 let language_server = buffer
221 .language_server()
222 .cloned()
223 .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
224 Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
225 })?;
226 Project::deserialize_workspace_edit(
227 project,
228 edit,
229 self.push_to_history,
230 language_name,
231 language_server,
232 &mut cx,
233 )
234 .await
235 } else {
236 Ok(ProjectTransaction::default())
237 }
238 }
239
240 fn to_proto(
241 &self,
242 project_id: u64,
243 buffer: &ModelHandle<Buffer>,
244 cx: &AppContext,
245 ) -> proto::PerformRename {
246 let buffer = buffer.read(cx);
247 let buffer_id = buffer.remote_id();
248 proto::PerformRename {
249 project_id,
250 buffer_id,
251 position: Some(language::proto::serialize_anchor(
252 &buffer.anchor_before(self.position),
253 )),
254 new_name: self.new_name.clone(),
255 }
256 }
257
258 fn buffer_id_from_proto(message: &proto::PerformRename) -> u64 {
259 message.buffer_id
260 }
261
262 fn from_proto(
263 message: proto::PerformRename,
264 _: &mut Project,
265 buffer: &ModelHandle<Buffer>,
266 cx: &mut ModelContext<Project>,
267 ) -> Result<Self> {
268 let position = message
269 .position
270 .and_then(deserialize_anchor)
271 .ok_or_else(|| anyhow!("invalid position"))?;
272 let buffer = buffer.read(cx);
273 if !buffer.can_resolve(&position) {
274 Err(anyhow!("cannot resolve position"))?;
275 }
276 Ok(Self {
277 position: position.to_point_utf16(buffer),
278 new_name: message.new_name,
279 push_to_history: false,
280 })
281 }
282
283 fn response_to_proto(
284 response: ProjectTransaction,
285 project: &mut Project,
286 peer_id: PeerId,
287 _: &clock::Global,
288 cx: &mut ModelContext<Project>,
289 ) -> proto::PerformRenameResponse {
290 let transaction = project.serialize_project_transaction_for_peer(response, peer_id, cx);
291 proto::PerformRenameResponse {
292 transaction: Some(transaction),
293 }
294 }
295
296 async fn response_from_proto(
297 self,
298 message: proto::PerformRenameResponse,
299 project: ModelHandle<Project>,
300 _: ModelHandle<Buffer>,
301 mut cx: AsyncAppContext,
302 ) -> Result<ProjectTransaction> {
303 let message = message
304 .transaction
305 .ok_or_else(|| anyhow!("missing transaction"))?;
306 project
307 .update(&mut cx, |project, cx| {
308 project.deserialize_project_transaction(message, self.push_to_history, cx)
309 })
310 .await
311 }
312}