lsp_command.rs

  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}