1192 lines · 43.1 KB · Rust
RawDownload
use futures::future::join_all;use itertools::Itertools;use language::language_settings::LanguageSettings;use text::BufferId;use ui::{Context, Window}; use crate::{Editor, LSP_REQUEST_DEBOUNCE_TIMEOUT}; impl Editor {    pub(super) fn refresh_folding_ranges(        &mut self,        for_buffer: Option<BufferId>,        _window: &Window,        cx: &mut Context<Self>,    ) {        if !self.lsp_data_enabled() || !self.use_document_folding_ranges {            return;        }        let Some(project) = self.project.as_ref().map(|p| p.downgrade()) else {            return;        };         let buffers_to_query = self            .visible_buffers(cx)            .into_iter()            .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))            .chain(for_buffer.and_then(|id| self.buffer.read(cx).buffer(id)))            .filter(|buffer| {                let id = buffer.read(cx).remote_id();                (for_buffer.is_none_or(|target| target == id))                    && self.registered_buffers.contains_key(&id)                    && LanguageSettings::for_buffer(buffer.read(cx), cx)                        .document_folding_ranges                        .enabled()            })            .unique_by(|buffer| buffer.read(cx).remote_id())            .collect::<Vec<_>>();         self.refresh_folding_ranges_task = cx.spawn(async move |editor, cx| {            cx.background_executor()                .timer(LSP_REQUEST_DEBOUNCE_TIMEOUT)                .await;             let Some(tasks) = editor                .update(cx, |_, cx| {                    let project = project.upgrade()?;                    Some(project.read(cx).lsp_store().update(cx, |lsp_store, cx| {                        buffers_to_query                            .into_iter()                            .map(|buffer| {                                let buffer_id = buffer.read(cx).remote_id();                                let task = lsp_store.fetch_folding_ranges(&buffer, cx);                                async move { (buffer_id, task.await) }                            })                            .collect::<Vec<_>>()                    }))                })                .ok()                .flatten()            else {                return;            };             let results = join_all(tasks).await;            if results.is_empty() {                return;            }             editor                .update(cx, |editor, cx| {                    editor.display_map.update(cx, |display_map, cx| {                        for (buffer_id, ranges) in results {                            display_map.set_lsp_folding_ranges(buffer_id, ranges, cx);                        }                    });                    cx.notify();                })                .ok();        });    }     pub fn document_folding_ranges_enabled(&self, cx: &ui::App) -> bool {        self.use_document_folding_ranges && self.display_map.read(cx).has_lsp_folding_ranges()    }     /// Removes LSP folding creases for buffers whose `lsp_folding_ranges`    /// setting has been turned off, and triggers a refresh so newly-enabled    /// buffers get their ranges fetched.    pub(super) fn clear_disabled_lsp_folding_ranges(        &mut self,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if !self.use_document_folding_ranges {            return;        }         let buffers_to_clear = self            .buffer            .read(cx)            .all_buffers()            .into_iter()            .filter(|buffer| {                let buffer = buffer.read(cx);                !LanguageSettings::for_buffer(&buffer, cx)                    .document_folding_ranges                    .enabled()            })            .map(|buffer| buffer.read(cx).remote_id())            .collect::<Vec<_>>();         if !buffers_to_clear.is_empty() {            self.display_map.update(cx, |display_map, cx| {                for buffer_id in buffers_to_clear {                    display_map.clear_lsp_folding_ranges(buffer_id, cx);                }            });            cx.notify();        }         self.refresh_folding_ranges(None, window, cx);    }} #[cfg(test)]mod tests {    use futures::StreamExt as _;    use gpui::TestAppContext;    use lsp::FoldingRange;    use multi_buffer::MultiBufferRow;    use pretty_assertions::assert_eq;    use settings::DocumentFoldingRanges;     use crate::{        editor_tests::{init_test, update_test_language_settings},        test::editor_lsp_test_context::EditorLspTestContext,    };     #[gpui::test]    async fn test_lsp_folding_ranges_populates_creases(cx: &mut TestAppContext) {        init_test(cx, |_| {});         update_test_language_settings(cx, &|settings| {            settings.defaults.document_folding_ranges = Some(DocumentFoldingRanges::On);        });         let mut cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),                ..lsp::ServerCapabilities::default()            },            cx,        )        .await;         let mut folding_request = cx            .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(                move |_, _, _| async move {                    Ok(Some(vec![                        FoldingRange {                            start_line: 0,                            start_character: Some(10),                            end_line: 4,                            end_character: Some(1),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 1,                            start_character: Some(13),                            end_line: 3,                            end_character: Some(5),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 6,                            start_character: Some(11),                            end_line: 8,                            end_character: Some(1),                            kind: None,                            collapsed_text: None,                        },                    ]))                },            );         cx.set_state(            "ˇfn main() {\n    if true {\n        println!(\"hello\");\n    }\n}\n\nfn other() {\n    let x = 1;\n}\n",        );        assert!(folding_request.next().await.is_some());        cx.run_until_parked();         cx.editor.read_with(&cx.cx.cx, |editor, cx| {            assert!(                editor.document_folding_ranges_enabled(cx),                "Expected LSP folding ranges to be populated"            );        });         cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(                !snapshot.is_line_folded(MultiBufferRow(0)),                "Line 0 should not be folded before any fold action"            );            assert!(                !snapshot.is_line_folded(MultiBufferRow(6)),                "Line 6 should not be folded before any fold action"            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(0), window, cx);        });         cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(                snapshot.is_line_folded(MultiBufferRow(0)),                "Line 0 should be folded after fold_at on an LSP crease"            );            assert_eq!(                editor.display_text(cx),                "fn main() ⋯\n\nfn other() {\n    let x = 1;\n}\n",            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(6), window, cx);        });         cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(                snapshot.is_line_folded(MultiBufferRow(6)),                "Line 6 should be folded after fold_at on the second LSP crease"            );            assert_eq!(editor.display_text(cx), "fn main() ⋯\n\nfn other() ⋯\n",);        });    }     #[gpui::test]    async fn test_lsp_folding_ranges_disabled_by_default(cx: &mut TestAppContext) {        init_test(cx, |_| {});         let mut cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),                ..lsp::ServerCapabilities::default()            },            cx,        )        .await;         cx.set_state("ˇfn main() {\n    let x = 1;\n}\n");        cx.run_until_parked();         cx.editor.read_with(&cx.cx.cx, |editor, cx| {            assert!(                !editor.document_folding_ranges_enabled(cx),                "LSP folding ranges should not be enabled by default"            );        });    }     #[gpui::test]    async fn test_lsp_folding_ranges_toggling_off_removes_creases(cx: &mut TestAppContext) {        init_test(cx, |_| {});         update_test_language_settings(cx, &|settings| {            settings.defaults.document_folding_ranges = Some(DocumentFoldingRanges::On);        });         let mut cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),                ..lsp::ServerCapabilities::default()            },            cx,        )        .await;         let mut folding_request = cx            .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(                move |_, _, _| async move {                    Ok(Some(vec![FoldingRange {                        start_line: 0,                        start_character: Some(10),                        end_line: 4,                        end_character: Some(1),                        kind: None,                        collapsed_text: None,                    }]))                },            );         cx.set_state("ˇfn main() {\n    if true {\n        println!(\"hello\");\n    }\n}\n");        assert!(folding_request.next().await.is_some());        cx.run_until_parked();         cx.editor.read_with(&cx.cx.cx, |editor, cx| {            assert!(                editor.document_folding_ranges_enabled(cx),                "Expected LSP folding ranges to be active before toggling off"            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(0), window, cx);        });        cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(                snapshot.is_line_folded(MultiBufferRow(0)),                "Line 0 should be folded via LSP crease before toggling off"            );            assert_eq!(editor.display_text(cx), "fn main() ⋯\n",);        });         update_test_language_settings(&mut cx.cx.cx, &|settings| {            settings.defaults.document_folding_ranges = Some(DocumentFoldingRanges::Off);        });        cx.run_until_parked();         cx.editor.read_with(&cx.cx.cx, |editor, cx| {            assert!(                !editor.document_folding_ranges_enabled(cx),                "LSP folding ranges should be cleared after toggling off"            );        });    }     #[gpui::test]    async fn test_lsp_folding_ranges_nested_folds(cx: &mut TestAppContext) {        init_test(cx, |_| {});         update_test_language_settings(cx, &|settings| {            settings.defaults.document_folding_ranges = Some(DocumentFoldingRanges::On);        });         let mut cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),                ..lsp::ServerCapabilities::default()            },            cx,        )        .await;         let mut folding_request = cx            .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(                move |_, _, _| async move {                    Ok(Some(vec![                        FoldingRange {                            start_line: 0,                            start_character: Some(10),                            end_line: 7,                            end_character: Some(1),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 1,                            start_character: Some(12),                            end_line: 3,                            end_character: Some(5),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 4,                            start_character: Some(13),                            end_line: 6,                            end_character: Some(5),                            kind: None,                            collapsed_text: None,                        },                    ]))                },            );         cx.set_state(            "ˇfn main() {\n    if true {\n        a();\n    }\n    if false {\n        b();\n    }\n}\n",        );        assert!(folding_request.next().await.is_some());        cx.run_until_parked();         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(1), window, cx);        });        cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(snapshot.is_line_folded(MultiBufferRow(1)));            assert!(!snapshot.is_line_folded(MultiBufferRow(0)));            assert_eq!(                editor.display_text(cx),                "fn main() {\n    if true ⋯\n    if false {\n        b();\n    }\n}\n",            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(4), window, cx);        });        cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(snapshot.is_line_folded(MultiBufferRow(4)));            assert_eq!(                editor.display_text(cx),                "fn main() {\n    if true ⋯\n    if false ⋯\n}\n",            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(0), window, cx);        });        cx.update_editor(|editor, _window, cx| {            let snapshot = editor.display_snapshot(cx);            assert!(snapshot.is_line_folded(MultiBufferRow(0)));            assert_eq!(editor.display_text(cx), "fn main() ⋯\n",);        });    }     #[gpui::test]    async fn test_lsp_folding_ranges_unsorted_from_server(cx: &mut TestAppContext) {        init_test(cx, |_| {});         update_test_language_settings(cx, &|settings| {            settings.defaults.document_folding_ranges = Some(DocumentFoldingRanges::On);        });         let mut cx = EditorLspTestContext::new_rust(            lsp::ServerCapabilities {                folding_range_provider: Some(lsp::FoldingRangeProviderCapability::Simple(true)),                ..lsp::ServerCapabilities::default()            },            cx,        )        .await;         let mut folding_request = cx            .set_request_handler::<lsp::request::FoldingRangeRequest, _, _>(                move |_, _, _| async move {                    Ok(Some(vec![                        FoldingRange {                            start_line: 6,                            start_character: Some(11),                            end_line: 8,                            end_character: Some(1),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 0,                            start_character: Some(10),                            end_line: 4,                            end_character: Some(1),                            kind: None,                            collapsed_text: None,                        },                        FoldingRange {                            start_line: 1,                            start_character: Some(13),                            end_line: 3,                            end_character: Some(5),                            kind: None,                            collapsed_text: None,                        },                    ]))                },            );         cx.set_state(            "ˇfn main() {\n    if true {\n        println!(\"hello\");\n    }\n}\n\nfn other() {\n    let x = 1;\n}\n",        );        assert!(folding_request.next().await.is_some());        cx.run_until_parked();         cx.editor.read_with(&cx.cx.cx, |editor, cx| {            assert!(                editor.document_folding_ranges_enabled(cx),                "Expected LSP folding ranges to be populated despite unsorted server response"            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(0), window, cx);        });        cx.update_editor(|editor, _window, cx| {            assert_eq!(                editor.display_text(cx),                "fn main() ⋯\n\nfn other() {\n    let x = 1;\n}\n",            );        });         cx.update_editor(|editor, window, cx| {            editor.fold_at(MultiBufferRow(6), window, cx);        });        cx.update_editor(|editor, _window, cx| {            assert_eq!(editor.display_text(cx), "fn main() ⋯\n\nfn other() ⋯\n",);

Lines 1–500 of 1192.