vim: Fix increment/decrement command (#17644)
Improving vim increment and decrement command. Closes: #16672 ## Release Notes: - vim: Improved edge-case handling for ctrl-a/ctrl-x --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
43e005e936
commit
c3f47b8040
7 changed files with 273 additions and 22 deletions
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{"Put":{"state":"0b111111111111111111111111111111111111111111111111111111111111111111111ˇ1\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n", "mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0b000000000000000000000000000000000000000000000000000000000000000000000ˇ0\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0b000000000000000000000000000000000000000000000000000000000000000000000ˇ1\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1\n", "mode":"Normal"}}
|
5
crates/vim/test_data/test_increment_hex_casing.json
Normal file
5
crates/vim/test_data/test_increment_hex_casing.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{"Put":{"state":"0xFˇa\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0xfˇb\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0xfˇc\n","mode":"Normal"}}
|
|
@ -0,0 +1,10 @@
|
|||
{"Put":{"state":"0xfffffffffffffffffffˇf\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0x0000fffffffffffffffˇf\n", "mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0x0000000000000000000ˇ0\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"0x0000000000000000000ˇ1\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"0x0000fffffffffffffffˇf\n", "mode":"Normal"}}
|
10
crates/vim/test_data/test_increment_inline.json
Normal file
10
crates/vim/test_data/test_increment_inline.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{"Put":{"state":"inline0x3ˇ9u32\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"inline0x3ˇau32\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"inline0x3ˇbu32\n", "mode":"Normal"}}
|
||||
{"Key":"l"}
|
||||
{"Key":"l"}
|
||||
{"Key":"l"}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"inline0x3bu3ˇ3\n", "mode":"Normal"}}
|
6
crates/vim/test_data/test_increment_sign_change.json
Normal file
6
crates/vim/test_data/test_increment_sign_change.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{"Put":{"state":"ˇ0\n"}}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"-ˇ1\n","mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"ˇ1\n", "mode":"Normal"}}
|
13
crates/vim/test_data/test_increment_wrapping.json
Normal file
13
crates/vim/test_data/test_increment_wrapping.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{"Put":{"state":"1844674407370955161ˇ9\n"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"1844674407370955161ˇ5\n","mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"-1844674407370955161ˇ5\n", "mode":"Normal"}}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"-1844674407370955161ˇ4\n", "mode":"Normal"}}
|
||||
{"Key":"3"}
|
||||
{"Key":"ctrl-x"}
|
||||
{"Get":{"state":"1844674407370955161ˇ4\n", "mode":"Normal"}}
|
||||
{"Key":"2"}
|
||||
{"Key":"ctrl-a"}
|
||||
{"Get":{"state":"-1844674407370955161ˇ5\n", "mode":"Normal"}}
|
Loading…
Add table
Add a link
Reference in a new issue