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 buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind};use futures::StreamExt;use gpui::{ div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions,};use indoc::indoc;use language::{ language_settings::{ AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings, }, BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,};use language_settings::{Formatter, FormatterList, IndentGuideSettings};use multi_buffer::{IndentGuide, PathKey};use parking_lot::Mutex;use pretty_assertions::{assert_eq, assert_ne};use project::project_settings::{LspSettings, ProjectSettings};use project::FakeFs;use serde_json::{self, json};use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};use std::{ iter, sync::atomic::{self, AtomicUsize},};use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};use text::ToPoint as _;use unindent::Unindent;use util::{ assert_set_eq, path, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, uri,};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(|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(); |window, cx| { let entity = cx.entity().clone(); cx.subscribe_in( &entity, window, 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, window, cx) } }); let editor2 = cx.add_window({ let events = events.clone(); |window, cx| { cx.subscribe_in( &cx.entity().clone(), window, 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, window, 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, window, cx| editor.insert("X", window, 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, window, cx| editor.delete(&Delete, window, 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, window, cx| editor.undo(&Undo, window, 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, window, cx| editor.redo(&Redo, window, 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, window, cx| editor.undo(&Undo, window, 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, window, cx| editor.redo(&Redo, window, 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, window, cx| { editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); editor.backspace(&Backspace, window, 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(|cx| { let mut buf = language::Buffer::local("123456", cx); buf.set_group_interval(group_interval); buf }); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx)); _ = editor.update(cx, |editor, window, cx| { editor.start_transaction_at(now, window, cx); editor.change_selections(None, window, cx, |s| s.select_ranges([2..4])); editor.insert("cd", window, 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, window, cx); editor.change_selections(None, window, cx, |s| s.select_ranges([4..5])); editor.insert("e", window, 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, window, 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, window, 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, window, cx); editor.undo(&Undo, window, 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, window, 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, window, cx); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![6..6]); // Test empty transactions. editor.start_transaction_at(now, window, cx); editor.end_transaction_at(now, cx); editor.undo(&Undo, window, cx); assert_eq!(editor.text(cx), "12cde6"); });} #[gpui::test]fn test_ime_composition(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new(|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(|cx| MultiBuffer::singleton(buffer, cx)); cx.add_window(|window, cx| { let mut editor = build_editor(buffer.clone(), window, cx); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx); editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx); editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, 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, "ā", window, 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(), window, cx); assert_eq!(editor.text(cx), "abcde"); assert_eq!(editor.marked_text_ranges(cx), None); editor.redo(&Default::default(), window, 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, window, 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(), window, 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, window, 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), "ę", window, 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, window, 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, window, 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, window, 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", window, 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(|window, cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)] ); _ = editor.update(cx, |editor, window, cx| { editor.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)] ); _ = editor.update(cx, |editor, window, cx| { editor.update_selection( DisplayPoint::new(DisplayRow(1), 1), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); editor.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx); editor.update_selection( DisplayPoint::new(DisplayRow(0), 0), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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, |editor, window, cx| { editor.end_selection(window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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(|window, cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx);Lines 1–500 of 16714. 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 buffer_diff::{BufferDiff, DiffHunkStatus, DiffHunkStatusKind}; use futures::StreamExt; use gpui::{ div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds, WindowOptions, }; use indoc::indoc; use language::{ language_settings::{ AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings, }, BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point, }; use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use multi_buffer::{IndentGuide, PathKey}; use parking_lot::Mutex; use pretty_assertions::{assert_eq, assert_ne}; use project::project_settings::{LspSettings, ProjectSettings}; use project::FakeFs; use serde_json::{self, json}; use std::{cell::RefCell, future::Future, rc::Rc, time::Instant}; use std::{ iter, sync::atomic::{self, AtomicUsize}, }; use test::{build_editor_with_project, editor_lsp_test_context::rust_lang}; use text::ToPoint as _; use unindent::Unindent; use util::{ assert_set_eq, path, test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker}, uri, }; 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(|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(); |window, cx| { let entity = cx.entity().clone(); cx.subscribe_in( &entity, window, 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, window, cx) } }); let editor2 = cx.add_window({ let events = events.clone(); |window, cx| { cx.subscribe_in( &cx.entity().clone(), window, 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, window, 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, window, cx| editor.insert("X", window, 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, window, cx| editor.delete(&Delete, window, 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, window, cx| editor.undo(&Undo, window, 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, window, cx| editor.redo(&Redo, window, 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, window, cx| editor.undo(&Undo, window, 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, window, cx| editor.redo(&Redo, window, 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, window, cx| { editor.change_selections(None, window, cx, |s| s.select_ranges([0..0])); editor.backspace(&Backspace, window, 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(|cx| { let mut buf = language::Buffer::local("123456", cx); buf.set_group_interval(group_interval); buf }); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx)); _ = editor.update(cx, |editor, window, cx| { editor.start_transaction_at(now, window, cx); editor.change_selections(None, window, cx, |s| s.select_ranges([2..4])); editor.insert("cd", window, 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, window, cx); editor.change_selections(None, window, cx, |s| s.select_ranges([4..5])); editor.insert("e", window, 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, window, 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, window, 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, window, cx); editor.undo(&Undo, window, 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, window, 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, window, cx); assert_eq!(editor.text(cx), "ab2cde6"); assert_eq!(editor.selections.ranges(cx), vec![6..6]); // Test empty transactions. editor.start_transaction_at(now, window, cx); editor.end_transaction_at(now, cx); editor.undo(&Undo, window, cx); assert_eq!(editor.text(cx), "12cde6"); }); } #[gpui::test] fn test_ime_composition(cx: &mut TestAppContext) { init_test(cx, |_| {}); let buffer = cx.new(|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(|cx| MultiBuffer::singleton(buffer, cx)); cx.add_window(|window, cx| { let mut editor = build_editor(buffer.clone(), window, cx); // Start a new IME composition. editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx); editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx); editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, 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, "ā", window, 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(), window, cx); assert_eq!(editor.text(cx), "abcde"); assert_eq!(editor.marked_text_ranges(cx), None); editor.redo(&Default::default(), window, 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, window, 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(), window, 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, window, 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), "ę", window, 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, window, 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, window, 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, window, 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", window, 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(|window, cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)] ); _ = editor.update(cx, |editor, window, cx| { editor.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)] ); _ = editor.update(cx, |editor, window, cx| { editor.update_selection( DisplayPoint::new(DisplayRow(1), 1), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); editor.update_selection( DisplayPoint::new(DisplayRow(3), 3), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.selections.display_ranges(cx)) .unwrap(), [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)] ); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx); editor.update_selection( DisplayPoint::new(DisplayRow(0), 0), 0, gpui::Point::<f32>::default(), window, cx, ); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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, |editor, window, cx| { editor.end_selection(window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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(|window, cx| { let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx); build_editor(buffer, window, cx) }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx); }); assert_eq!( editor .update(cx, |editor, _, cx| editor.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, |editor, window, cx| { editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx); }); _ = editor.update(cx, |editor, window, cx| { editor.end_selection(window, cx);