lsp_command.rs

  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}