lsp_command.rs

  1use crate::{Project, ProjectTransaction};
  2use anyhow::{anyhow, Result};
  3use client::proto;
  4use futures::{future::LocalBoxFuture, FutureExt};
  5use gpui::{AppContext, AsyncAppContext, ModelHandle};
  6use language::{
  7    proto::deserialize_anchor, range_from_lsp, Anchor, Buffer, PointUtf16, ToLspPosition,
  8};
  9use std::{ops::Range, path::Path};
 10
 11pub(crate) trait LspCommand: 'static {
 12    type Response: 'static + Default + Send;
 13    type LspRequest: 'static + Send + lsp::request::Request;
 14    type ProtoRequest: 'static + Send + proto::RequestMessage;
 15
 16    fn to_lsp(
 17        &self,
 18        path: &Path,
 19        cx: &AppContext,
 20    ) -> <Self::LspRequest as lsp::request::Request>::Params;
 21    fn to_proto(&self, project_id: u64, cx: &AppContext) -> Self::ProtoRequest;
 22    fn response_from_lsp(
 23        self,
 24        message: <Self::LspRequest as lsp::request::Request>::Result,
 25        project: ModelHandle<Project>,
 26        cx: AsyncAppContext,
 27    ) -> LocalBoxFuture<'static, Result<Self::Response>>;
 28    fn response_from_proto(
 29        self,
 30        message: <Self::ProtoRequest as proto::RequestMessage>::Response,
 31        project: ModelHandle<Project>,
 32        cx: AsyncAppContext,
 33    ) -> LocalBoxFuture<'static, Result<Self::Response>>;
 34}
 35
 36pub(crate) struct PrepareRename {
 37    pub buffer: ModelHandle<Buffer>,
 38    pub position: PointUtf16,
 39}
 40
 41#[derive(Debug)]
 42pub(crate) struct PerformRename {
 43    pub buffer: ModelHandle<Buffer>,
 44    pub position: PointUtf16,
 45    pub new_name: String,
 46    pub push_to_history: bool,
 47}
 48
 49impl LspCommand for PrepareRename {
 50    type Response = Option<Range<Anchor>>;
 51    type LspRequest = lsp::request::PrepareRenameRequest;
 52    type ProtoRequest = proto::PrepareRename;
 53
 54    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
 55        lsp::TextDocumentPositionParams {
 56            text_document: lsp::TextDocumentIdentifier {
 57                uri: lsp::Url::from_file_path(path).unwrap(),
 58            },
 59            position: self.position.to_lsp_position(),
 60        }
 61    }
 62
 63    fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PrepareRename {
 64        let buffer = &self.buffer.read(cx);
 65        let buffer_id = buffer.remote_id();
 66        proto::PrepareRename {
 67            project_id,
 68            buffer_id,
 69            position: Some(language::proto::serialize_anchor(
 70                &buffer.anchor_before(self.position),
 71            )),
 72        }
 73    }
 74
 75    fn response_from_lsp(
 76        self,
 77        message: Option<lsp::PrepareRenameResponse>,
 78        _: ModelHandle<Project>,
 79        cx: AsyncAppContext,
 80    ) -> LocalBoxFuture<'static, Result<Option<Range<Anchor>>>> {
 81        async move {
 82            Ok(message.and_then(|result| match result {
 83                lsp::PrepareRenameResponse::Range(range)
 84                | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. } => {
 85                    self.buffer.read_with(&cx, |buffer, _| {
 86                        let range = range_from_lsp(range);
 87                        Some(buffer.anchor_after(range.start)..buffer.anchor_before(range.end))
 88                    })
 89                }
 90                _ => None,
 91            }))
 92        }
 93        .boxed_local()
 94    }
 95
 96    fn response_from_proto(
 97        self,
 98        message: proto::PrepareRenameResponse,
 99        _: ModelHandle<Project>,
100        mut cx: AsyncAppContext,
101    ) -> LocalBoxFuture<'static, Result<Option<Range<Anchor>>>> {
102        async move {
103            if message.can_rename {
104                self.buffer
105                    .update(&mut cx, |buffer, _| {
106                        buffer.wait_for_version(message.version.into())
107                    })
108                    .await;
109                let start = message.start.and_then(deserialize_anchor);
110                let end = message.end.and_then(deserialize_anchor);
111                Ok(start.zip(end).map(|(start, end)| start..end))
112            } else {
113                Ok(None)
114            }
115        }
116        .boxed_local()
117    }
118}
119
120impl LspCommand for PerformRename {
121    type Response = ProjectTransaction;
122    type LspRequest = lsp::request::Rename;
123    type ProtoRequest = proto::PerformRename;
124
125    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
126        lsp::RenameParams {
127            text_document_position: lsp::TextDocumentPositionParams {
128                text_document: lsp::TextDocumentIdentifier {
129                    uri: lsp::Url::from_file_path(path).unwrap(),
130                },
131                position: self.position.to_lsp_position(),
132            },
133            new_name: self.new_name.clone(),
134            work_done_progress_params: Default::default(),
135        }
136    }
137
138    fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PerformRename {
139        let buffer = &self.buffer.read(cx);
140        let buffer_id = buffer.remote_id();
141        proto::PerformRename {
142            project_id,
143            buffer_id,
144            position: Some(language::proto::serialize_anchor(
145                &buffer.anchor_before(self.position),
146            )),
147            new_name: self.new_name.clone(),
148        }
149    }
150
151    fn response_from_lsp(
152        self,
153        message: Option<lsp::WorkspaceEdit>,
154        project: ModelHandle<Project>,
155        mut cx: AsyncAppContext,
156    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
157        async move {
158            if let Some(edit) = message {
159                let (language_name, language_server) =
160                    self.buffer.read_with(&cx, |buffer, _| {
161                        let language = buffer
162                            .language()
163                            .ok_or_else(|| anyhow!("buffer's language was removed"))?;
164                        let language_server = buffer
165                            .language_server()
166                            .cloned()
167                            .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
168                        Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
169                    })?;
170                Project::deserialize_workspace_edit(
171                    project,
172                    edit,
173                    self.push_to_history,
174                    language_name,
175                    language_server,
176                    &mut cx,
177                )
178                .await
179            } else {
180                Ok(ProjectTransaction::default())
181            }
182        }
183        .boxed_local()
184    }
185
186    fn response_from_proto(
187        self,
188        message: proto::PerformRenameResponse,
189        project: ModelHandle<Project>,
190        mut cx: AsyncAppContext,
191    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
192        async move {
193            let message = message
194                .transaction
195                .ok_or_else(|| anyhow!("missing transaction"))?;
196            project
197                .update(&mut cx, |project, cx| {
198                    project.deserialize_project_transaction(message, self.push_to_history, cx)
199                })
200                .await
201        }
202        .boxed_local()
203    }
204}