editor_lsp_test_context.rs

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