@@ -28,18 +28,18 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);
let step = if action.step { 1 } else { 0 };
- vim.increment(count as i32, step, cx)
+ vim.increment(count as i64, step, cx)
});
Vim::action(editor, cx, |vim, action: &Decrement, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1);
let step = if action.step { -1 } else { 0 };
- vim.increment(-(count as i32), step, cx)
+ vim.increment(-(count as i64), step, cx)
});
}
impl Vim {
- fn increment(&mut self, mut delta: i32, step: i32, cx: &mut ViewContext<Self>) {
+ fn increment(&mut self, mut delta: i64, step: i32, cx: &mut ViewContext<Self>) {
self.store_visual_marks(cx);
self.update_editor(cx, |vim, editor, cx| {
let mut edits = Vec::new();
@@ -60,23 +60,14 @@ impl Vim {
};
if let Some((range, num, radix)) = find_number(&snapshot, start) {
- if let Ok(val) = i32::from_str_radix(&num, radix) {
- let result = val + delta;
- delta += step;
- let replace = match radix {
- 10 => format!("{}", result),
- 16 => {
- if num.to_ascii_lowercase() == num {
- format!("{:x}", result)
- } else {
- format!("{:X}", result)
- }
- }
- 2 => format!("{:b}", result),
- _ => unreachable!(),
- };
- edits.push((range.clone(), replace));
- }
+ let replace = match radix {
+ 10 => increment_decimal_string(&num, delta),
+ 16 => increment_hex_string(&num, delta),
+ 2 => increment_binary_string(&num, delta),
+ _ => unreachable!(),
+ };
+ delta += step as i64;
+ edits.push((range.clone(), replace));
if selection.is_empty() {
new_anchors.push((false, snapshot.anchor_after(range.end)))
}
@@ -107,6 +98,70 @@ impl Vim {
}
}
+fn increment_decimal_string(mut num: &str, mut delta: i64) -> String {
+ let mut negative = false;
+ if num.chars().next() == Some('-') {
+ negative = true;
+ delta = 0 - delta;
+ num = &num[1..];
+ }
+ let result = if let Ok(value) = u64::from_str_radix(num, 10) {
+ let wrapped = value.wrapping_add_signed(delta);
+ if delta < 0 && wrapped > value {
+ negative = !negative;
+ (u64::MAX - wrapped).wrapping_add(1)
+ } else if delta > 0 && wrapped < value {
+ negative = !negative;
+ u64::MAX - wrapped
+ } else {
+ wrapped
+ }
+ } else {
+ u64::MAX
+ };
+
+ if result == 0 || !negative {
+ format!("{}", result)
+ } else {
+ format!("-{}", result)
+ }
+}
+
+fn increment_hex_string(num: &str, delta: i64) -> String {
+ let result = if let Ok(val) = u64::from_str_radix(&num, 16) {
+ val.wrapping_add_signed(delta)
+ } else {
+ u64::MAX
+ };
+ if should_use_lowercase(num) {
+ format!("{:0width$x}", result, width = num.len())
+ } else {
+ format!("{:0width$X}", result, width = num.len())
+ }
+}
+
+fn should_use_lowercase(num: &str) -> bool {
+ let mut use_uppercase = false;
+ for ch in num.chars() {
+ if ch.is_ascii_lowercase() {
+ return true;
+ }
+ if ch.is_ascii_uppercase() {
+ use_uppercase = true;
+ }
+ }
+ !use_uppercase
+}
+
+fn increment_binary_string(num: &str, delta: i64) -> String {
+ let result = if let Ok(val) = u64::from_str_radix(&num, 2) {
+ val.wrapping_add_signed(delta)
+ } else {
+ u64::MAX
+ };
+ format!("{:0width$b}", result, width = num.len())
+}
+
fn find_number(
snapshot: &MultiBufferSnapshot,
start: Point,
@@ -114,10 +169,10 @@ fn find_number(
let mut offset = start.to_offset(snapshot);
let ch0 = snapshot.chars_at(offset).next();
- if ch0.as_ref().is_some_and(char::is_ascii_digit) || matches!(ch0, Some('-' | 'b' | 'x')) {
+ if ch0.as_ref().is_some_and(char::is_ascii_hexdigit) || matches!(ch0, Some('-' | 'b' | 'x')) {
// go backwards to the start of any number the selection is within
for ch in snapshot.reversed_chars_at(offset) {
- if ch.is_ascii_digit() || ch == '-' || ch == 'b' || ch == 'x' {
+ if ch.is_ascii_hexdigit() || ch == '-' || ch == 'b' || ch == 'x' {
offset -= ch.len_utf8();
continue;
}
@@ -158,6 +213,8 @@ fn find_number(
begin = Some(offset);
}
num.push(ch);
+ println!("pushing {}", ch);
+ println!();
} else if begin.is_some() {
end = Some(offset);
break;
@@ -250,6 +307,146 @@ mod test {
"});
}
+ #[gpui::test]
+ async fn test_increment_sign_change(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ ˇ0
+ "})
+ .await;
+ cx.simulate_shared_keystrokes("ctrl-x").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ -ˇ1
+ "});
+ cx.simulate_shared_keystrokes("2 ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ ˇ1
+ "});
+ }
+
+ #[gpui::test]
+ async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ 0b111111111111111111111111111111111111111111111111111111111111111111111ˇ1
+ "})
+ .await;
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0b000000000000000000000000000000000000000000000000000000000000000000000ˇ0
+ "});
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0b000000000000000000000000000000000000000000000000000000000000000000000ˇ1
+ "});
+ cx.simulate_shared_keystrokes("2 ctrl-x").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1
+ "});
+ }
+
+ #[gpui::test]
+ async fn test_increment_hex_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ 0xfffffffffffffffffffˇf
+ "})
+ .await;
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0x0000fffffffffffffffˇf
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0x0000000000000000000ˇ0
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0x0000000000000000000ˇ1
+ "});
+ cx.simulate_shared_keystrokes("2 ctrl-x").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0x0000fffffffffffffffˇf
+ "});
+ }
+
+ #[gpui::test]
+ async fn test_increment_wrapping(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ 1844674407370955161ˇ9
+ "})
+ .await;
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 1844674407370955161ˇ5
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ -1844674407370955161ˇ5
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ -1844674407370955161ˇ4
+ "});
+ cx.simulate_shared_keystrokes("3 ctrl-x").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 1844674407370955161ˇ4
+ "});
+ cx.simulate_shared_keystrokes("2 ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ -1844674407370955161ˇ5
+ "});
+ }
+
+ #[gpui::test]
+ async fn test_increment_inline(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ inline0x3ˇ9u32
+ "})
+ .await;
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ inline0x3ˇau32
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ inline0x3ˇbu32
+ "});
+ cx.simulate_shared_keystrokes("l l l ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ inline0x3bu3ˇ3
+ "});
+ }
+
+ #[gpui::test]
+ async fn test_increment_hex_casing(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state(indoc! {"
+ 0xFˇa
+ "})
+ .await;
+
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0xfˇb
+ "});
+ cx.simulate_shared_keystrokes("ctrl-a").await;
+ cx.shared_state().await.assert_eq(indoc! {"
+ 0xfˇc
+ "});
+ }
+
#[gpui::test]
async fn test_increment_radix(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;