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