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) =
67 cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx));
68 project
69 .update(cx, |project, cx| {
70 project.find_or_create_local_worktree("/root", true, cx)
71 })
72 .await
73 .unwrap();
74 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
75 .await;
76
77 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
78 let item = workspace
79 .update(cx, |workspace, cx| workspace.open_path(file, true, cx))
80 .await
81 .expect("Could not open test file");
82
83 let editor = cx.update(|cx| {
84 item.act_as::<Editor>(cx)
85 .expect("Opened test file wasn't an editor")
86 });
87 editor.update(cx, |_, cx| cx.focus_self());
88
89 let lsp = fake_servers.next().await.unwrap();
90
91 Self {
92 cx: EditorTestContext {
93 cx,
94 window_id,
95 editor,
96 },
97 lsp,
98 workspace,
99 buffer_lsp_url: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
100 }
101 }
102
103 pub async fn new_rust(
104 capabilities: lsp::ServerCapabilities,
105 cx: &'a mut gpui::TestAppContext,
106 ) -> EditorLspTestContext<'a> {
107 let language = Language::new(
108 LanguageConfig {
109 name: "Rust".into(),
110 path_suffixes: vec!["rs".to_string()],
111 ..Default::default()
112 },
113 Some(tree_sitter_rust::language()),
114 );
115
116 Self::new(language, capabilities, cx).await
117 }
118
119 // Constructs lsp range using a marked string with '[', ']' range delimiters
120 pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
121 let ranges = self.ranges(marked_text);
122 self.to_lsp_range(ranges[0].clone())
123 }
124
125 pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
126 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
127 let start_point = range.start.to_point(&snapshot.buffer_snapshot);
128 let end_point = range.end.to_point(&snapshot.buffer_snapshot);
129
130 self.editor(|editor, cx| {
131 let buffer = editor.buffer().read(cx);
132 let start = point_to_lsp(
133 buffer
134 .point_to_buffer_offset(start_point, cx)
135 .unwrap()
136 .1
137 .to_point_utf16(&buffer.read(cx)),
138 );
139 let end = point_to_lsp(
140 buffer
141 .point_to_buffer_offset(end_point, cx)
142 .unwrap()
143 .1
144 .to_point_utf16(&buffer.read(cx)),
145 );
146
147 lsp::Range { start, end }
148 })
149 }
150
151 pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
152 let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
153 let point = offset.to_point(&snapshot.buffer_snapshot);
154
155 self.editor(|editor, cx| {
156 let buffer = editor.buffer().read(cx);
157 point_to_lsp(
158 buffer
159 .point_to_buffer_offset(point, cx)
160 .unwrap()
161 .1
162 .to_point_utf16(&buffer.read(cx)),
163 )
164 })
165 }
166
167 pub fn update_workspace<F, T>(&mut self, update: F) -> T
168 where
169 F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
170 {
171 self.workspace.update(self.cx.cx, update)
172 }
173
174 pub fn handle_request<T, F, Fut>(
175 &self,
176 mut handler: F,
177 ) -> futures::channel::mpsc::UnboundedReceiver<()>
178 where
179 T: 'static + request::Request,
180 T::Params: 'static + Send,
181 F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
182 Fut: 'static + Send + Future<Output = Result<T::Result>>,
183 {
184 let url = self.buffer_lsp_url.clone();
185 self.lsp.handle_request::<T, _, _>(move |params, cx| {
186 let url = url.clone();
187 handler(url, params, cx)
188 })
189 }
190
191 pub fn notify<T: notification::Notification>(&self, params: T::Params) {
192 self.lsp.notify::<T>(params);
193 }
194}
195
196impl<'a> Deref for EditorLspTestContext<'a> {
197 type Target = EditorTestContext<'a>;
198
199 fn deref(&self) -> &Self::Target {
200 &self.cx
201 }
202}
203
204impl<'a> DerefMut for EditorLspTestContext<'a> {
205 fn deref_mut(&mut self) -> &mut Self::Target {
206 &mut self.cx
207 }
208}