@@ -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<Selection<usize>> = None;
for selection in editor.selections.all::<usize>(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::<usize>();
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 {
@@ -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::<Editor>()
+ .unwrap();
+
+ workspace.update(cx, |workspace, cx| {
+ assert_eq!(
+ &SelectionStats {
+ lines: 0,
+ characters: 0,
+ selections: 1,
+ },
+ workspace
+ .status_bar()
+ .read(cx)
+ .item_of_type::<CursorPosition>()
+ .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::<CursorPosition>()
+ .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<Workspace>,
cx: &mut VisualTestContext,