diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 0f14af3bd1..f53543ac6b 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -12,11 +12,11 @@ use ui::{ use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{item::ItemHandle, StatusItemView, Workspace}; -#[derive(Copy, Clone, Default, PartialOrd, PartialEq)] -struct SelectionStats { - lines: usize, - characters: usize, - selections: usize, +#[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)] +pub(crate) struct SelectionStats { + pub lines: usize, + pub characters: usize, + pub selections: usize, } pub struct CursorPosition { @@ -44,7 +44,10 @@ impl CursorPosition { self.selected_count.selections = editor.selections.count(); let mut last_selection: Option> = None; for selection in editor.selections.all::(cx) { - self.selected_count.characters += selection.end - selection.start; + self.selected_count.characters += buffer + .text_for_range(selection.start..selection.end) + .map(|t| t.chars().count()) + .sum::(); if last_selection .as_ref() .map_or(true, |last_selection| selection.id > last_selection.id) @@ -106,6 +109,11 @@ impl CursorPosition { } text.push(')'); } + + #[cfg(test)] + pub(crate) fn selection_stats(&self) -> &SelectionStats { + &self.selected_count + } } impl Render for CursorPosition { diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 155f38d808..4f3e6194a0 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -221,6 +221,8 @@ impl Render for GoToLine { #[cfg(test)] mod tests { use super::*; + use cursor_position::{CursorPosition, SelectionStats}; + use editor::actions::SelectAll; use gpui::{TestAppContext, VisualTestContext}; use indoc::indoc; use project::{FakeFs, Project}; @@ -335,6 +337,83 @@ mod tests { assert_single_caret_at_row(&editor, expected_highlighted_row, cx); } + #[gpui::test] + async fn test_unicode_characters_selection(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + "/dir", + json!({ + "a.rs": "ēlo" + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); + workspace.update(cx, |workspace, cx| { + let cursor_position = cx.new_view(|_| CursorPosition::new(workspace)); + workspace.status_bar().update(cx, |status_bar, cx| { + status_bar.add_right_item(cursor_position, cx); + }); + }); + + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().update(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + let _buffer = project + .update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx)) + .await + .unwrap(); + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "a.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + workspace.update(cx, |workspace, cx| { + assert_eq!( + &SelectionStats { + lines: 0, + characters: 0, + selections: 1, + }, + workspace + .status_bar() + .read(cx) + .item_of_type::() + .expect("missing cursor position item") + .read(cx) + .selection_stats(), + "No selections should be initially" + ); + }); + editor.update(cx, |editor, cx| editor.select_all(&SelectAll, cx)); + workspace.update(cx, |workspace, cx| { + assert_eq!( + &SelectionStats { + lines: 1, + characters: 3, + selections: 1, + }, + workspace + .status_bar() + .read(cx) + .item_of_type::() + .expect("missing cursor position item") + .read(cx) + .selection_stats(), + "After selecting a text with multibyte unicode characters, the character count should be correct" + ); + }); + } + fn open_go_to_line_view( workspace: &View, cx: &mut VisualTestContext,