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, Bias, 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                        if buffer.clip_point_utf16(range.start, Bias::Left) == range.start
 88                            && buffer.clip_point_utf16(range.end, Bias::Left) == range.end
 89                        {
 90                            Some(buffer.anchor_after(range.start)..buffer.anchor_before(range.end))
 91                        } else {
 92                            None
 93                        }
 94                    })
 95                }
 96                _ => None,
 97            }))
 98        }
 99        .boxed_local()
100    }
101
102    fn response_from_proto(
103        self,
104        message: proto::PrepareRenameResponse,
105        _: ModelHandle<Project>,
106        mut cx: AsyncAppContext,
107    ) -> LocalBoxFuture<'static, Result<Option<Range<Anchor>>>> {
108        async move {
109            if message.can_rename {
110                self.buffer
111                    .update(&mut cx, |buffer, _| {
112                        buffer.wait_for_version(message.version.into())
113                    })
114                    .await;
115                let start = message.start.and_then(deserialize_anchor);
116                let end = message.end.and_then(deserialize_anchor);
117                Ok(start.zip(end).map(|(start, end)| start..end))
118            } else {
119                Ok(None)
120            }
121        }
122        .boxed_local()
123    }
124}
125
126impl LspCommand for PerformRename {
127    type Response = ProjectTransaction;
128    type LspRequest = lsp::request::Rename;
129    type ProtoRequest = proto::PerformRename;
130
131    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
132        lsp::RenameParams {
133            text_document_position: lsp::TextDocumentPositionParams {
134                text_document: lsp::TextDocumentIdentifier {
135                    uri: lsp::Url::from_file_path(path).unwrap(),
136                },
137                position: self.position.to_lsp_position(),
138            },
139            new_name: self.new_name.clone(),
140            work_done_progress_params: Default::default(),
141        }
142    }
143
144    fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PerformRename {
145        let buffer = &self.buffer.read(cx);
146        let buffer_id = buffer.remote_id();
147        proto::PerformRename {
148            project_id,
149            buffer_id,
150            position: Some(language::proto::serialize_anchor(
151                &buffer.anchor_before(self.position),
152            )),
153            new_name: self.new_name.clone(),
154        }
155    }
156
157    fn response_from_lsp(
158        self,
159        message: Option<lsp::WorkspaceEdit>,
160        project: ModelHandle<Project>,
161        mut cx: AsyncAppContext,
162    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
163        async move {
164            if let Some(edit) = message {
165                let (language_name, language_server) =
166                    self.buffer.read_with(&cx, |buffer, _| {
167                        let language = buffer
168                            .language()
169                            .ok_or_else(|| anyhow!("buffer's language was removed"))?;
170                        let language_server = buffer
171                            .language_server()
172                            .cloned()
173                            .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
174                        Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
175                    })?;
176                Project::deserialize_workspace_edit(
177                    project,
178                    edit,
179                    self.push_to_history,
180                    language_name,
181                    language_server,
182                    &mut cx,
183                )
184                .await
185            } else {
186                Ok(ProjectTransaction::default())
187            }
188        }
189        .boxed_local()
190    }
191
192    fn response_from_proto(
193        self,
194        message: proto::PerformRenameResponse,
195        project: ModelHandle<Project>,
196        mut cx: AsyncAppContext,
197    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
198        async move {
199            let message = message
200                .transaction
201                .ok_or_else(|| anyhow!("missing transaction"))?;
202            project
203                .update(&mut cx, |project, cx| {
204                    project.deserialize_project_transaction(message, self.push_to_history, cx)
205                })
206                .await
207        }
208        .boxed_local()
209    }
210}