use super::*;use crate::{ scroll::scroll_amount::ScrollAmount, test::{ assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, select_ranges, }, JoinLines,};use futures::StreamExt;use gpui::{ div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions,};use indoc::indoc;use language::{ language_settings::{ AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings, }, BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, ParsedMarkdown, Point,};use language_settings::{Formatter, FormatterList, IndentGuideSettings};use multi_buffer::MultiBufferIndentGuide;use parking_lot::Mutex;use project::FakeFs;use project::{ lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT, project_settings::{LspSettings, ProjectSettings},};use serde_json::{self, json};use std::sync::atomic::AtomicUsize;use std::sync::atomic::{self, AtomicBool};use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};use test::editor_lsp_test_context::rust_lang;use unindent::Unindent;use util::{ assert_set_eq, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},};use workspace::{ item::{FollowEvent, FollowableItem, Item, ItemHandle}, NavigationEntry, ViewId,}; #[gpui::test]fn test_edit_events(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new_model(|cx| { let mut buffer = language::Buffer::local("123456", cx); buffer.set_group_interval(Duration::from_secs(1)); buffer }); let events = Rc::new(RefCell::new(Vec::new())); let editor1 = cx.add_window({ let events = events.clone(); |cx| { let view = cx.view().clone(); cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event { EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")), EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")), _ => {} }) .detach(); Editor::for_buffer(buffer.clone(), None, cx) } }); let editor2 = cx.add_window({ let events = events.clone(); |cx| { cx.subscribe( &cx.view().clone(), move |_, _, event: &EditorEvent, _| match event { EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")), EditorEvent::BufferEdited => { events.borrow_mut().push(("editor2", "buffer edited")) } _ => {} }, ) .detach(); Editor::for_buffer(buffer.clone(), None, cx) } }); assert_eq!(mem::take(&mut *events.borrow_mut()), []); // Mutating editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.insert("X", cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Mutating editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Undoing on editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Redoing on editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Undoing on editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Redoing on editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // No event is emitted when the mutation is a no-op. _ = editor2.update(cx, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([0..0])); editor.backspace(&Backspace, cx); }); assert_eq!(mem::take(&mut *events.borrow_mut()), []);} #[gpui::test]fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { init_test(cx, |_| {}); let mut now = Instant::now(); let group_interval = Duration::from_millis(1); let buffer = cx.new_model(|cx| { let mut buf = language::Buffer::local("123456", cx); buf.set_group_interval(group_interval); buf }); let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx)); _ = editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); editor.change_selections(None, cx, |s| s.select_ranges([2..4])); editor.insert("cd", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cd56"); assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, cx); editor.change_selections(None, cx, |s| s.select_ranges([4..5])); editor.insert("e", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); editor.change_selections(None, cx, |s| s.select_ranges([2..2])); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { buffer.start_transaction_at(now, cx); buffer.edit([(0..1, "a")], None, cx); buffer.edit([(1..1, "b")], None, cx); buffer.end_transaction_at(now, cx); }); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![3..3]); // Last transaction happened past the group interval in a different editor. // Undo it individually and don't restore selections. editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![2..2]); // First two transactions happened within the group interval in this editor. // Undo them together and restore selections. editor.undo(&Undo, cx); editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op. assert_eq!(editor.text(cx), "123456"); assert_eq!(editor.selections.ranges(cx), vec![0..0]); // Redo the first two transactions together. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); // Redo the last transaction on its own. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![6..6]); // Test empty transactions. editor.start_transaction_at(now, cx); editor.end_transaction_at(now, cx); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "12cde6"); });} #[gpui::test]fn test_ime_composition(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new_model(|cx| { let mut buffer = language::Buffer::local("abcde", cx); // Ensure automatic grouping doesn't occur. buffer.set_group_interval(Duration::ZERO); buffer }); let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); cx.add_window(|cx| { let mut editor = build_editor(buffer.clone(), cx); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx); editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx); assert_eq!(editor.text(cx), "äbcde"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) ); // Finalize IME composition. editor.replace_text_in_range(None, "ā", cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // IME composition edits are grouped and are undone/redone at once. editor.undo(&Default::default(), cx); assert_eq!(editor.text(cx), "abcde"); assert_eq!(editor.marked_text_ranges(cx), None); editor.redo(&Default::default(), cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) ); // Undoing during an IME composition cancels it. editor.undo(&Default::default(), cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with an invalid marked range, ensuring it gets clipped. editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx); assert_eq!(editor.text(cx), "ābcdè"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(4)..OffsetUtf16(5)]) ); // Finalize IME composition with an invalid replacement range, ensuring it gets clipped. editor.replace_text_in_range(Some(4..999), "ę", cx); assert_eq!(editor.text(cx), "ābcdę"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with multiple cursors. editor.change_selections(None, cx, |s| { s.select_ranges([ OffsetUtf16(1)..OffsetUtf16(1), OffsetUtf16(3)..OffsetUtf16(3), OffsetUtf16(5)..OffsetUtf16(5), ]) }); editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx); assert_eq!(editor.text(cx), "XYZbXYZdXYZ"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![ OffsetUtf16(0)..OffsetUtf16(3), OffsetUtf16(4)..OffsetUtf16(7), OffsetUtf16(8)..OffsetUtf16(11) ]) ); // Ensure the newly-marked range gets treated as relative to the previously-marked ranges. editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx); assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![ OffsetUtf16(1)..OffsetUtf16(2), OffsetUtf16(5)..OffsetUtf16(6), OffsetUtf16(9)..OffsetUtf16(10) ]) ); // Finalize IME composition with multiple cursors. editor.replace_text_in_range(Some(9..10), "2", cx); assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z"); assert_eq!(editor.marked_text_ranges(cx), None); editor });} #[gpui::test]fn test_selection_with_mouse(cx: &mut TestAppContext) { init_test(cx, |_| {}); let editor = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, cx) }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)] ); _ = editor.update(cx, |view, cx| { view.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)] ); _ = editor.update(cx, |view, cx| { view.update_selection( DisplayPoint::new(DisplayRow(1), 1), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |view, cx| { view.end_selection(cx); view.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx); view.update_selection( DisplayPoint::new(DisplayRow(0), 0), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [ DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0) ] ); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)] );} #[gpui::test]fn test_multiple_cursor_removal(cx: &mut TestAppContext) { init_test(cx, |_| {}); let editor = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, cx) }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [ DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1), DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2) ] ); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)] );} #[gpui::test]fn test_canceling_pending_selection(cx: &mut TestAppContext) { init_test(cx, |_| {}); Lines 1–500 of 14122. Load more
use super::*; use crate::{ scroll::scroll_amount::ScrollAmount, test::{ assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext, select_ranges, }, JoinLines, }; use futures::StreamExt; use gpui::{ div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions, }; use indoc::indoc; use language::{ language_settings::{ AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings, }, BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, ParsedMarkdown, Point, }; use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use multi_buffer::MultiBufferIndentGuide; use parking_lot::Mutex; use project::FakeFs; use project::{ lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT, project_settings::{LspSettings, ProjectSettings}, }; use serde_json::{self, json}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::{self, AtomicBool}; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use test::editor_lsp_test_context::rust_lang; use unindent::Unindent; use util::{ assert_set_eq, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, }; use workspace::{ item::{FollowEvent, FollowableItem, Item, ItemHandle}, NavigationEntry, ViewId, }; #[gpui::test] fn test_edit_events(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new_model(|cx| { let mut buffer = language::Buffer::local("123456", cx); buffer.set_group_interval(Duration::from_secs(1)); buffer }); let events = Rc::new(RefCell::new(Vec::new())); let editor1 = cx.add_window({ let events = events.clone(); |cx| { let view = cx.view().clone(); cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event { EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")), EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")), _ => {} }) .detach(); Editor::for_buffer(buffer.clone(), None, cx) } }); let editor2 = cx.add_window({ let events = events.clone(); |cx| { cx.subscribe( &cx.view().clone(), move |_, _, event: &EditorEvent, _| match event { EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")), EditorEvent::BufferEdited => { events.borrow_mut().push(("editor2", "buffer edited")) } _ => {} }, ) .detach(); Editor::for_buffer(buffer.clone(), None, cx) } }); assert_eq!(mem::take(&mut *events.borrow_mut()), []); // Mutating editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.insert("X", cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Mutating editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Undoing on editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Redoing on editor 1 will emit an `Edited` event only for that editor. _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor1", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Undoing on editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // Redoing on editor 2 will emit an `Edited` event only for that editor. _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx)); assert_eq!( mem::take(&mut *events.borrow_mut()), [ ("editor2", "edited"), ("editor1", "buffer edited"), ("editor2", "buffer edited"), ] ); // No event is emitted when the mutation is a no-op. _ = editor2.update(cx, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([0..0])); editor.backspace(&Backspace, cx); }); assert_eq!(mem::take(&mut *events.borrow_mut()), []); } #[gpui::test] fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) { init_test(cx, |_| {}); let mut now = Instant::now(); let group_interval = Duration::from_millis(1); let buffer = cx.new_model(|cx| { let mut buf = language::Buffer::local("123456", cx); buf.set_group_interval(group_interval); buf }); let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx)); _ = editor.update(cx, |editor, cx| { editor.start_transaction_at(now, cx); editor.change_selections(None, cx, |s| s.select_ranges([2..4])); editor.insert("cd", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cd56"); assert_eq!(editor.selections.ranges(cx), vec![4..4]); editor.start_transaction_at(now, cx); editor.change_selections(None, cx, |s| s.select_ranges([4..5])); editor.insert("e", cx); editor.end_transaction_at(now, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); now += group_interval + Duration::from_millis(1); editor.change_selections(None, cx, |s| s.select_ranges([2..2])); // Simulate an edit in another editor buffer.update(cx, |buffer, cx| { buffer.start_transaction_at(now, cx); buffer.edit([(0..1, "a")], None, cx); buffer.edit([(1..1, "b")], None, cx); buffer.end_transaction_at(now, cx); }); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![3..3]); // Last transaction happened past the group interval in a different editor. // Undo it individually and don't restore selections. editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![2..2]); // First two transactions happened within the group interval in this editor. // Undo them together and restore selections. editor.undo(&Undo, cx); editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op. assert_eq!(editor.text(cx), "123456"); assert_eq!(editor.selections.ranges(cx), vec![0..0]); // Redo the first two transactions together. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "12cde6"); assert_eq!(editor.selections.ranges(cx), vec![5..5]); // Redo the last transaction on its own. editor.redo(&Redo, cx); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![6..6]); // Test empty transactions. editor.start_transaction_at(now, cx); editor.end_transaction_at(now, cx); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "12cde6"); }); } #[gpui::test] fn test_ime_composition(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new_model(|cx| { let mut buffer = language::Buffer::local("abcde", cx); // Ensure automatic grouping doesn't occur. buffer.set_group_interval(Duration::ZERO); buffer }); let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); cx.add_window(|cx| { let mut editor = build_editor(buffer.clone(), cx); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx); editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx); assert_eq!(editor.text(cx), "äbcde"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) ); // Finalize IME composition. editor.replace_text_in_range(None, "ā", cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // IME composition edits are grouped and are undone/redone at once. editor.undo(&Default::default(), cx); assert_eq!(editor.text(cx), "abcde"); assert_eq!(editor.marked_text_ranges(cx), None); editor.redo(&Default::default(), cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(0)..OffsetUtf16(1)]) ); // Undoing during an IME composition cancels it. editor.undo(&Default::default(), cx); assert_eq!(editor.text(cx), "ābcde"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with an invalid marked range, ensuring it gets clipped. editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx); assert_eq!(editor.text(cx), "ābcdè"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![OffsetUtf16(4)..OffsetUtf16(5)]) ); // Finalize IME composition with an invalid replacement range, ensuring it gets clipped. editor.replace_text_in_range(Some(4..999), "ę", cx); assert_eq!(editor.text(cx), "ābcdę"); assert_eq!(editor.marked_text_ranges(cx), None); // Start a new IME composition with multiple cursors. editor.change_selections(None, cx, |s| { s.select_ranges([ OffsetUtf16(1)..OffsetUtf16(1), OffsetUtf16(3)..OffsetUtf16(3), OffsetUtf16(5)..OffsetUtf16(5), ]) }); editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx); assert_eq!(editor.text(cx), "XYZbXYZdXYZ"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![ OffsetUtf16(0)..OffsetUtf16(3), OffsetUtf16(4)..OffsetUtf16(7), OffsetUtf16(8)..OffsetUtf16(11) ]) ); // Ensure the newly-marked range gets treated as relative to the previously-marked ranges. editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx); assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z"); assert_eq!( editor.marked_text_ranges(cx), Some(vec![ OffsetUtf16(1)..OffsetUtf16(2), OffsetUtf16(5)..OffsetUtf16(6), OffsetUtf16(9)..OffsetUtf16(10) ]) ); // Finalize IME composition with multiple cursors. editor.replace_text_in_range(Some(9..10), "2", cx); assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z"); assert_eq!(editor.marked_text_ranges(cx), None); editor }); } #[gpui::test] fn test_selection_with_mouse(cx: &mut TestAppContext) { init_test(cx, |_| {}); let editor = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, cx) }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)] ); _ = editor.update(cx, |view, cx| { view.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)] ); _ = editor.update(cx, |view, cx| { view.update_selection( DisplayPoint::new(DisplayRow(1), 1), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |view, cx| { view.end_selection(cx); view.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx); view.update_selection( DisplayPoint::new(DisplayRow(0), 0), 0, gpui::Point::<f32>::default(), cx, ); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [ DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1), DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0) ] ); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)] ); } #[gpui::test] fn test_multiple_cursor_removal(cx: &mut TestAppContext) { init_test(cx, |_| {}); let editor = cx.add_window(|cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, cx) }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [ DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1), DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2) ] ); _ = editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx); }); _ = editor.update(cx, |view, cx| { view.end_selection(cx); }); assert_eq!( editor .update(cx, |view, cx| view.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)] ); } #[gpui::test] fn test_canceling_pending_selection(cx: &mut TestAppContext) { init_test(cx, |_| {});