Detailed changes
@@ -335,6 +335,9 @@ impl Chunk {
let mut point = Point::new(0, 0);
for ch in self.0.chars() {
if point >= target {
+ if point > target {
+ panic!("point {:?} is inside of character {:?}", target, ch);
+ }
break;
}
@@ -346,8 +349,6 @@ impl Chunk {
}
offset += ch.len_utf8();
}
-
- assert_eq!(point, target);
offset
}
}
@@ -2358,7 +2358,11 @@ impl workspace::ItemView for BufferView {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{editor::Point, settings, test::sample_text};
+ use crate::{
+ editor::Point,
+ settings,
+ test::{multibyte_sample_text, sample_text},
+ };
use unindent::Unindent;
#[gpui::test]
@@ -2715,6 +2719,70 @@ mod tests {
});
}
+ #[gpui::test]
+ fn test_move_cursor_multibyte(app: &mut gpui::MutableAppContext) {
+ let buffer = app.add_model(|ctx| Buffer::new(0, "βββββ\nabcde\nαβγδΡ\n", ctx));
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
+
+ assert_eq!('β'.len_utf8(), 3);
+ assert_eq!('Ξ±'.len_utf8(), 2);
+
+ view.update(app, |view, ctx| {
+ view.fold_ranges(
+ vec![
+ Point::new(0, 6)..Point::new(0, 12),
+ Point::new(1, 2)..Point::new(1, 4),
+ Point::new(2, 4)..Point::new(2, 8),
+ ],
+ ctx,
+ );
+ assert_eq!(view.text(ctx.as_ref()), "βββ¦β\nabβ¦e\nΞ±Ξ²β¦Ξ΅\n");
+
+ view.move_right(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3)]
+ );
+
+ view.move_down(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+ );
+
+ view.move_right(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2)]
+ );
+
+ view.move_down(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+ );
+
+ view.move_left(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+ );
+
+ view.move_up(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+ );
+
+ view.move_up(&(), ctx);
+ assert_eq!(
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3)]
+ );
+ });
+ }
+
#[gpui::test]
fn test_beginning_end_of_line(app: &mut gpui::MutableAppContext) {
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx));
@@ -92,8 +92,7 @@ impl DisplayMap {
let mut is_blank = true;
for c in self
.snapshot(ctx)
- .chunks_at(DisplayPoint::new(display_row, 0), ctx)
- .flat_map(str::chars)
+ .chars_at(DisplayPoint::new(display_row, 0), ctx)
{
if c == ' ' {
indent += 1;
@@ -165,6 +164,32 @@ impl DisplayMapSnapshot {
self.chunks_at(point, app).flat_map(str::chars)
}
+ pub fn column_to_chars(&self, display_row: u32, target: u32, ctx: &AppContext) -> u32 {
+ let mut count = 0;
+ let mut column = 0;
+ for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
+ count += 1;
+ column += c.len_utf8() as u32;
+ if column >= target {
+ break;
+ }
+ }
+ count
+ }
+
+ pub fn column_from_chars(&self, display_row: u32, char_count: u32, ctx: &AppContext) -> u32 {
+ let mut count = 0;
+ let mut column = 0;
+ for c in self.chars_at(DisplayPoint::new(display_row, 0), ctx) {
+ count += 1;
+ column += c.len_utf8() as u32;
+ if count >= char_count {
+ break;
+ }
+ }
+ column
+ }
+
fn expand_tabs(&self, mut point: DisplayPoint, ctx: &AppContext) -> DisplayPoint {
let chars = self
.folds_snapshot
@@ -187,7 +212,6 @@ impl DisplayMapSnapshot {
let (collapsed, expanded_char_column, to_next_stop) =
collapse_tabs(chars, expanded, bias, self.tab_size);
*point.column_mut() = collapsed as u32;
-
(point, expanded_char_column, to_next_stop)
}
}
@@ -360,6 +384,10 @@ pub fn collapse_tabs(
expanded_bytes += c.len_utf8();
}
collapsed_bytes += c.len_utf8();
+
+ if expanded_bytes > column {
+ panic!("column {} is inside of character {:?}", column, c);
+ }
}
(collapsed_bytes, expanded_chars, 0)
}
@@ -16,7 +16,12 @@ pub fn left(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Resu
pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result<DisplayPoint> {
let max_column = map.line_len(point.row(), app);
if point.column() < max_column {
- *point.column_mut() += 1;
+ *point.column_mut() += map
+ .snapshot(app)
+ .chars_at(point, app)
+ .next()
+ .unwrap()
+ .len_utf8() as u32;
} else if point.row() < map.max_point(app).row() {
*point.row_mut() += 1;
*point.column_mut() = 0;
@@ -35,9 +40,13 @@ pub fn up(
} else {
point.column()
};
+
+ let map = map.snapshot(app);
+ let char_column = map.column_to_chars(point.row(), goal_column, app);
+
if point.row() > 0 {
*point.row_mut() -= 1;
- *point.column_mut() = cmp::min(goal_column, map.line_len(point.row(), app));
+ *point.column_mut() = map.column_from_chars(point.row(), char_column, app);
} else {
point = DisplayPoint::new(0, 0);
}
@@ -56,10 +65,14 @@ pub fn down(
} else {
point.column()
};
+
let max_point = map.max_point(app);
+ let map = map.snapshot(app);
+ let char_column = map.column_to_chars(point.row(), goal_column, app);
+
if point.row() < max_point.row() {
*point.row_mut() += 1;
- *point.column_mut() = cmp::min(goal_column, map.line_len(point.row(), app))
+ *point.column_mut() = map.column_from_chars(point.row(), char_column, app);
} else {
point = max_point;
}