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}