fresh-editor 0.3.6

A lightweight, fast terminal-based text editor with LSP support and TypeScript plugins
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
// Split View Behavioral Expectations Validation Tests
// These tests verify the expected behaviors documented in docs/TODO.md
// Key insight: Splits share the SAME buffer by default (Emacs-style)
// Each split has independent cursor and scroll positions

use crate::common::harness::EditorTestHarness;
use crossterm::event::{KeyCode, KeyModifiers};
use tempfile::TempDir;

/// Helper: Create a horizontal split via command palette
fn split_horizontal(harness: &mut EditorTestHarness) {
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("split horiz").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
}

/// Helper: Create a vertical split via command palette
fn split_vertical(harness: &mut EditorTestHarness) {
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("split vert").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
}

/// Helper: Navigate to previous split via command palette
fn prev_split(harness: &mut EditorTestHarness) {
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
}

/// Helper: Navigate to next split via command palette
fn next_split(harness: &mut EditorTestHarness) {
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("next split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
}

/// Helper: Close the active split via command palette
fn close_split(harness: &mut EditorTestHarness) {
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("close split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
}

/// Test that horizontal split creates two views of the SAME buffer
#[test]
fn test_horizontal_split_shares_same_buffer() {
    let mut harness = EditorTestHarness::new(100, 40).unwrap();

    // Type text in first buffer
    harness.type_text("Original content").unwrap();
    harness.render().unwrap();

    let original_content = harness.get_buffer_content().unwrap();
    assert_eq!(original_content, "Original content");

    // Split horizontally
    split_horizontal(&mut harness);

    // New split should show the SAME buffer content
    let new_split_content = harness.get_buffer_content().unwrap();
    assert_eq!(
        new_split_content, "Original content",
        "New split should show the same buffer, got '{}'",
        new_split_content
    );

    // Screen should show "Split pane horizontally" message (may be truncated)
    harness.assert_screen_contains("Split pane horiz");

    // Verify separator line exists (horizontal split uses ─)
    let screen = harness.screen_to_string();
    assert!(
        screen.contains('─'),
        "Horizontal split should show ─ separator"
    );
}

/// Test that vertical split creates two views of the SAME buffer
#[test]
fn test_vertical_split_shares_same_buffer() {
    let mut harness = EditorTestHarness::new(100, 40).unwrap();

    // Type text in first buffer
    harness.type_text("Buffer content").unwrap();
    harness.render().unwrap();

    // Split vertically (Alt+V)
    split_vertical(&mut harness);

    // New split should show the SAME buffer content
    let new_split_content = harness.get_buffer_content().unwrap();
    assert_eq!(
        new_split_content, "Buffer content",
        "New split should show the same buffer"
    );

    // Verify separator line exists (vertical split uses │)
    let screen = harness.screen_to_string();
    assert!(
        screen.contains('│'),
        "Vertical split should show │ separator"
    );
}

/// Test that typing in one split modifies the shared buffer
/// and changes are visible when switching to other split
#[test]
fn test_typing_modifies_shared_buffer() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type in first buffer
    harness.type_text("Hello").unwrap();

    // Create vertical split (both splits now show same buffer)
    split_vertical(&mut harness);

    // Verify new split shows same content
    assert_eq!(harness.get_buffer_content().unwrap(), "Hello");

    // New split has independent cursor at position 0
    // Move cursor to end before typing
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();

    // Type more text in the second split (modifies shared buffer)
    harness.type_text(" World").unwrap();
    harness.render().unwrap();

    // Current buffer should now be "Hello World"
    assert_eq!(harness.get_buffer_content().unwrap(), "Hello World");

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split should ALSO show "Hello World" (same buffer)
    assert_eq!(harness.get_buffer_content().unwrap(), "Hello World");
}

/// Test that each split has independent cursor position for the same buffer
#[test]
fn test_independent_cursor_positions_same_buffer() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type text and position cursor at beginning
    harness.type_text("ABCDEFGHIJ").unwrap();
    harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
    // Cursor in first split is at position 0

    // Create vertical split via command palette
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("split vert").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // New split should show same buffer
    assert_eq!(harness.get_buffer_content().unwrap(), "ABCDEFGHIJ");

    // Move cursor in second split to end
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    let cursor_in_second_split = harness.cursor_position();
    assert_eq!(cursor_in_second_split, 10); // End of "ABCDEFGHIJ"

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split cursor should still be at position 0 (independent)
    let cursor_in_first_split = harness.cursor_position();
    assert_eq!(
        cursor_in_first_split, 0,
        "First split cursor should remain at 0, not {}",
        cursor_in_first_split
    );
}

