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        let app_state = cx.update(AppState::test);
 38
 39        cx.update(|cx| {
 40            theme::init((), cx);
 41            language::init(cx);
 42            crate::init(cx);
 43            pane::init(cx);
 44            Project::init_settings(cx);
 45            workspace::init_settings(cx);
 46        });
 47
 48        let file_name = format!(
 49            "file.{}",
 50            language
 51                .path_suffixes()
 52                .first()
 53                .unwrap_or(&"txt".to_string())
 54        );
 55
 56        let mut fake_servers = language
 57            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 58                capabilities,
 59                ..Default::default()
 60            }))
 61            .await;
 62
 63        let project = Project::test(app_state.fs.clone(), [], cx).await;
 64        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
 65
 66        app_state
 67            .fs
 68            .as_fake()
 69            .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
 70            .await;
 71
 72        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
 73        let workspace = window.root(cx);
 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: window.into(),
103                editor,
104            },
105            lsp,
106            workspace,
107            buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).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        .with_queries(LanguageQueries {
124            indents: Some(Cow::from(indoc! {r#"
125                [
126                    ((where_clause) _ @end)
127                    (field_expression)
128                    (call_expression)
129                    (assignment_expression)
130                    (let_declaration)
131                    (let_chain)
132                    (await_expression)
133                ] @indent
134
135                (_ "[" "]" @end) @indent
136                (_ "<" ">" @end) @indent
137                (_ "{" "}" @end) @indent
138                (_ "(" ")" @end) @indent"#})),
139            brackets: Some(Cow::from(indoc! {r#"
140                ("(" @open ")" @close)
141                ("[" @open "]" @close)
142                ("{" @open "}" @close)
143                ("<" @open ">" @close)
144                ("\"" @open "\"" @close)
145                (closure_parameters "|" @open "|" @close)"#})),
146            ..Default::default()
147        })
148        .expect("Could not parse queries");
149
150        Self::new(language, capabilities, cx).await
151    }
152
153    pub async fn new_typescript(
154        capabilities: lsp::ServerCapabilities,
155        cx: &'a mut gpui::TestAppContext,
156    ) -> EditorLspTestContext<'a> {
157        let language = Language::new(
158            LanguageConfig {
159                name: "Typescript".into(),
160                path_suffixes: vec!["ts".to_string()],
161                ..Default::default()
162            },
163            Some(tree_sitter_typescript::language_typescript()),
164        )
165        .with_queries(LanguageQueries {
166            brackets: Some(Cow::from(indoc! {r#"
167                ("(" @open ")" @close)
168                ("[" @open "]" @close)
169                ("{" @open "}" @close)
170                ("<" @open ">" @close)
171                ("\"" @open "\"" @close)"#})),
172            ..Default::default()
173        })
174        .expect("Could not parse queries");
175
176        Self::new(language, capabilities, cx).await
177    }
178
179    // Constructs lsp range using a marked string with '[', ']' range delimiters
180    pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
181        let ranges = self.ranges(marked_text);
182        self.to_lsp_range(ranges[0].clone())
183    }
184
185    pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
186        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
187        let start_point = range.start.to_point(&snapshot.buffer_snapshot);
188        let end_point = range.end.to_point(&snapshot.buffer_snapshot);
189
190        self.editor(|editor, cx| {
191            let buffer = editor.buffer().read(cx);
192            let start = point_to_lsp(
193                buffer
194                    .point_to_buffer_offset(start_point, cx)
195                    .unwrap()
196                    .1
197                    .to_point_utf16(&buffer.read(cx)),
198            );
199            let end = point_to_lsp(
200                buffer
201                    .point_to_buffer_offset(end_point, cx)
202                    .unwrap()
203                    .1
204                    .to_point_utf16(&buffer.read(cx)),
205            );
206
207            lsp::Range { start, end }
208        })
209    }
210
211    pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
212        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
213        let point = offset.to_point(&snapshot.buffer_snapshot);
214
215        self.editor(|editor, cx| {
216            let buffer = editor.buffer().read(cx);
217            point_to_lsp(
218                buffer
219                    .point_to_buffer_offset(point, cx)
220                    .unwrap()
221                    .1
222                    .to_point_utf16(&buffer.read(cx)),
223            )
224        })
225    }
226
227    pub fn update_workspace<F, T>(&mut self, update: F) -> T
228    where
229        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
230    {
231        self.workspace.update(self.cx.cx, update)
232    }
233
234    pub fn handle_request<T, F, Fut>(
235        &self,
236        mut handler: F,
237    ) -> futures::channel::mpsc::UnboundedReceiver<()>
238    where
239        T: 'static + request::Request,
240        T::Params: 'static + Send,
241        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
242        Fut: 'static + Send + Future<Output = Result<T::Result>>,
243    {
244        let url = self.buffer_lsp_url.clone();
245        self.lsp.handle_request::<T, _, _>(move |params, cx| {
246            let url = url.clone();
247            handler(url, params, cx)
248        })
249    }
250
251    pub fn notify<T: notification::Notification>(&self, params: T::Params) {
252        self.lsp.notify::<T>(params);
253    }
254}
255
256impl<'a> Deref for EditorLspTestContext<'a> {
257    type Target = EditorTestContext<'a>;
258
259    fn deref(&self) -> &Self::Target {
260        &self.cx
261    }
262}
263
264impl<'a> DerefMut for EditorLspTestContext<'a> {
265    fn deref_mut(&mut self) -> &mut Self::Target {
266        &mut self.cx
267    }
268}