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