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}