1use std::{
2 ops::{Deref, DerefMut, Range},
3 sync::Arc,
4};
5
6use anyhow::Result;
7
8use futures::Future;
9use gpui::{json, ViewContext, ViewHandle};
10use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig};
11use lsp::{notification, request};
12use project::Project;
13use smol::stream::StreamExt;
14use workspace::{pane, AppState, Workspace, WorkspaceHandle};
15
16use crate::{multi_buffer::ToPointUtf16, Editor, ToPoint};
17
18use super::editor_test_context::EditorTestContext;
19
20pub struct EditorLspTestContext<'a> {
21 pub cx: EditorTestContext<'a>,
22 pub lsp: lsp::FakeLanguageServer,
23 pub workspace: ViewHandle<Workspace>,
24 pub buffer_lsp_url: lsp::Url,
25}
26
27impl<'a> EditorLspTestContext<'a> {
28 pub async fn new(
29 mut language: Language,
30 capabilities: lsp::ServerCapabilities,
31 cx: &'a mut gpui::TestAppContext,
32 ) -> EditorLspTestContext<'a> {
33 use json::json;
34
35 cx.update(|cx| {
36 crate::init(cx);
37 pane::init(cx);
38 });
39
40 let params = cx.update(AppState::test);
41
42 let file_name = format!(
43 "file.{}",
44 language
45 .path_suffixes()
46 .first()
47 .unwrap_or(&"txt".to_string())
48 );
49
50 let mut fake_servers = language
51 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
52 capabilities,
53 ..Default::default()
54 }))
55 .await;
56
57 let project = Project::test(params.fs.clone(), [], cx).await;
58 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
59
60 params
61 .fs
62 .as_fake()
63 .insert_tree("/root", json!({ "dir": { file_name: "" }}))
64 .await;
65
66 let (window_id, workspace) = cx.add_window(|cx| {
67 Workspace::new(
68 Default::default(),
69 0,
70 project.clone(),
71 |_, _| unimplemented!(),
72 cx,
73 )
74 });
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_id,
104 editor,
105 },
106 lsp,
107 workspace,
108 buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").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
125 Self::new(language, capabilities, cx).await
126 }
127
128 // Constructs lsp range using a marked string with '[', ']' range delimiters
129 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
130 let ranges = self.ranges(marked_text);
131 self.to_lsp_range(ranges[0].clone())
132 }
133
134 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
135 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
136 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
137 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
138
139 self.editor(|editor, cx| {
140 let buffer = editor.buffer().read(cx);
141 let start = point_to_lsp(
142 buffer
143 .point_to_buffer_offset(start_point, cx)
144 .unwrap()
145 .1
146 .to_point_utf16(&buffer.read(cx)),
147 );
148 let end = point_to_lsp(
149 buffer
150 .point_to_buffer_offset(end_point, cx)
151 .unwrap()
152 .1
153 .to_point_utf16(&buffer.read(cx)),
154 );
155
156 lsp::Range { start, end }
157 })
158 }
159
160 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
161 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
162 let point = offset.to_point(&snapshot.buffer_snapshot);
163
164 self.editor(|editor, cx| {
165 let buffer = editor.buffer().read(cx);
166 point_to_lsp(
167 buffer
168 .point_to_buffer_offset(point, cx)
169 .unwrap()
170 .1
171 .to_point_utf16(&buffer.read(cx)),
172 )
173 })
174 }
175
176 pub fn update_workspace<F, T>(&mut self, update: F) -> T
177 where
178 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
179 {
180 self.workspace.update(self.cx.cx, update)
181 }
182
183 pub fn handle_request<T, F, Fut>(
184 &self,
185 mut handler: F,
186 ) -> futures::channel::mpsc::UnboundedReceiver<()>
187 where
188 T: 'static + request::Request,
189 T::Params: 'static + Send,
190 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
191 Fut: 'static + Send + Future<Output = Result<T::Result>>,
192 {
193 let url = self.buffer_lsp_url.clone();
194 self.lsp.handle_request::<T, _, _>(move |params, cx| {
195 let url = url.clone();
196 handler(url, params, cx)
197 })
198 }
199
200 pub fn notify<T: notification::Notification>(&self, params: T::Params) {
201 self.lsp.notify::<T>(params);
202 }
203}
204
205impl<'a> Deref for EditorLspTestContext<'a> {
206 type Target = EditorTestContext<'a>;
207
208 fn deref(&self) -> &Self::Target {
209 &self.cx
210 }
211}
212
213impl<'a> DerefMut for EditorLspTestContext<'a> {
214 fn deref_mut(&mut self) -> &mut Self::Target {
215 &mut self.cx
216 }
217}