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