1use editor::{Editor, MultiBufferSnapshot, ToOffset, ToPoint};
2use gpui::{Action, Context, Window};
3use language::{Bias, Point};
4use schemars::JsonSchema;
5use serde::Deserialize;
6use std::ops::Range;
7
8use crate::{Vim, state::Mode};
9
10const BOOLEAN_PAIRS: &[(&str, &str)] = &[("true", "false"), ("yes", "no"), ("on", "off")];
11
12/// Increments the number under the cursor or toggles boolean values.
13#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
14#[action(namespace = vim)]
15#[serde(deny_unknown_fields)]
16struct Increment {
17 #[serde(default)]
18 step: bool,
19}
20
21/// Decrements the number under the cursor or toggles boolean values.
22#[derive(Clone, Deserialize, JsonSchema, PartialEq, Action)]
23#[action(namespace = vim)]
24#[serde(deny_unknown_fields)]
25struct Decrement {
26 #[serde(default)]
27 step: bool,
28}
29
30pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
31 Vim::action(editor, cx, |vim, action: &Increment, window, cx| {
32 vim.record_current_action(cx);
33 let count = Vim::take_count(cx).unwrap_or(1);
34 Vim::take_forced_motion(cx);
35 let step = if action.step { count as i32 } else { 0 };
36 vim.increment(count as i64, step, window, cx)
37 });
38 Vim::action(editor, cx, |vim, action: &Decrement, window, cx| {
39 vim.record_current_action(cx);
40 let count = Vim::take_count(cx).unwrap_or(1);
41 Vim::take_forced_motion(cx);
42 let step = if action.step { -1 * (count as i32) } else { 0 };
43 vim.increment(-(count as i64), step, window, cx)
44 });
45}
46
47impl Vim {
48 fn increment(
49 &mut self,
50 mut delta: i64,
51 step: i32,
52 window: &mut Window,
53 cx: &mut Context<Self>,
54 ) {
55 self.store_visual_marks(window, cx);
56 self.update_editor(cx, |vim, editor, cx| {
57 let mut edits = Vec::new();
58 let mut new_anchors = Vec::new();
59
60 let snapshot = editor.buffer().read(cx).snapshot(cx);
61 for selection in editor.selections.all_adjusted(cx) {
62 if !selection.is_empty()
63 && (vim.mode != Mode::VisualBlock || new_anchors.is_empty())
64 {
65 new_anchors.push((true, snapshot.anchor_before(selection.start)))
66 }
67 for row in selection.start.row..=selection.end.row {
68 let start = if row == selection.start.row {
69 selection.start
70 } else {
71 Point::new(row, 0)
72 };
73
74 if let Some((range, num, radix)) = find_number(&snapshot, start) {
75 let replace = match radix {
76 10 => increment_decimal_string(&num, delta),
77 16 => increment_hex_string(&num, delta),
78 2 => increment_binary_string(&num, delta),
79 _ => unreachable!(),
80 };
81 delta += step as i64;
82 edits.push((range.clone(), replace));
83 if selection.is_empty() {
84 new_anchors.push((false, snapshot.anchor_after(range.end)))
85 }
86 } else if let Some((range, boolean)) = find_boolean(&snapshot, start) {
87 let replace = toggle_boolean(&boolean);
88 delta += step as i64;
89 edits.push((range.clone(), replace));
90 if selection.is_empty() {
91 new_anchors.push((false, snapshot.anchor_after(range.end)))
92 }
93 } else if selection.is_empty() {
94 new_anchors.push((true, snapshot.anchor_after(start)))
95 }
96 }
97 }
98 editor.transact(window, cx, |editor, window, cx| {
99 editor.edit(edits, cx);
100
101 let snapshot = editor.buffer().read(cx).snapshot(cx);
102 editor.change_selections(Default::default(), window, cx, |s| {
103 let mut new_ranges = Vec::new();
104 for (visual, anchor) in new_anchors.iter() {
105 let mut point = anchor.to_point(&snapshot);
106 if !*visual && point.column > 0 {
107 point.column -= 1;
108 point = snapshot.clip_point(point, Bias::Left)
109 }
110 new_ranges.push(point..point);
111 }
112 s.select_ranges(new_ranges)
113 })
114 });
115 });
116 self.switch_mode(Mode::Normal, true, window, cx)
117 }
118}
119
120fn increment_decimal_string(num: &str, delta: i64) -> String {
121 let (negative, delta, num_str) = match num.strip_prefix('-') {
122 Some(n) => (true, -delta, n),
123 None => (false, delta, num),
124 };
125 let num_length = num_str.len();
126 let leading_zero = num_str.starts_with('0');
127
128 let (result, new_negative) = match u64::from_str_radix(num_str, 10) {
129 Ok(value) => {
130 let wrapped = value.wrapping_add_signed(delta);
131 if delta < 0 && wrapped > value {
132 ((u64::MAX - wrapped).wrapping_add(1), !negative)
133 } else if delta > 0 && wrapped < value {
134 (u64::MAX - wrapped, !negative)
135 } else {
136 (wrapped, negative)
137 }
138 }
139 Err(_) => (u64::MAX, negative),
140 };
141
142 let formatted = format!("{}", result);
143 let new_significant_digits = formatted.len();
144 let padding = if leading_zero {
145 num_length.saturating_sub(new_significant_digits)
146 } else {
147 0
148 };
149
150 if new_negative && result != 0 {
151 format!("-{}{}", "0".repeat(padding), formatted)
152 } else {
153 format!("{}{}", "0".repeat(padding), formatted)
154 }
155}
156
157fn increment_hex_string(num: &str, delta: i64) -> String {
158 let result = if let Ok(val) = u64::from_str_radix(num, 16) {
159 val.wrapping_add_signed(delta)
160 } else {
161 u64::MAX
162 };
163 if should_use_lowercase(num) {
164 format!("{:0width$x}", result, width = num.len())
165 } else {
166 format!("{:0width$X}", result, width = num.len())
167 }
168}
169
170fn should_use_lowercase(num: &str) -> bool {
171 let mut use_uppercase = false;
172 for ch in num.chars() {
173 if ch.is_ascii_lowercase() {
174 return true;
175 }
176 if ch.is_ascii_uppercase() {
177 use_uppercase = true;
178 }
179 }
180 !use_uppercase
181}
182
183fn increment_binary_string(num: &str, delta: i64) -> String {
184 let result = if let Ok(val) = u64::from_str_radix(num, 2) {
185 val.wrapping_add_signed(delta)
186 } else {
187 u64::MAX
188 };
189 format!("{:0width$b}", result, width = num.len())
190}
191
192fn find_number(
193 snapshot: &MultiBufferSnapshot,
194 start: Point,
195) -> Option<(Range<Point>, String, u32)> {
196 let mut offset = start.to_offset(snapshot);
197
198 let ch0 = snapshot.chars_at(offset).next();
199 if ch0.as_ref().is_some_and(char::is_ascii_hexdigit) || matches!(ch0, Some('-' | 'b' | 'x')) {
200 // go backwards to the start of any number the selection is within
201 for ch in snapshot.reversed_chars_at(offset) {
202 if ch.is_ascii_hexdigit() || ch == '-' || ch == 'b' || ch == 'x' {
203 offset -= ch.len_utf8();
204 continue;
205 }
206 break;
207 }
208 }
209
210 let mut begin = None;
211 let mut end = None;
212 let mut num = String::new();
213 let mut radix = 10;
214
215 let mut chars = snapshot.chars_at(offset).peekable();
216 // find the next number on the line (may start after the original cursor position)
217 while let Some(ch) = chars.next() {
218 if num == "0" && ch == 'b' && chars.peek().is_some() && chars.peek().unwrap().is_digit(2) {
219 radix = 2;
220 begin = None;
221 num = String::new();
222 }
223 if num == "0"
224 && ch == 'x'
225 && chars.peek().is_some()
226 && chars.peek().unwrap().is_ascii_hexdigit()
227 {
228 radix = 16;
229 begin = None;
230 num = String::new();
231 }
232
233 if ch.is_digit(radix)
234 || (begin.is_none()
235 && ch == '-'
236 && chars.peek().is_some()
237 && chars.peek().unwrap().is_digit(radix))
238 {
239 if begin.is_none() {
240 begin = Some(offset);
241 }
242 num.push(ch);
243 } else if begin.is_some() {
244 end = Some(offset);
245 break;
246 } else if ch == '\n' {
247 break;
248 }
249 offset += ch.len_utf8();
250 }
251 if let Some(begin) = begin {
252 let end = end.unwrap_or(offset);
253 Some((begin.to_point(snapshot)..end.to_point(snapshot), num, radix))
254 } else {
255 None
256 }
257}
258
259fn find_boolean(snapshot: &MultiBufferSnapshot, start: Point) -> Option<(Range<Point>, String)> {
260 let mut offset = start.to_offset(snapshot);
261
262 let ch0 = snapshot.chars_at(offset).next();
263 if ch0.as_ref().is_some_and(|c| c.is_ascii_alphabetic()) {
264 for ch in snapshot.reversed_chars_at(offset) {
265 if ch.is_ascii_alphabetic() {
266 offset -= ch.len_utf8();
267 continue;
268 }
269 break;
270 }
271 }
272
273 let mut begin = None;
274 let mut end = None;
275 let mut word = String::new();
276
277 let chars = snapshot.chars_at(offset);
278
279 for ch in chars {
280 if ch.is_ascii_alphabetic() {
281 if begin.is_none() {
282 begin = Some(offset);
283 }
284 word.push(ch);
285 } else if begin.is_some() {
286 end = Some(offset);
287 let word_lower = word.to_lowercase();
288 if BOOLEAN_PAIRS
289 .iter()
290 .any(|(a, b)| word_lower == *a || word_lower == *b)
291 {
292 return Some((
293 begin.unwrap().to_point(snapshot)..end.unwrap().to_point(snapshot),
294 word,
295 ));
296 }
297 begin = None;
298 end = None;
299 word = String::new();
300 } else if ch == '\n' {
301 break;
302 }
303 offset += ch.len_utf8();
304 }
305 if let Some(begin) = begin {
306 let end = end.unwrap_or(offset);
307 let word_lower = word.to_lowercase();
308 if BOOLEAN_PAIRS
309 .iter()
310 .any(|(a, b)| word_lower == *a || word_lower == *b)
311 {
312 return Some((begin.to_point(snapshot)..end.to_point(snapshot), word));
313 }
314 }
315 None
316}
317
318fn toggle_boolean(boolean: &str) -> String {
319 let lower = boolean.to_lowercase();
320
321 let target = BOOLEAN_PAIRS
322 .iter()
323 .find_map(|(a, b)| {
324 if lower == *a {
325 Some(b)
326 } else if lower == *b {
327 Some(a)
328 } else {
329 None
330 }
331 })
332 .unwrap_or(&boolean);
333
334 if boolean.chars().all(|c| c.is_uppercase()) {
335 // Upper case
336 target.to_uppercase()
337 } else if boolean.chars().next().unwrap_or(' ').is_uppercase() {
338 // Title case
339 let mut chars = target.chars();
340 match chars.next() {
341 None => String::new(),
342 Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
343 }
344 } else {
345 target.to_string()
346 }
347}
348
349#[cfg(test)]
350mod test {
351 use indoc::indoc;
352
353 use crate::{
354 state::Mode,
355 test::{NeovimBackedTestContext, VimTestContext},
356 };
357
358 #[gpui::test]
359 async fn test_increment(cx: &mut gpui::TestAppContext) {
360 let mut cx = NeovimBackedTestContext::new(cx).await;
361
362 cx.set_shared_state(indoc! {"
363 1ˇ2
364 "})
365 .await;
366
367 cx.simulate_shared_keystrokes("ctrl-a").await;
368 cx.shared_state().await.assert_eq(indoc! {"
369 1ˇ3
370 "});
371 cx.simulate_shared_keystrokes("ctrl-x").await;
372 cx.shared_state().await.assert_eq(indoc! {"
373 1ˇ2
374 "});
375
376 cx.simulate_shared_keystrokes("9 9 ctrl-a").await;
377 cx.shared_state().await.assert_eq(indoc! {"
378 11ˇ1
379 "});
380 cx.simulate_shared_keystrokes("1 1 1 ctrl-x").await;
381 cx.shared_state().await.assert_eq(indoc! {"
382 ˇ0
383 "});
384 cx.simulate_shared_keystrokes(".").await;
385 cx.shared_state().await.assert_eq(indoc! {"
386 -11ˇ1
387 "});
388 }
389
390 #[gpui::test]
391 async fn test_increment_with_dot(cx: &mut gpui::TestAppContext) {
392 let mut cx = NeovimBackedTestContext::new(cx).await;
393
394 cx.set_shared_state(indoc! {"
395 1ˇ.2
396 "})
397 .await;
398
399 cx.simulate_shared_keystrokes("ctrl-a").await;
400 cx.shared_state().await.assert_eq(indoc! {"
401 1.ˇ3
402 "});
403 cx.simulate_shared_keystrokes("ctrl-x").await;
404 cx.shared_state().await.assert_eq(indoc! {"
405 1.ˇ2
406 "});
407 }
408
409 #[gpui::test]
410 async fn test_increment_with_leading_zeros(cx: &mut gpui::TestAppContext) {
411 let mut cx = NeovimBackedTestContext::new(cx).await;
412
413 cx.set_shared_state(indoc! {"
414 000ˇ9
415 "})
416 .await;
417
418 cx.simulate_shared_keystrokes("ctrl-a").await;
419 cx.shared_state().await.assert_eq(indoc! {"
420 001ˇ0
421 "});
422 cx.simulate_shared_keystrokes("2 ctrl-x").await;
423 cx.shared_state().await.assert_eq(indoc! {"
424 000ˇ8
425 "});
426 }
427
428 #[gpui::test]
429 async fn test_increment_with_leading_zeros_and_zero(cx: &mut gpui::TestAppContext) {
430 let mut cx = NeovimBackedTestContext::new(cx).await;
431
432 cx.set_shared_state(indoc! {"
433 01ˇ1
434 "})
435 .await;
436
437 cx.simulate_shared_keystrokes("ctrl-a").await;
438 cx.shared_state().await.assert_eq(indoc! {"
439 01ˇ2
440 "});
441 cx.simulate_shared_keystrokes("1 2 ctrl-x").await;
442 cx.shared_state().await.assert_eq(indoc! {"
443 00ˇ0
444 "});
445 }
446
447 #[gpui::test]
448 async fn test_increment_with_changing_leading_zeros(cx: &mut gpui::TestAppContext) {
449 let mut cx = NeovimBackedTestContext::new(cx).await;
450
451 cx.set_shared_state(indoc! {"
452 099ˇ9
453 "})
454 .await;
455
456 cx.simulate_shared_keystrokes("ctrl-a").await;
457 cx.shared_state().await.assert_eq(indoc! {"
458 100ˇ0
459 "});
460 cx.simulate_shared_keystrokes("2 ctrl-x").await;
461 cx.shared_state().await.assert_eq(indoc! {"
462 99ˇ8
463 "});
464 }
465
466 #[gpui::test]
467 async fn test_increment_with_two_dots(cx: &mut gpui::TestAppContext) {
468 let mut cx = NeovimBackedTestContext::new(cx).await;
469
470 cx.set_shared_state(indoc! {"
471 111.ˇ.2
472 "})
473 .await;
474
475 cx.simulate_shared_keystrokes("ctrl-a").await;
476 cx.shared_state().await.assert_eq(indoc! {"
477 111..ˇ3
478 "});
479 cx.simulate_shared_keystrokes("ctrl-x").await;
480 cx.shared_state().await.assert_eq(indoc! {"
481 111..ˇ2
482 "});
483 }
484
485 #[gpui::test]
486 async fn test_increment_sign_change(cx: &mut gpui::TestAppContext) {
487 let mut cx = NeovimBackedTestContext::new(cx).await;
488 cx.set_shared_state(indoc! {"
489 ˇ0
490 "})
491 .await;
492 cx.simulate_shared_keystrokes("ctrl-x").await;
493 cx.shared_state().await.assert_eq(indoc! {"
494 -ˇ1
495 "});
496 cx.simulate_shared_keystrokes("2 ctrl-a").await;
497 cx.shared_state().await.assert_eq(indoc! {"
498 ˇ1
499 "});
500 }
501
502 #[gpui::test]
503 async fn test_increment_sign_change_with_leading_zeros(cx: &mut gpui::TestAppContext) {
504 let mut cx = NeovimBackedTestContext::new(cx).await;
505 cx.set_shared_state(indoc! {"
506 00ˇ1
507 "})
508 .await;
509 cx.simulate_shared_keystrokes("ctrl-x").await;
510 cx.shared_state().await.assert_eq(indoc! {"
511 00ˇ0
512 "});
513 cx.simulate_shared_keystrokes("ctrl-x").await;
514 cx.shared_state().await.assert_eq(indoc! {"
515 -00ˇ1
516 "});
517 cx.simulate_shared_keystrokes("2 ctrl-a").await;
518 cx.shared_state().await.assert_eq(indoc! {"
519 00ˇ1
520 "});
521 }
522
523 #[gpui::test]
524 async fn test_increment_bin_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
525 let mut cx = NeovimBackedTestContext::new(cx).await;
526 cx.set_shared_state(indoc! {"
527 0b111111111111111111111111111111111111111111111111111111111111111111111ˇ1
528 "})
529 .await;
530
531 cx.simulate_shared_keystrokes("ctrl-a").await;
532 cx.shared_state().await.assert_eq(indoc! {"
533 0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1
534 "});
535 cx.simulate_shared_keystrokes("ctrl-a").await;
536 cx.shared_state().await.assert_eq(indoc! {"
537 0b000000000000000000000000000000000000000000000000000000000000000000000ˇ0
538 "});
539
540 cx.simulate_shared_keystrokes("ctrl-a").await;
541 cx.shared_state().await.assert_eq(indoc! {"
542 0b000000000000000000000000000000000000000000000000000000000000000000000ˇ1
543 "});
544 cx.simulate_shared_keystrokes("2 ctrl-x").await;
545 cx.shared_state().await.assert_eq(indoc! {"
546 0b000000111111111111111111111111111111111111111111111111111111111111111ˇ1
547 "});
548 }
549
550 #[gpui::test]
551 async fn test_increment_hex_wrapping_and_padding(cx: &mut gpui::TestAppContext) {
552 let mut cx = NeovimBackedTestContext::new(cx).await;
553 cx.set_shared_state(indoc! {"
554 0xfffffffffffffffffffˇf
555 "})
556 .await;
557
558 cx.simulate_shared_keystrokes("ctrl-a").await;
559 cx.shared_state().await.assert_eq(indoc! {"
560 0x0000fffffffffffffffˇf
561 "});
562 cx.simulate_shared_keystrokes("ctrl-a").await;
563 cx.shared_state().await.assert_eq(indoc! {"
564 0x0000000000000000000ˇ0
565 "});
566 cx.simulate_shared_keystrokes("ctrl-a").await;
567 cx.shared_state().await.assert_eq(indoc! {"
568 0x0000000000000000000ˇ1
569 "});
570 cx.simulate_shared_keystrokes("2 ctrl-x").await;
571 cx.shared_state().await.assert_eq(indoc! {"
572 0x0000fffffffffffffffˇf
573 "});
574 }
575
576 #[gpui::test]
577 async fn test_increment_wrapping(cx: &mut gpui::TestAppContext) {
578 let mut cx = NeovimBackedTestContext::new(cx).await;
579 cx.set_shared_state(indoc! {"
580 1844674407370955161ˇ9
581 "})
582 .await;
583
584 cx.simulate_shared_keystrokes("ctrl-a").await;
585 cx.shared_state().await.assert_eq(indoc! {"
586 1844674407370955161ˇ5
587 "});
588 cx.simulate_shared_keystrokes("ctrl-a").await;
589 cx.shared_state().await.assert_eq(indoc! {"
590 -1844674407370955161ˇ5
591 "});
592 cx.simulate_shared_keystrokes("ctrl-a").await;
593 cx.shared_state().await.assert_eq(indoc! {"
594 -1844674407370955161ˇ4
595 "});
596 cx.simulate_shared_keystrokes("3 ctrl-x").await;
597 cx.shared_state().await.assert_eq(indoc! {"
598 1844674407370955161ˇ4
599 "});
600 cx.simulate_shared_keystrokes("2 ctrl-a").await;
601 cx.shared_state().await.assert_eq(indoc! {"
602 -1844674407370955161ˇ5
603 "});
604 }
605
606 #[gpui::test]
607 async fn test_increment_inline(cx: &mut gpui::TestAppContext) {
608 let mut cx = NeovimBackedTestContext::new(cx).await;
609 cx.set_shared_state(indoc! {"
610 inline0x3ˇ9u32
611 "})
612 .await;
613
614 cx.simulate_shared_keystrokes("ctrl-a").await;
615 cx.shared_state().await.assert_eq(indoc! {"
616 inline0x3ˇau32
617 "});
618 cx.simulate_shared_keystrokes("ctrl-a").await;
619 cx.shared_state().await.assert_eq(indoc! {"
620 inline0x3ˇbu32
621 "});
622 cx.simulate_shared_keystrokes("l l l ctrl-a").await;
623 cx.shared_state().await.assert_eq(indoc! {"
624 inline0x3bu3ˇ3
625 "});
626 }
627
628 #[gpui::test]
629 async fn test_increment_hex_casing(cx: &mut gpui::TestAppContext) {
630 let mut cx = NeovimBackedTestContext::new(cx).await;
631 cx.set_shared_state(indoc! {"
632 0xFˇa
633 "})
634 .await;
635
636 cx.simulate_shared_keystrokes("ctrl-a").await;
637 cx.shared_state().await.assert_eq(indoc! {"
638 0xfˇb
639 "});
640 cx.simulate_shared_keystrokes("ctrl-a").await;
641 cx.shared_state().await.assert_eq(indoc! {"
642 0xfˇc
643 "});
644 }
645
646 #[gpui::test]
647 async fn test_increment_radix(cx: &mut gpui::TestAppContext) {
648 let mut cx = NeovimBackedTestContext::new(cx).await;
649
650 cx.simulate("ctrl-a", "ˇ total: 0xff")
651 .await
652 .assert_matches();
653 cx.simulate("ctrl-x", "ˇ total: 0xff")
654 .await
655 .assert_matches();
656 cx.simulate("ctrl-x", "ˇ total: 0xFF")
657 .await
658 .assert_matches();
659 cx.simulate("ctrl-a", "(ˇ0b10f)").await.assert_matches();
660 cx.simulate("ctrl-a", "ˇ-1").await.assert_matches();
661 cx.simulate("ctrl-a", "banˇana").await.assert_matches();
662 }
663
664 #[gpui::test]
665 async fn test_increment_steps(cx: &mut gpui::TestAppContext) {
666 let mut cx = NeovimBackedTestContext::new(cx).await;
667
668 cx.set_shared_state(indoc! {"
669 ˇ1
670 1
671 1 2
672 1
673 1"})
674 .await;
675
676 cx.simulate_shared_keystrokes("j v shift-g g ctrl-a").await;
677 cx.shared_state().await.assert_eq(indoc! {"
678 1
679 ˇ2
680 3 2
681 4
682 5"});
683
684 cx.simulate_shared_keystrokes("shift-g ctrl-v g g").await;
685 cx.shared_state().await.assert_eq(indoc! {"
686 «1ˇ»
687 «2ˇ»
688 «3ˇ» 2
689 «4ˇ»
690 «5ˇ»"});
691
692 cx.simulate_shared_keystrokes("g ctrl-x").await;
693 cx.shared_state().await.assert_eq(indoc! {"
694 ˇ0
695 0
696 0 2
697 0
698 0"});
699 cx.simulate_shared_keystrokes("v shift-g g ctrl-a").await;
700 cx.simulate_shared_keystrokes("v shift-g 5 g ctrl-a").await;
701 cx.shared_state().await.assert_eq(indoc! {"
702 ˇ6
703 12
704 18 2
705 24
706 30"});
707 }
708
709 #[gpui::test]
710 async fn test_toggle_boolean(cx: &mut gpui::TestAppContext) {
711 let mut cx = VimTestContext::new(cx, true).await;
712
713 cx.set_state("let enabled = trˇue;", Mode::Normal);
714 cx.simulate_keystrokes("ctrl-a");
715 cx.assert_state("let enabled = falsˇe;", Mode::Normal);
716
717 cx.simulate_keystrokes("0 ctrl-a");
718 cx.assert_state("let enabled = truˇe;", Mode::Normal);
719
720 cx.set_state(
721 indoc! {"
722 ˇlet enabled = TRUE;
723 let enabled = TRUE;
724 let enabled = TRUE;
725 "},
726 Mode::Normal,
727 );
728 cx.simulate_keystrokes("shift-v j j ctrl-x");
729 cx.assert_state(
730 indoc! {"
731 ˇlet enabled = FALSE;
732 let enabled = FALSE;
733 let enabled = FALSE;
734 "},
735 Mode::Normal,
736 );
737
738 cx.set_state(
739 indoc! {"
740 let enabled = ˇYes;
741 let enabled = Yes;
742 let enabled = Yes;
743 "},
744 Mode::Normal,
745 );
746 cx.simulate_keystrokes("ctrl-v j j e ctrl-x");
747 cx.assert_state(
748 indoc! {"
749 let enabled = ˇNo;
750 let enabled = No;
751 let enabled = No;
752 "},
753 Mode::Normal,
754 );
755
756 cx.set_state("ˇlet enabled = True;", Mode::Normal);
757 cx.simulate_keystrokes("ctrl-a");
758 cx.assert_state("let enabled = Falsˇe;", Mode::Normal);
759
760 cx.simulate_keystrokes("ctrl-a");
761 cx.assert_state("let enabled = Truˇe;", Mode::Normal);
762
763 cx.set_state("let enabled = Onˇ;", Mode::Normal);
764 cx.simulate_keystrokes("v b ctrl-a");
765 cx.assert_state("let enabled = ˇOff;", Mode::Normal);
766 }
767}