vim: Implement partial increment/decrement for visual selection (#36553)
This change adds the ability to increment / decrement numbers that are part of a visual selection. Previously Zed would resolve to the entire number under visual selection for increment as oppposed to only incrementing the part of the number that is selected Release Notes: - vim: Fixed increment/decrement in visual mode
This commit is contained in:
parent
e1a96b68f0
commit
f5fd4ac670
2 changed files with 130 additions and 1 deletions
|
@ -70,8 +70,19 @@ impl Vim {
|
||||||
} else {
|
} else {
|
||||||
Point::new(row, 0)
|
Point::new(row, 0)
|
||||||
};
|
};
|
||||||
|
let end = if row == selection.end.row {
|
||||||
|
selection.end
|
||||||
|
} else {
|
||||||
|
Point::new(row, snapshot.line_len(multi_buffer::MultiBufferRow(row)))
|
||||||
|
};
|
||||||
|
|
||||||
if let Some((range, num, radix)) = find_number(&snapshot, start) {
|
let number_result = if !selection.is_empty() {
|
||||||
|
find_number_in_range(&snapshot, start, end)
|
||||||
|
} else {
|
||||||
|
find_number(&snapshot, start)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((range, num, radix)) = number_result {
|
||||||
let replace = match radix {
|
let replace = match radix {
|
||||||
10 => increment_decimal_string(&num, delta),
|
10 => increment_decimal_string(&num, delta),
|
||||||
16 => increment_hex_string(&num, delta),
|
16 => increment_hex_string(&num, delta),
|
||||||
|
@ -189,6 +200,90 @@ fn increment_binary_string(num: &str, delta: i64) -> String {
|
||||||
format!("{:0width$b}", result, width = num.len())
|
format!("{:0width$b}", result, width = num.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_number_in_range(
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
start: Point,
|
||||||
|
end: Point,
|
||||||
|
) -> Option<(Range<Point>, String, u32)> {
|
||||||
|
let start_offset = start.to_offset(snapshot);
|
||||||
|
let end_offset = end.to_offset(snapshot);
|
||||||
|
|
||||||
|
let mut offset = start_offset;
|
||||||
|
|
||||||
|
// Backward scan to find the start of the number, but stop at start_offset
|
||||||
|
for ch in snapshot.reversed_chars_at(offset) {
|
||||||
|
if ch.is_ascii_hexdigit() || ch == '-' || ch == 'b' || ch == 'x' {
|
||||||
|
if offset == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset -= ch.len_utf8();
|
||||||
|
if offset < start_offset {
|
||||||
|
offset = start_offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut begin = None;
|
||||||
|
let mut end_num = None;
|
||||||
|
let mut num = String::new();
|
||||||
|
let mut radix = 10;
|
||||||
|
|
||||||
|
let mut chars = snapshot.chars_at(offset).peekable();
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if offset >= end_offset {
|
||||||
|
break; // stop at end of selection
|
||||||
|
}
|
||||||
|
|
||||||
|
if num == "0" && ch == 'b' && chars.peek().is_some() && chars.peek().unwrap().is_digit(2) {
|
||||||
|
radix = 2;
|
||||||
|
begin = None;
|
||||||
|
num = String::new();
|
||||||
|
} else if num == "0"
|
||||||
|
&& ch == 'x'
|
||||||
|
&& chars.peek().is_some()
|
||||||
|
&& chars.peek().unwrap().is_ascii_hexdigit()
|
||||||
|
{
|
||||||
|
radix = 16;
|
||||||
|
begin = None;
|
||||||
|
num = String::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch.is_digit(radix)
|
||||||
|
|| (begin.is_none()
|
||||||
|
&& ch == '-'
|
||||||
|
&& chars.peek().is_some()
|
||||||
|
&& chars.peek().unwrap().is_digit(radix))
|
||||||
|
{
|
||||||
|
if begin.is_none() {
|
||||||
|
begin = Some(offset);
|
||||||
|
}
|
||||||
|
num.push(ch);
|
||||||
|
} else if begin.is_some() {
|
||||||
|
end_num = Some(offset);
|
||||||
|
break;
|
||||||
|
} else if ch == '\n' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += ch.len_utf8();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(begin) = begin {
|
||||||
|
let end_num = end_num.unwrap_or(offset);
|
||||||
|
Some((
|
||||||
|
begin.to_point(snapshot)..end_num.to_point(snapshot),
|
||||||
|
num,
|
||||||
|
radix,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_number(
|
fn find_number(
|
||||||
snapshot: &MultiBufferSnapshot,
|
snapshot: &MultiBufferSnapshot,
|
||||||
start: Point,
|
start: Point,
|
||||||
|
@ -764,4 +859,18 @@ mod test {
|
||||||
cx.simulate_keystrokes("v b ctrl-a");
|
cx.simulate_keystrokes("v b ctrl-a");
|
||||||
cx.assert_state("let enabled = ˇOff;", Mode::Normal);
|
cx.assert_state("let enabled = ˇOff;", Mode::Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_increment_visual_partial_number(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state("ˇ123").await;
|
||||||
|
cx.simulate_shared_keystrokes("v l ctrl-a").await;
|
||||||
|
cx.shared_state().await.assert_eq(indoc! {"ˇ133"});
|
||||||
|
cx.simulate_shared_keystrokes("l v l ctrl-a").await;
|
||||||
|
cx.shared_state().await.assert_eq(indoc! {"1ˇ34"});
|
||||||
|
cx.simulate_shared_keystrokes("shift-v y p p ctrl-v k k l ctrl-a")
|
||||||
|
.await;
|
||||||
|
cx.shared_state().await.assert_eq(indoc! {"ˇ144\n144\n144"});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{"Put":{"state":"ˇ123"}}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"l"}
|
||||||
|
{"Key":"ctrl-a"}
|
||||||
|
{"Get":{"state":"ˇ133","mode":"Normal"}}
|
||||||
|
{"Key":"l"}
|
||||||
|
{"Key":"v"}
|
||||||
|
{"Key":"l"}
|
||||||
|
{"Key":"ctrl-a"}
|
||||||
|
{"Get":{"state":"1ˇ34","mode":"Normal"}}
|
||||||
|
{"Key":"shift-v"}
|
||||||
|
{"Key":"y"}
|
||||||
|
{"Key":"p"}
|
||||||
|
{"Key":"p"}
|
||||||
|
{"Key":"ctrl-v"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Key":"l"}
|
||||||
|
{"Key":"ctrl-a"}
|
||||||
|
{"Get":{"state":"ˇ144\n144\n144","mode":"Normal"}}
|
Loading…
Add table
Add a link
Reference in a new issue