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}
 47
 48impl LspCommand for PrepareRename {
 49    type Response = Option<Range<Anchor>>;
 50    type LspRequest = lsp::request::PrepareRenameRequest;
 51    type ProtoRequest = proto::PrepareRename;
 52
 53    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::TextDocumentPositionParams {
 54        lsp::TextDocumentPositionParams {
 55            text_document: lsp::TextDocumentIdentifier {
 56                uri: lsp::Url::from_file_path(path).unwrap(),
 57            },
 58            position: self.position.to_lsp_position(),
 59        }
 60    }
 61
 62    fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PrepareRename {
 63        let buffer_id = self.buffer.read(cx).remote_id();
 64        proto::PrepareRename {
 65            project_id,
 66            buffer_id,
 67            position: None,
 68        }
 69    }
 70
 71    fn response_from_lsp(
 72        self,
 73        message: Option<lsp::PrepareRenameResponse>,
 74        _: ModelHandle<Project>,
 75        cx: AsyncAppContext,
 76    ) -> LocalBoxFuture<'static, Result<Option<Range<Anchor>>>> {
 77        async move {
 78            Ok(message.and_then(|result| match result {
 79                lsp::PrepareRenameResponse::Range(range)
 80                | lsp::PrepareRenameResponse::RangeWithPlaceholder { range, .. } => {
 81                    self.buffer.read_with(&cx, |buffer, _| {
 82                        let range = range_from_lsp(range);
 83                        Some(buffer.anchor_after(range.start)..buffer.anchor_before(range.end))
 84                    })
 85                }
 86                _ => None,
 87            }))
 88        }
 89        .boxed_local()
 90    }
 91
 92    fn response_from_proto(
 93        self,
 94        message: proto::PrepareRenameResponse,
 95        _: ModelHandle<Project>,
 96        _: AsyncAppContext,
 97    ) -> LocalBoxFuture<'static, Result<Option<Range<Anchor>>>> {
 98        async move {
 99            if message.can_rename {
100                let start = message.start.and_then(deserialize_anchor);
101                let end = message.end.and_then(deserialize_anchor);
102                Ok(start.zip(end).map(|(start, end)| start..end))
103            } else {
104                Ok(None)
105            }
106        }
107        .boxed_local()
108    }
109}
110
111impl LspCommand for PerformRename {
112    type Response = ProjectTransaction;
113    type LspRequest = lsp::request::Rename;
114    type ProtoRequest = proto::PerformRename;
115
116    fn to_lsp(&self, path: &Path, _: &AppContext) -> lsp::RenameParams {
117        lsp::RenameParams {
118            text_document_position: lsp::TextDocumentPositionParams {
119                text_document: lsp::TextDocumentIdentifier {
120                    uri: lsp::Url::from_file_path(path).unwrap(),
121                },
122                position: self.position.to_lsp_position(),
123            },
124            new_name: self.new_name.clone(),
125            work_done_progress_params: Default::default(),
126        }
127    }
128
129    fn to_proto(&self, project_id: u64, cx: &AppContext) -> proto::PerformRename {
130        let buffer_id = self.buffer.read(cx).remote_id();
131        proto::PerformRename {
132            project_id,
133            buffer_id,
134            position: None,
135            new_name: self.new_name.clone(),
136        }
137    }
138
139    fn response_from_lsp(
140        self,
141        message: Option<lsp::WorkspaceEdit>,
142        project: ModelHandle<Project>,
143        mut cx: AsyncAppContext,
144    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
145        async move {
146            if let Some(edit) = message {
147                let (language_name, language_server) =
148                    self.buffer.read_with(&cx, |buffer, _| {
149                        let language = buffer
150                            .language()
151                            .ok_or_else(|| anyhow!("buffer's language was removed"))?;
152                        let language_server = buffer
153                            .language_server()
154                            .cloned()
155                            .ok_or_else(|| anyhow!("buffer's language server was removed"))?;
156                        Ok::<_, anyhow::Error>((language.name().to_string(), language_server))
157                    })?;
158                Project::deserialize_workspace_edit(
159                    project,
160                    edit,
161                    false,
162                    language_name,
163                    language_server,
164                    &mut cx,
165                )
166                .await
167            } else {
168                Ok(ProjectTransaction::default())
169            }
170        }
171        .boxed_local()
172    }
173
174    fn response_from_proto(
175        self,
176        message: proto::PerformRenameResponse,
177        project: ModelHandle<Project>,
178        mut cx: AsyncAppContext,
179    ) -> LocalBoxFuture<'static, Result<ProjectTransaction>> {
180        async move {
181            let message = message
182                .transaction
183                .ok_or_else(|| anyhow!("missing transaction"))?;
184            project
185                .update(&mut cx, |project, cx| {
186                    project.deserialize_project_transaction(message, false, cx)
187                })
188                .await
189        }
190        .boxed_local()
191    }
192}