1use std::{
2 borrow::Cow,
3 ops::{Deref, DerefMut, Range},
4 sync::Arc,
5};
6
7use anyhow::Result;
8
9use futures::Future;
10use gpui::{json, ViewContext, ViewHandle};
11use indoc::indoc;
12use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
13use lsp::{notification, request};
14use project::Project;
15use smol::stream::StreamExt;
16use workspace::{pane, AppState, Workspace, WorkspaceHandle};
17
18use crate::{multi_buffer::ToPointUtf16, Editor, ToPoint};
19
20use super::editor_test_context::EditorTestContext;
21
22pub struct EditorLspTestContext<'a> {
23 pub cx: EditorTestContext<'a>,
24 pub lsp: lsp::FakeLanguageServer,
25 pub workspace: ViewHandle<Workspace>,
26 pub buffer_lsp_url: lsp::Url,
27}
28
29impl<'a> EditorLspTestContext<'a> {
30 pub async fn new(
31 mut language: Language,
32 capabilities: lsp::ServerCapabilities,
33 cx: &'a mut gpui::TestAppContext,
34 ) -> EditorLspTestContext<'a> {
35 use json::json;
36
37 cx.update(|cx| {
38 crate::init(cx);
39 pane::init(cx);
40 });
41
42 let app_state = cx.update(AppState::test);
43
44 let file_name = format!(
45 "file.{}",
46 language
47 .path_suffixes()
48 .first()
49 .unwrap_or(&"txt".to_string())
50 );
51
52 let mut fake_servers = language
53 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
54 capabilities,
55 ..Default::default()
56 }))
57 .await;
58
59 let project = Project::test(app_state.fs.clone(), [], cx).await;
60 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
61
62 app_state
63 .fs
64 .as_fake()
65 .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
66 .await;
67
68 let (window_id, workspace) = cx.add_window(|cx| {
69 Workspace::new(
70 Default::default(),
71 0,
72 project.clone(),
73 |_, _| unimplemented!(),
74 cx,
75 )
76 });
77 project
78 .update(cx, |project, cx| {
79 project.find_or_create_local_worktree("/root", true, cx)
80 })
81 .await
82 .unwrap();
83 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
84 .await;
85
86 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
87 let item = workspace
88 .update(cx, |workspace, cx| {
89 workspace.open_path(file, None, true, cx)
90 })
91 .await
92 .expect("Could not open test file");
93
94 let editor = cx.update(|cx| {
95 item.act_as::<Editor>(cx)
96 .expect("Opened test file wasn't an editor")
97 });
98 editor.update(cx, |_, cx| cx.focus_self());
99
100 let lsp = fake_servers.next().await.unwrap();
101
102 Self {
103 cx: EditorTestContext {
104 cx,
105 window_id,
106 editor,
107 },
108 lsp,
109 workspace,
110 buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
111 }
112 }
113
114 pub async fn new_rust(
115 capabilities: lsp::ServerCapabilities,
116 cx: &'a mut gpui::TestAppContext,
117 ) -> EditorLspTestContext<'a> {
118 let language = Language::new(
119 LanguageConfig {
120 name: "Rust".into(),
121 path_suffixes: vec!["rs".to_string()],
122 ..Default::default()
123 },
124 Some(tree_sitter_rust::language()),
125 )
126 .with_queries(LanguageQueries {
127 indents: Some(Cow::from(indoc! {r#"
128 [
129 ((where_clause) _ @end)
130 (field_expression)
131 (call_expression)
132 (assignment_expression)
133 (let_declaration)
134 (let_chain)
135 (await_expression)
136 ] @indent
137
138 (_ "[" "]" @end) @indent
139 (_ "<" ">" @end) @indent
140 (_ "{" "}" @end) @indent
141 (_ "(" ")" @end) @indent"#})),
142 brackets: Some(Cow::from(indoc! {r#"
143 ("(" @open ")" @close)
144 ("[" @open "]" @close)
145 ("{" @open "}" @close)
146 ("<" @open ">" @close)
147 ("\"" @open "\"" @close)
148 (closure_parameters "|" @open "|" @close)"#})),
149 ..Default::default()
150 })
151 .expect("Could not parse queries");
152
153 Self::new(language, capabilities, cx).await
154 }
155
156 pub async fn new_typescript(
157 capabilities: lsp::ServerCapabilities,
158 cx: &'a mut gpui::TestAppContext,
159 ) -> EditorLspTestContext<'a> {
160 let language = Language::new(
161 LanguageConfig {
162 name: "Typescript".into(),
163 path_suffixes: vec!["ts".to_string()],
164 ..Default::default()
165 },
166 Some(tree_sitter_typescript::language_typescript()),
167 )
168 .with_queries(LanguageQueries {
169 brackets: Some(Cow::from(indoc! {r#"
170 ("(" @open ")" @close)
171 ("[" @open "]" @close)
172 ("{" @open "}" @close)
173 ("<" @open ">" @close)
174 ("\"" @open "\"" @close)"#})),
175 ..Default::default()
176 })
177 .expect("Could not parse queries");
178
179 Self::new(language, capabilities, cx).await
180 }
181
182 // Constructs lsp range using a marked string with '[', ']' range delimiters
183 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
184 let ranges = self.ranges(marked_text);
185 self.to_lsp_range(ranges[0].clone())
186 }
187
188 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
189 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
190 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
191 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
192
193 self.editor(|editor, cx| {
194 let buffer = editor.buffer().read(cx);
195 let start = point_to_lsp(
196 buffer
197 .point_to_buffer_offset(start_point, cx)
198 .unwrap()
199 .1
200 .to_point_utf16(&buffer.read(cx)),
201 );
202 let end = point_to_lsp(
203 buffer
204 .point_to_buffer_offset(end_point, cx)
205 .unwrap()
206 .1
207 .to_point_utf16(&buffer.read(cx)),
208 );
209
210 lsp::Range { start, end }
211 })
212 }
213
214 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
215 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
216 let point = offset.to_point(&snapshot.buffer_snapshot);
217
218 self.editor(|editor, cx| {
219 let buffer = editor.buffer().read(cx);
220 point_to_lsp(
221 buffer
222 .point_to_buffer_offset(point, cx)
223 .unwrap()
224 .1
225 .to_point_utf16(&buffer.read(cx)),
226 )
227 })
228 }
229
230 pub fn update_workspace<F, T>(&mut self, update: F) -> T
231 where
232 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
233 {
234 self.workspace.update(self.cx.cx, update)
235 }
236
237 pub fn handle_request<T, F, Fut>(
238 &self,
239 mut handler: F,
240 ) -> futures::channel::mpsc::UnboundedReceiver<()>
241 where
242 T: 'static + request::Request,
243 T::Params: 'static + Send,
244 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
245 Fut: 'static + Send + Future<Output = Result<T::Result>>,
246 {
247 let url = self.buffer_lsp_url.clone();
248 self.lsp.handle_request::<T, _, _>(move |params, cx| {
249 let url = url.clone();
250 handler(url, params, cx)
251 })
252 }
253
254 pub fn notify<T: notification::Notification>(&self, params: T::Params) {
255 self.lsp.notify::<T>(params);
256 }
257}
258
259impl<'a> Deref for EditorLspTestContext<'a> {
260 type Target = EditorTestContext<'a>;
261
262 fn deref(&self) -> &Self::Target {
263 &self.cx
264 }
265}
266
267impl<'a> DerefMut for EditorLspTestContext<'a> {
268 fn deref_mut(&mut self) -> &mut Self::Target {
269 &mut self.cx
270 }
271}