1241 lines · 44.5 KB · Rust
RawDownload
use std::path::Path; use futures::channel::oneshot;use vec1::vec1;use warp_editor::content::buffer::{InitialBufferState, SelectionOffsets};use warp_editor::multiline::MultilineString;use warp_util::content_version::ContentVersion;use warpui::App; use super::*;use crate::code::editor::line::EditorLineLocation;use crate::code::editor::view::code_text_styles;use crate::settings::FontSettings;use crate::test_util::settings::initialize_settings_for_tests; fn initialize_deps(app: &mut App) {    app.add_singleton_model(|_| Appearance::mock());    initialize_settings_for_tests(app);} fn mock_model(app: &mut App, text: &str, version: ContentVersion) -> ModelHandle<CodeEditorModel> {    app.add_model(|ctx| {        let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None);        let mut model = CodeEditorModel::new(styles, None, false, None, ctx);        let state = InitialBufferState::plain_text(text).with_version(version);        model.reset_content(state, ctx);        model.set_language_with_local_path(Path::new("/test.rs"), ctx);        model    })} /// Builds a `CodeEditorModel` that shares an existing `Buffer` rather than creating its own./// Used to reproduce configurations where multiple editors point at the same underlying content/// (e.g. two views of the same open file), such as via `CodeEditorView::new`'s optional shared/// `Buffer` parameter.fn mock_model_with_buffer(    app: &mut App,    buffer: ModelHandle<Buffer>,) -> ModelHandle<CodeEditorModel> {    app.add_model(|ctx| {        let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None);        let mut model = CodeEditorModel::new(styles, None, false, Some(buffer), ctx);        model.set_language_with_local_path(Path::new("/test.rs"), ctx);        model    })} fn mock_model_with_diff(    app: &mut App,    base_text: &str,    current_text: &str,    version: ContentVersion,) -> ModelHandle<CodeEditorModel> {    app.add_model(|ctx| {        let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None);        let mut model = CodeEditorModel::new(styles, None, false, None, ctx);        let state = InitialBufferState::plain_text(current_text).with_version(version);        model.reset_content(state, ctx);        model.set_language_with_local_path(Path::new("/test.rs"), ctx);         // Set up diff model with base text        model.diff().update(ctx, |diff, _| {            diff.set_base(MultilineString::apply(base_text));        });         model    })} async fn layout_model(app: &mut App, model: &ModelHandle<CodeEditorModel>) {    app.read(|ctx| model.as_ref(ctx).render_state.as_ref(ctx).layout_complete())        .await;} #[test]fn test_two_editors_sharing_a_buffer_both_lay_out_a_large_content_replace() {    // Regression test for APP-4844: `CodeEditorView::new` can pass an existing shared `Buffer`    // into `CodeEditorModel::new`, so two editors can point at the same underlying content (for    // example, two views of the same open file). Both editors independently subscribe to and    // clone the `ContentChanged` event's `EditDelta` emitted by a full content replace, so more    // than one clone of `new_lines` can be alive when either editor lays out. This exercises that    // configuration end to end: it must not panic, and both editors must lay out the new content    // correctly regardless of how many clones of the delta are alive when their layout runs.    App::test((), |mut app| async move {        initialize_deps(&mut app);         let shared_buffer = app.add_model(|_| Buffer::default());        let editor_a = mock_model_with_buffer(&mut app, shared_buffer.clone());        let editor_b = mock_model_with_buffer(&mut app, shared_buffer.clone());         let content = "line one\nline two\nline three\n".repeat(50);        shared_buffer.update(&mut app, |buffer, ctx| {            buffer.replace_all(content.clone(), ctx);        });         layout_model(&mut app, &editor_a).await;        layout_model(&mut app, &editor_b).await;         for editor in [&editor_a, &editor_b] {            editor.read(&app, |editor, ctx| {                let text = editor.content.as_ref(ctx).text().as_str().to_string();                assert!(                    text.contains("line three"),                    "expected replaced content, got: {text}"                );                assert_eq!(text.matches("line one").count(), 50);                assert!(editor.render_state.as_ref(ctx).blocks() > 1);            });        }    })} #[test]fn replace_first_n_characters_handles_incremental_unicode_prefix() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(&mut app, "suffix", ContentVersion::new());         editor.update(&mut app, |editor, ctx| {            editor.replace_first_n_characters(CharOffset::from(0), "é", ctx);            editor.replace_first_n_characters(CharOffset::from(1), "élan", ctx);        });         editor.read(&app, |editor, ctx| {            assert_eq!(editor.content().as_ref(ctx).text().as_str(), "élansuffix");        });    });} #[test]fn test_no_trailing_newline() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(&mut app, "", ContentVersion::new());        // We need to layout the model to be able to select by line boundaries.        layout_model(&mut app, &editor).await;         editor.update(&mut app, |editor, ctx| {            editor.cursor_at(CharOffset::from(1), ctx);        });         // At the start we should have a trailing newline (the same height as an empty line).        let old_height = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height());         editor.update(&mut app, |editor, ctx| {            editor.insert("a", EditOrigin::UserTyped, ctx);        });        layout_model(&mut app, &editor).await;         // After insertion, we should replace the trailing newline with an actual text line.        let height_after_insertion =            app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height());        assert_eq!(old_height, height_after_insertion);         editor.update(&mut app, |editor, ctx| {            editor.enter(ctx);        });        layout_model(&mut app, &editor).await;         // Enter should create a newline, which adds to the height.        let height_after_newline =            app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height());        assert_ne!(old_height, height_after_newline);    })} #[test]fn test_toggle_comment() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(&mut app, "    First\n    Second", ContentVersion::new());        // We need to layout the model to be able to select by line boundaries.        layout_model(&mut app, &editor).await;         editor.update(&mut app, |editor, ctx| {            editor.cursor_at(CharOffset::from(5), ctx);            editor.select_to_line_end(ctx);        });         editor.update(&mut app, |editor, ctx| {            assert_eq!(                editor.selections(ctx),                vec1![SelectionOffsets {                    head: CharOffset::from(10),                    tail: CharOffset::from(5)                }]            );            editor.toggle_comments(ctx);        });         // Toggling comment for the first line.        editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "    // First\n    Second"            );        });         editor.update(&mut app, |editor, ctx| {            editor.select_all(ctx);        });         // Not all selected lines have comment prefix. This should add the prefix again.        editor.update(&mut app, |editor, ctx| {            editor.toggle_comments(ctx);        });         editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "    // // First\n    // Second"            );        });         editor.update(&mut app, |editor, ctx| {            editor.cursor_at(CharOffset::from(20), ctx);            editor.select_to_line_end(ctx);        });         editor.update(&mut app, |editor, ctx| {            editor.toggle_comments(ctx);        });         // Toggling comment on a line with comment should uncomment it.        editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "    // // First\n    Second"            );        });    })} #[test]fn test_apply_diffs() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        // Historically, applying diffs at the very start of an empty buffer (line range 0..0)        // could preserve the buffer's internal trailing-newline/block marker, resulting in an        // extra newline in the final content. This guards against that by ensuring we replace the        // implicit initial block correctly and do not end up with an extra trailing newline.        let editor_zero = mock_model(&mut app, "", ContentVersion::new());        layout_model(&mut app, &editor_zero).await;        editor_zero.update(&mut app, |editor, ctx| {            editor.apply_diffs(                vec![DiffDelta {                    replacement_line_range: 0..0,                    insertion: "First\nSecond\n".to_string(),                }],                ctx,            );        });        editor_zero.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "First\nSecond\n"            );        });        let editor = mock_model(            &mut app,            "    First\n    Second\n        Third",            ContentVersion::new(),        );        // We need to layout the model to be able to select by line boundaries.        layout_model(&mut app, &editor).await;         // Put cursor on the second line between S and e.        editor.update(&mut app, |editor, ctx| {            editor.cursor_at(CharOffset::from(16), ctx);        });         editor.update(&mut app, |editor, ctx| {            editor.apply_diffs(                vec![DiffDelta {                    replacement_line_range: 2..3,                    insertion: "    Fourth\n    Fifth\n".to_string(),                }],                ctx,            );        });         // Inserted content should have the same indentation level.        // The selections should not change after the insertion.        editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "    First\n    Fourth\n    Fifth\n        Third"            );            assert_eq!(                editor.selections(ctx),                vec1![SelectionOffsets {                    head: CharOffset::from(16),                    tail: CharOffset::from(16)                }]            );        });    })} #[test]fn test_apply_diff_on_empty_buffer() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(&mut app, "", ContentVersion::new());        layout_model(&mut app, &editor).await;         editor.update(&mut app, |editor, ctx| {            editor.apply_diffs(                vec![DiffDelta {                    replacement_line_range: 0..0,                    insertion: "First line\nSecond line\n".to_string(),                }],                ctx,            );        });         editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "First line\nSecond line\n"            );            assert_eq!(                editor.selections(ctx),                vec1![SelectionOffsets {                    head: CharOffset::from(24),                    tail: CharOffset::from(24)                }]            );        });    })} #[test]fn test_apply_single_deletion() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(            &mut app,            "    First\n    Second\n        Third",            ContentVersion::new(),        );        layout_model(&mut app, &editor).await;         editor.update(&mut app, |editor, ctx| {            editor.apply_diffs(                vec![DiffDelta {                    replacement_line_range: 1..2,                    insertion: "".to_string(),                }],                ctx,            );        });         editor.read(&app, |editor, ctx| {            assert_eq!(                editor.content.as_ref(ctx).text().as_str(),                "    Second\n        Third"            );        });         editor.update(&mut app, |editor, ctx| {            editor.apply_diffs(                vec![DiffDelta {                    replacement_line_range: 2..3,                    insertion: "".to_string(),                }],                ctx,            );        });        editor.read(&app, |editor, ctx| {            assert_eq!(editor.content.as_ref(ctx).text().as_str(), "    Second\n");        });    })} // #[test]// fn test_indent() {//     App::test((), |mut app| async move {//         initialize_deps(&mut app);//         let editor = mock_model(&mut app, "fn test() {\ns\n{\ntest\n}\n}");//         layout_model(&mut app, &editor).await; //         editor.update(&mut app, |editor, ctx| {//             editor.cursor_at(CharOffset::from(13), ctx);//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.indent(false, ctx);//         }); //         // Indent with one expected indent level.//         editor.read(&app, |editor, ctx| {//             assert_eq!(//                 editor.content.as_ref(ctx).text(),//                 "fn test() {\n    s\n{\ntest\n}\n}"//             );//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.cursor_at(CharOffset::from(21), ctx);//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.indent(false, ctx);//         }); //         // Indent with two expected indent level.//         editor.read(&app, |editor, ctx| {//             assert_eq!(//                 editor.content.as_ref(ctx).text(),//                 "fn test() {\n    s\n{\n        test\n}\n}"//             );//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.undo(ctx);//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.cursor_at(CharOffset::from(22), ctx);//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.indent(false, ctx);//         }); //         // Indent in the middle of the text should only advance one indent level.//         editor.read(&app, |editor, ctx| {//             // TODO(kevin): Looks like there is a bug here where the indentation should be 4,//             // it is currently 3.//             assert_eq!(//                 editor.content.as_ref(ctx).text(),//                 "fn test() {\n    s\n{\nt   est\n}\n}"//             );//         });//     })// } // This test flakes occasionally. I think it could be due to how we are initializing tree-sitter// in unit tests.// TODO(kevin): Re-enable this// #[test]// fn test_bracket_expansion() {//     App::test((), |mut app| async move {//         initialize_deps(&mut app);//         let editor = mock_model(&mut app, "fn test() {}");//         layout_model(&mut app, &editor).await; //         editor.update(&mut app, |editor, ctx| {//             editor.cursor_at(CharOffset::from(9), ctx);//         }); //         // Bracket expansion in parentheses.//         editor.update(&mut app, |editor, ctx| {//             editor.enter(ctx);//         }); //         editor.read(&app, |editor, ctx| {//             assert_eq!(//                 editor.selections(ctx),//                 vec1![SelectionOffsets {//                     head: CharOffset::from(14),//                     tail: CharOffset::from(14)//                 }]//             );//             assert_eq!(editor.content.as_ref(ctx).text(), "fn test(\n    \n) {}");//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.cursor_at(CharOffset::from(18), ctx);//         }); //         editor.update(&mut app, |editor, ctx| {//             editor.enter(ctx);//         }); //         // Bracket expansion in brackets.//         editor.read(&app, |editor, ctx| {//             assert_eq!(//                 editor.selections(ctx),//                 vec1![SelectionOffsets {//                     head: CharOffset::from(23),//                     tail: CharOffset::from(23)//                 }]//             );//             assert_eq!(//                 editor.content.as_ref(ctx).text(),//                 "fn test(\n    \n) {\n    \n}"//             );//         });//     })// } #[test]fn test_move_by_word() {    App::test((), |mut app| async move {        initialize_deps(&mut app);        let editor = mock_model(            &mut app,            "fn test() {}\nfn test2() {}",

Lines 1–500 of 1241.