diff --git a/crates/editor/src/lsp_ext.rs b/crates/editor/src/lsp_ext.rs index d9ae428e8ffa62a0bf2756eea834bbc955f6e833..ef0f92de79b0fe7a7e4a495dc29c1305b2f5eefa 100644 --- a/crates/editor/src/lsp_ext.rs +++ b/crates/editor/src/lsp_ext.rs @@ -2,10 +2,9 @@ use std::sync::Arc; use std::time::Duration; use crate::Editor; -use collections::HashMap; +use collections::{HashMap, HashSet}; use gpui::AsyncApp; use gpui::{App, Entity, Task}; -use itertools::Itertools; use language::Buffer; use language::Language; use lsp::LanguageServerId; @@ -33,22 +32,34 @@ where F: Fn(&Language) -> bool, { let project = editor.project.clone()?; + let multi_buffer = editor.buffer(); + let mut seen_buffer_ids = HashSet::default(); editor .selections .disjoint_anchors_arc() .iter() - .filter_map(|selection| Some((selection.head(), selection.head().text_anchor.buffer_id?))) - .unique_by(|(_, buffer_id)| *buffer_id) - .find_map(|(trigger_anchor, buffer_id)| { - let buffer = editor.buffer().read(cx).buffer(buffer_id)?; - let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?; + .find_map(|selection| { + let multi_buffer = multi_buffer.read(cx); + let (position, buffer) = multi_buffer + .buffer_for_anchor(selection.head(), cx) + .map(|buffer| (selection.head(), buffer)) + .or_else(|| { + multi_buffer + .buffer_for_anchor(selection.tail(), cx) + .map(|buffer| (selection.tail(), buffer)) + })?; + if !seen_buffer_ids.insert(buffer.read(cx).remote_id()) { + return None; + } + + let language = buffer.read(cx).language_at(position.text_anchor)?; if filter_language(&language) { let server_id = buffer.update(cx, |buffer, cx| { project .read(cx) .language_server_id_for_name(buffer, &language_server_name, cx) })?; - Some((trigger_anchor, language, server_id, buffer)) + Some((position, language, server_id, buffer)) } else { None } @@ -173,3 +184,100 @@ pub fn lsp_tasks( .await }) } + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use futures::StreamExt as _; + use gpui::{AppContext as _, Entity, TestAppContext}; + use language::{FakeLspAdapter, Language}; + use languages::rust_lang; + use lsp::{LanguageServerId, LanguageServerName}; + use multi_buffer::{Anchor, MultiBuffer}; + use project::{FakeFs, Project}; + use util::path; + + use crate::{MoveToEnd, editor_tests::init_test, test::build_editor_with_project}; + + use super::find_specific_language_server_in_selection; + + #[gpui::test] + async fn test_find_language_server_at_end_of_file(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.executor()); + fs.insert_file(path!("/file.rs"), "fn main() {}".into()) + .await; + + let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await; + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(rust_lang()); + let mut fake_servers = + language_registry.register_fake_lsp("Rust", FakeLspAdapter::default()); + + let underlying_buffer = project + .update(cx, |project, cx| { + project.open_local_buffer(path!("/file.rs"), cx) + }) + .await + .unwrap(); + + let buffer = cx.new(|cx| MultiBuffer::singleton(underlying_buffer.clone(), cx)); + let (editor, cx) = cx.add_window_view(|window, cx| { + build_editor_with_project(project.clone(), buffer, window, cx) + }); + + let fake_server = fake_servers.next().await.unwrap(); + cx.executor().run_until_parked(); + + let expected_server_id = fake_server.server.server_id(); + let language_server_name = LanguageServerName::new_static("the-fake-language-server"); + let filter = |language: &Language| language.name().as_ref() == "Rust"; + + let assert_result = |result: Option<( + Anchor, + Arc, + LanguageServerId, + Entity, + )>, + message: &str| { + let (_, language, server_id, buffer) = result.expect(message); + assert_eq!( + language.name().as_ref(), + "Rust", + "{message}: wrong language" + ); + assert_eq!(server_id, expected_server_id, "{message}: wrong server ID"); + assert_eq!(buffer, underlying_buffer, "{message}: wrong buffer"); + }; + + editor.update(cx, |editor, cx| { + assert_result( + find_specific_language_server_in_selection( + editor, + cx, + filter, + language_server_name.clone(), + ), + "should find correct language server at beginning of file", + ); + }); + + editor.update_in(cx, |editor, window, cx| { + editor.move_to_end(&MoveToEnd, window, cx); + }); + + editor.update(cx, |editor, cx| { + assert_result( + find_specific_language_server_in_selection( + editor, + cx, + filter, + language_server_name.clone(), + ), + "should find correct language server at end of file", + ); + }); + } +}