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 project.clone(),
70 |_, _| unimplemented!(),
71 cx,
72 )
73 });
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_id,
103 editor,
104 },
105 lsp,
106 workspace,
107 buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").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
124 Self::new(language, capabilities, cx).await
125 }
126
127 // Constructs lsp range using a marked string with '[', ']' range delimiters
128 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
129 let ranges = self.ranges(marked_text);
130 self.to_lsp_range(ranges[0].clone())
131 }
132
133 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
134 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
135 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
136 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
137
138 self.editor(|editor, cx| {
139 let buffer = editor.buffer().read(cx);
140 let start = point_to_lsp(
141 buffer
142 .point_to_buffer_offset(start_point, cx)
143 .unwrap()
144 .1
145 .to_point_utf16(&buffer.read(cx)),
146 );
147 let end = point_to_lsp(
148 buffer
149 .point_to_buffer_offset(end_point, cx)
150 .unwrap()
151 .1
152 .to_point_utf16(&buffer.read(cx)),
153 );
154
155 lsp::Range { start, end }
156 })
157 }
158
159 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
160 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
161 let point = offset.to_point(&snapshot.buffer_snapshot);
162
163 self.editor(|editor, cx| {
164 let buffer = editor.buffer().read(cx);
165 point_to_lsp(
166 buffer
167 .point_to_buffer_offset(point, cx)
168 .unwrap()
169 .1
170 .to_point_utf16(&buffer.read(cx)),
171 )
172 })
173 }
174
175 pub fn update_workspace<F, T>(&mut self, update: F) -> T
176 where
177 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
178 {
179 self.workspace.update(self.cx.cx, update)
180 }
181
182 pub fn handle_request<T, F, Fut>(
183 &self,
184 mut handler: F,
185 ) -> futures::channel::mpsc::UnboundedReceiver<()>
186 where
187 T: 'static + request::Request,
188 T::Params: 'static + Send,
189 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
190 Fut: 'static + Send + Future<Output = Result<T::Result>>,
191 {
192 let url = self.buffer_lsp_url.clone();
193 self.lsp.handle_request::<T, _, _>(move |params, cx| {
194 let url = url.clone();
195 handler(url, params, cx)
196 })
197 }
198
199 pub fn notify<T: notification::Notification>(&self, params: T::Params) {
200 self.lsp.notify::<T>(params);
201 }
202}
203
204impl<'a> Deref for EditorLspTestContext<'a> {
205 type Target = EditorTestContext<'a>;
206
207 fn deref(&self) -> &Self::Target {
208 &self.cx
209 }
210}
211
212impl<'a> DerefMut for EditorLspTestContext<'a> {
213 fn deref_mut(&mut self) -> &mut Self::Target {
214 &mut self.cx
215 }
216}