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