editor_lsp_test_context.rs

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