/// Test that each split has independent scroll position for the same buffer
#[test]
fn test_independent_scroll_positions_same_buffer() {
    let mut harness = EditorTestHarness::new(120, 30).unwrap();

    // Create long content (more than viewport height)
    let long_text = (1..=50)
        .map(|i| format!("Line {}", i))
        .collect::<Vec<_>>()
        .join("\n");
    harness.type_text(&long_text).unwrap();
    harness.render().unwrap();

    // Scroll to top in first split
    harness
        .send_key(KeyCode::Home, KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    let top_byte_first_split = harness.top_byte();

    // Create vertical split (both show same buffer)
    split_vertical(&mut harness);

    // Scroll down in second split - use Ctrl+End to go to end of buffer
    harness
        .send_key(KeyCode::End, KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    let top_byte_second_split = harness.top_byte();

    // Second split should have scrolled
    assert_ne!(
        top_byte_second_split, top_byte_first_split,
        "Second split should have different scroll position"
    );

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split should still be at original scroll position
    assert_eq!(
        harness.top_byte(),
        top_byte_first_split,
        "First split scroll position should be preserved"
    );
}

/// Test next_split and prev_split circular navigation
#[test]
fn test_split_navigation_circular() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Create content
    harness.type_text("Shared buffer").unwrap();

    // Create second split
    split_vertical(&mut harness);

    // Create third split
    split_vertical(&mut harness);

    // Move cursor to unique position in third split
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    let cursor_third = harness.cursor_position();

    // Navigate to next split (should wrap to first)
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("next split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // All splits show same buffer, but cursor positions differ
    assert_eq!(harness.get_buffer_content().unwrap(), "Shared buffer");

    // Keep navigating to verify circular behavior
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("next split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("next split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // After 3 next operations, should be back to third split with cursor at end
    assert_eq!(harness.cursor_position(), cursor_third);
}

/// Test that closing a split expands the remaining split
#[test]
fn test_close_split_expands_remaining() {
    let mut harness = EditorTestHarness::new(100, 40).unwrap();

    // Create content
    harness.type_text("Buffer content").unwrap();

    // Create split
    split_vertical(&mut harness);

    // Verify separator exists
    let screen_before = harness.screen_to_string();
    assert!(
        screen_before.contains('│'),
        "Should have vertical separator"
    );

    // Close current split via command palette
    close_split(&mut harness);

    // Should see success message
    harness.assert_screen_contains("Closed split");

    // Buffer content should still be accessible
    assert_eq!(harness.get_buffer_content().unwrap(), "Buffer content");
}

/// Test that each split can open different files
#[test]
fn test_split_with_different_files() {
    let temp_dir = TempDir::new().unwrap();
    let file1 = temp_dir.path().join("file1.txt");
    let file2 = temp_dir.path().join("file2.txt");

    std::fs::write(&file1, "Content of file 1").unwrap();
    std::fs::write(&file2, "Content of file 2").unwrap();

    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Open first file
    harness.open_file(&file1).unwrap();
    harness.render().unwrap();
    assert_eq!(harness.get_buffer_content().unwrap(), "Content of file 1");

    // Create vertical split (initially shows same buffer)
    split_vertical(&mut harness);
    assert_eq!(harness.get_buffer_content().unwrap(), "Content of file 1");

    // Open different file in new split
    harness.open_file(&file2).unwrap();
    harness.render().unwrap();
    assert_eq!(harness.get_buffer_content().unwrap(), "Content of file 2");

    // Verify both file names appear in the UI
    harness.assert_screen_contains("file1.txt");
    harness.assert_screen_contains("file2.txt");

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split should still show file1 content
    assert_eq!(harness.get_buffer_content().unwrap(), "Content of file 1");
}

/// Test nested splits (3+ levels deep)
#[test]
fn test_nested_splits_maintain_hierarchy() {
    let mut harness = EditorTestHarness::new(160, 50).unwrap();

    // Create content
    harness.type_text("Base content").unwrap();

    // Vertical split
    split_vertical(&mut harness);

    // Horizontal split on second split
    split_horizontal(&mut harness);

    // Another vertical split
    split_vertical(&mut harness);

    // We should now have 4 splits, all showing same buffer
    assert_eq!(harness.get_buffer_content().unwrap(), "Base content");

    // Navigate through all splits to verify they exist
    for _ in 0..4 {
        harness
            .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
            .unwrap();
        harness.render().unwrap();
        harness.type_text("next split").unwrap();
        harness
            .send_key(KeyCode::Enter, KeyModifiers::NONE)
            .unwrap();
        harness.render().unwrap();

        // All splits show same buffer
        assert_eq!(harness.get_buffer_content().unwrap(), "Base content");
    }
}

/// Test that undo/redo affects the shared buffer (visible in all splits)
#[test]
fn test_undo_redo_affects_shared_buffer() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type text
    harness.type_text("Hello").unwrap();

    // Create split
    split_vertical(&mut harness);

    // Type more in second split (cursor starts at 0, move to end first)
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness.type_text(" World").unwrap();
    harness.render().unwrap();
    assert_eq!(harness.get_buffer_content().unwrap(), "Hello World");

    // Undo in second split (affects shared buffer)
    harness
        .send_key(KeyCode::Char('z'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    let after_undo = harness.get_buffer_content().unwrap();

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split should also show the undone state (same buffer)
    assert_eq!(
        harness.get_buffer_content().unwrap(),
        after_undo,
        "Both splits should show same buffer state after undo"
    );
}

/// Test status bar shows active split's buffer info
#[test]
fn test_status_bar_reflects_active_split() {
    let temp_dir = TempDir::new().unwrap();
    let file1 = temp_dir.path().join("alpha.txt");
    let file2 = temp_dir.path().join("beta.txt");

    std::fs::write(&file1, "Alpha content").unwrap();
    std::fs::write(&file2, "Beta content").unwrap();

    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Open first file
    harness.open_file(&file1).unwrap();
    harness.render().unwrap();

    // Status bar should show alpha.txt
    harness.assert_screen_contains("alpha.txt");

    // Create split via palette (Alt+V can collide with the View
    // menu accelerator on some platforms — keyboard handling for
    // Alt-modified keys differs between Linux and macOS terminals).
    split_vertical(&mut harness);
    harness.open_file(&file2).unwrap();
    harness.render().unwrap();

    // Status bar should now show beta.txt (active split)
    harness.assert_screen_contains("beta.txt");
}

/// Test that deleting text in one split is visible in other splits showing same buffer
#[test]
fn test_delete_visible_in_all_splits() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type text
    harness.type_text("ABCDE").unwrap();

    // Create split
    split_vertical(&mut harness);

    // Delete in second split (modifies shared buffer)
    // Move cursor to end first (cursor starts at 0 in new split)
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness
        .send_key(KeyCode::Backspace, KeyModifiers::NONE)
        .unwrap();
    harness
        .send_key(KeyCode::Backspace, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    assert_eq!(harness.get_buffer_content().unwrap(), "ABC");

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split should also show "ABC" (same buffer)
    assert_eq!(harness.get_buffer_content().unwrap(), "ABC");
}

/// Test that cursor movements don't affect other splits
#[test]
fn test_cursor_movement_isolated_to_active_split() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type multi-line content
    harness.type_text("Line 1\nLine 2\nLine 3").unwrap();

    // Position cursor at start
    harness
        .send_key(KeyCode::Home, KeyModifiers::CONTROL)
        .unwrap();
    let first_cursor_before = harness.cursor_position();
    assert_eq!(first_cursor_before, 0);

    // Create split (shares same buffer)
    split_vertical(&mut harness);

    // Move cursor around in second split
    harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
    harness.send_key(KeyCode::Down, KeyModifiers::NONE).unwrap();
    harness
        .send_key(KeyCode::Right, KeyModifiers::NONE)
        .unwrap();
    harness
        .send_key(KeyCode::Right, KeyModifiers::NONE)
        .unwrap();
    let second_cursor = harness.cursor_position();

    // Second split cursor should have moved
    assert_ne!(second_cursor, first_cursor_before);

    // Navigate back to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // First split cursor should be exactly where we left it
    assert_eq!(harness.cursor_position(), first_cursor_before);
}

/// Test creating a split with minimal height (edge case)
#[test]
fn test_split_with_minimal_height() {
    // Use a small terminal height but wider to fit status bar
    let mut harness = EditorTestHarness::new(100, 12).unwrap();

    harness.type_text("Small terminal").unwrap();

    // Create horizontal split (splits the limited vertical space)
    split_horizontal(&mut harness);

    // Should succeed without panic (message may be truncated on narrow terminals)
    harness.assert_screen_contains("Split pane horiz");

    // Should be able to type in split (modifying shared buffer)
    harness.type_text(" - modified").unwrap();
    harness.render().unwrap();
    assert!(harness.get_buffer_content().unwrap().contains("modified"));
}

/// Test that copy/paste works across splits (shared clipboard)
#[test]
fn test_clipboard_shared_across_splits() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type and select text in first buffer
    harness.type_text("CopyThis").unwrap();
    // Select all
    harness
        .send_key(KeyCode::Char('a'), KeyModifiers::CONTROL)
        .unwrap();
    // Copy
    harness
        .send_key(KeyCode::Char('c'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Create split (shows same buffer)
    split_vertical(&mut harness);

    // Go to end and paste
    harness.send_key(KeyCode::End, KeyModifiers::NONE).unwrap();
    harness
        .send_key(KeyCode::Char('v'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Should have "CopyThis" + "CopyThis" = "CopyThisCopyThis"
    assert_eq!(harness.get_buffer_content().unwrap(), "CopyThisCopyThis");
}

/// Test that cursor positions in other splits adjust when buffer is edited
/// This is an advanced feature that requires tracking cursor positions across splits
/// and adjusting them when the shared buffer is modified.
#[test]
fn test_cursor_adjustment_on_shared_buffer_edit() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Type text
    harness.type_text("ABCDEFGHIJ").unwrap();

    // Create split
    split_vertical(&mut harness);

    // In second split, move cursor to position 5 (after "ABCDE")
    harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
    for _ in 0..5 {
        harness
            .send_key(KeyCode::Right, KeyModifiers::NONE)
            .unwrap();
    }
    assert_eq!(harness.cursor_position(), 5);

    // Navigate to first split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("prev split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // In first split, insert text at beginning
    harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
    harness.type_text("XXX").unwrap(); // Insert 3 chars
    harness.render().unwrap();

    // Buffer is now "XXXABCDEFGHIJ"
    assert_eq!(harness.get_buffer_content().unwrap(), "XXXABCDEFGHIJ");

    // Navigate back to second split
    harness
        .send_key(KeyCode::Char('p'), KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();
    harness.type_text("next split").unwrap();
    harness
        .send_key(KeyCode::Enter, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();

    // Second split cursor should have adjusted from 5 to 8 (5 + 3 inserted chars)
    // This is a critical feature for shared buffer editing
    let adjusted_cursor = harness.cursor_position();
    assert_eq!(
        adjusted_cursor, 8,
        "Cursor in second split should adjust for insertion, got {}",
        adjusted_cursor
    );
}

/// Test that cursors in inactive splits are rendered (visible on screen)
/// Each split should show its cursor position even when not focused
#[test]
fn test_cursors_visible_in_all_splits() {
    let mut harness = EditorTestHarness::new(120, 30).unwrap();

    // Type some text
    harness.type_text("ABCDEFGHIJ").unwrap();
    harness.render().unwrap();

    // Create vertical split
    split_vertical(&mut harness);

    // Move cursor to different position in second split
    harness.send_key(KeyCode::Home, KeyModifiers::NONE).unwrap();
    for _ in 0..5 {
        harness
            .send_key(KeyCode::Right, KeyModifiers::NONE)
            .unwrap();
    }
    harness.render().unwrap();

    // Second split cursor at position 5 (after "ABCDE")
    let second_split_cursor_pos = harness.cursor_position();
    assert_eq!(second_split_cursor_pos, 5);

    // Find all rendered cursors on screen
    let cursors = harness.find_all_cursors();

    // Should have at least 2 cursors visible (one for each split)
    // The active split has the hardware cursor, inactive split should have REVERSED cursor
    assert!(
        cursors.len() >= 2,
        "Should render cursors for both splits, found {} cursors: {:?}",
        cursors.len(),
        cursors
    );

    // Check that we have both a primary (hardware) cursor and a secondary (REVERSED) cursor
    let primary_cursors: Vec<_> = cursors
        .iter()
        .filter(|(_, _, _, is_primary)| *is_primary)
        .collect();
    let secondary_cursors: Vec<_> = cursors
        .iter()
        .filter(|(_, _, _, is_primary)| !*is_primary)
        .collect();

    assert_eq!(
        primary_cursors.len(),
        1,
        "Should have exactly one primary (hardware) cursor"
    );
    assert!(
        !secondary_cursors.is_empty(),
        "Should have at least one secondary cursor for inactive split"
    );
}

/// Test that cursor movement works after switching splits
#[test]
fn test_cursor_movement_after_split_switch() {
    let mut harness = EditorTestHarness::new(120, 30).unwrap();

    // Type some text
    harness.type_text("ABCDEFGHIJ").unwrap();
    harness.render().unwrap();

    // Cursor should be at end (position 10)
    let initial_pos = harness.cursor_position();
    assert_eq!(initial_pos, 10);

    // Create vertical split
    split_vertical(&mut harness);

    // New split should have cursor at position 0
    let second_split_pos = harness.cursor_position();
    assert_eq!(second_split_pos, 0);

    // Move cursor right in second split
    harness
        .send_key(KeyCode::Right, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
    let after_right = harness.cursor_position();
    assert_eq!(after_right, 1);

    // Switch back to first split
    prev_split(&mut harness);

    // First split should have cursor at position 10 (where we left it)
    let first_split_pos = harness.cursor_position();
    assert_eq!(first_split_pos, 10);

    // Move cursor left in first split
    harness.send_key(KeyCode::Left, KeyModifiers::NONE).unwrap();
    harness.render().unwrap();
    let after_left = harness.cursor_position();
    assert_eq!(after_left, 9, "Cursor should move left from 10 to 9");

    // Get screen position to verify visual cursor moved
    let (screen_x1, _screen_y1) = harness.screen_cursor_position();

    // Move right to go back
    harness
        .send_key(KeyCode::Right, KeyModifiers::NONE)
        .unwrap();
    harness.render().unwrap();
    let after_right2 = harness.cursor_position();
    assert_eq!(after_right2, 10);

    let (screen_x2, _screen_y2) = harness.screen_cursor_position();

    // Verify screen position changed
    assert_ne!(screen_x1, screen_x2, "Screen cursor X should have moved");
}

/// Test that mouse scroll wheel scrolls the viewport under the pointer, not the focused one
#[test]
fn test_scroll_wheel_targets_viewport_under_pointer() {
    let mut harness = EditorTestHarness::new(120, 40).unwrap();

    // Create long content so both splits can scroll
    let long_text = (1..=100)
        .map(|i| format!("Line {}", i))
        .collect::<Vec<_>>()
        .join("\n");
    let _fixture = harness.load_buffer_from_text(&long_text).unwrap();

    // Scroll to top
    harness
        .send_key(KeyCode::Home, KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Create horizontal split - focus moves to the new (bottom) split
    split_horizontal(&mut harness);

    // Scroll to top in second (bottom) split too
    harness
        .send_key(KeyCode::Home, KeyModifiers::CONTROL)
        .unwrap();
    harness.render().unwrap();

    // Navigate back to first (top) split so it's the focused one
    prev_split(&mut harness);

    // Confirm first split is focused and at top
    let top_byte_first_split = harness.top_byte();

    // Now scroll down with mouse wheel over the BOTTOM split (row 30 is in bottom half).
    // The focused split is the TOP one, so if the bug exists, the top split scrolls instead.
    for _ in 0..5 {
        harness.mouse_scroll_down(60, 30).unwrap();
    }

    // Check that the focused (top) split did NOT scroll
    let top_byte_first_after = harness.top_byte();
    assert_eq!(
        top_byte_first_after, top_byte_first_split,
        "Focused (top) split should NOT have scrolled when mouse was over bottom split"
    );

    // Switch to the bottom split and check it DID scroll
    next_split(&mut harness);
    let top_byte_second_after = harness.top_byte();
    assert_ne!(
        top_byte_second_after, top_byte_first_split,
        "Bottom split (under mouse pointer) should have scrolled"
    );
}