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}