Skip to content

Commit 692bcc5

Browse files
jgarzikclaude
andcommitted
vi: Code cleanups - add char_index_at_byte helper and consolidate patterns
- Add char_index_at_byte() helper to buffer/line.rs for finding character index at a given byte offset in UTF-8 strings - Refactor repetitive char_indices() iteration patterns in motion.rs, text_object.rs, operator.rs, and insert.rs to use the new helper - Consolidate 6 word motion functions (w/W/e/E/b/B) into thin wrappers around move_forward_by() and move_backward_by() generic helpers - Add ViPtySession helper struct for PTY tests, reducing boilerplate in 6 test functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 5a62b2e commit 692bcc5

File tree

7 files changed

+179
-391
lines changed

7 files changed

+179
-391
lines changed

editors/tests/pty/mod.rs

Lines changed: 94 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use portable_pty::{native_pty_system, CommandBuilder, PtySize};
77
use std::io::{Read, Write};
8+
use std::path::Path;
89
use std::thread;
910
use std::time::Duration;
1011
use tempfile::tempdir;
@@ -41,47 +42,74 @@ fn wait_with_timeout(child: &mut Box<dyn portable_pty::Child + Send + Sync>, tim
4142
}
4243
}
4344

45+
/// Helper struct for PTY-based vi tests.
46+
struct ViPtySession {
47+
child: Box<dyn portable_pty::Child + Send + Sync>,
48+
writer: Box<dyn Write + Send>,
49+
#[allow(dead_code)]
50+
reader_thread: thread::JoinHandle<()>,
51+
}
52+
53+
impl ViPtySession {
54+
/// Spawn vi with the given file in a PTY of the specified size.
55+
fn new(file_path: &Path, rows: u16, cols: u16) -> Self {
56+
let pty_system = native_pty_system();
57+
let pair = pty_system
58+
.openpty(PtySize {
59+
rows,
60+
cols,
61+
pixel_width: 0,
62+
pixel_height: 0,
63+
})
64+
.unwrap();
65+
66+
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
67+
cmd.arg(file_path);
68+
cmd.env("TERM", "vt100");
69+
70+
let child = pair.slave.spawn_command(cmd).unwrap();
71+
drop(pair.slave);
72+
73+
let reader = pair.master.try_clone_reader().unwrap();
74+
let reader_thread = spawn_reader_drain(reader);
75+
let writer = pair.master.take_writer().unwrap();
76+
77+
Self {
78+
child,
79+
writer,
80+
reader_thread,
81+
}
82+
}
83+
84+
/// Send key sequence to vi.
85+
fn keys(&mut self, s: &str) {
86+
write_keys(&mut self.writer, s);
87+
}
88+
89+
/// Sleep for the given number of milliseconds.
90+
fn sleep_ms(&self, ms: u64) {
91+
thread::sleep(Duration::from_millis(ms));
92+
}
93+
94+
/// Wait for vi to exit with a timeout.
95+
fn wait(mut self) {
96+
wait_with_timeout(&mut self.child, Duration::from_secs(5));
97+
}
98+
}
99+
44100
/// Test: Insert text and save file.
45101
#[test]
46102
fn test_pty_vi_insert_and_save() {
47103
let td = tempdir().unwrap();
48104
let file_path = td.path().join("test.txt");
49105
std::fs::write(&file_path, "").unwrap();
50106

51-
let pty_system = native_pty_system();
52-
let pair = pty_system
53-
.openpty(PtySize {
54-
rows: 25,
55-
cols: 80,
56-
pixel_width: 0,
57-
pixel_height: 0,
58-
})
59-
.unwrap();
60-
61-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
62-
cmd.arg(&file_path);
63-
cmd.env("TERM", "vt100");
64-
65-
let mut child = pair.slave.spawn_command(cmd).unwrap();
66-
drop(pair.slave);
67-
68-
let reader = pair.master.try_clone_reader().unwrap();
69-
let _reader_thread = spawn_reader_drain(reader);
70-
let mut writer = pair.master.take_writer().unwrap();
71-
72-
// Wait for vi startup
73-
thread::sleep(Duration::from_millis(500));
74-
75-
// Insert "Hello" and save
76-
write_keys(&mut writer, "i");
77-
thread::sleep(Duration::from_millis(50));
78-
write_keys(&mut writer, "Hello");
79-
thread::sleep(Duration::from_millis(50));
80-
write_keys(&mut writer, "\x1b"); // ESC
81-
thread::sleep(Duration::from_millis(100));
82-
write_keys(&mut writer, ":wq\r");
83-
84-
wait_with_timeout(&mut child, Duration::from_secs(5));
107+
let mut vi = ViPtySession::new(&file_path, 25, 80);
108+
vi.sleep_ms(500);
109+
vi.keys("iHello\x1b");
110+
vi.sleep_ms(100);
111+
vi.keys(":wq\r");
112+
vi.wait();
85113

86114
let contents = std::fs::read_to_string(&file_path).unwrap();
87115
assert_eq!(contents.trim(), "Hello");
@@ -94,36 +122,12 @@ fn test_pty_vi_quit_no_save() {
94122
let file_path = td.path().join("test.txt");
95123
std::fs::write(&file_path, "original\n").unwrap();
96124

97-
let pty_system = native_pty_system();
98-
let pair = pty_system
99-
.openpty(PtySize {
100-
rows: 25,
101-
cols: 80,
102-
pixel_width: 0,
103-
pixel_height: 0,
104-
})
105-
.unwrap();
106-
107-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
108-
cmd.arg(&file_path);
109-
cmd.env("TERM", "vt100");
110-
111-
let mut child = pair.slave.spawn_command(cmd).unwrap();
112-
drop(pair.slave);
113-
114-
let reader = pair.master.try_clone_reader().unwrap();
115-
let _reader_thread = spawn_reader_drain(reader);
116-
let mut writer = pair.master.take_writer().unwrap();
117-
118-
// Wait for vi startup
119-
thread::sleep(Duration::from_millis(500));
120-
121-
// Delete line and quit without saving
122-
write_keys(&mut writer, "dd");
123-
thread::sleep(Duration::from_millis(100));
124-
write_keys(&mut writer, ":q!\r");
125-
126-
wait_with_timeout(&mut child, Duration::from_secs(5));
125+
let mut vi = ViPtySession::new(&file_path, 25, 80);
126+
vi.sleep_ms(500);
127+
vi.keys("dd");
128+
vi.sleep_ms(100);
129+
vi.keys(":q!\r");
130+
vi.wait();
127131

128132
let contents = std::fs::read_to_string(&file_path).unwrap();
129133
assert_eq!(contents, "original\n");
@@ -136,40 +140,12 @@ fn test_pty_vi_multiple_lines() {
136140
let file_path = td.path().join("test.txt");
137141
std::fs::write(&file_path, "").unwrap();
138142

139-
let pty_system = native_pty_system();
140-
let pair = pty_system
141-
.openpty(PtySize {
142-
rows: 25,
143-
cols: 80,
144-
pixel_width: 0,
145-
pixel_height: 0,
146-
})
147-
.unwrap();
148-
149-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
150-
cmd.arg(&file_path);
151-
cmd.env("TERM", "vt100");
152-
153-
let mut child = pair.slave.spawn_command(cmd).unwrap();
154-
drop(pair.slave);
155-
156-
let reader = pair.master.try_clone_reader().unwrap();
157-
let _reader_thread = spawn_reader_drain(reader);
158-
let mut writer = pair.master.take_writer().unwrap();
159-
160-
// Wait for vi startup
161-
thread::sleep(Duration::from_millis(500));
162-
163-
// Insert three lines
164-
write_keys(&mut writer, "i");
165-
thread::sleep(Duration::from_millis(50));
166-
write_keys(&mut writer, "Line1\rLine2\rLine3");
167-
thread::sleep(Duration::from_millis(50));
168-
write_keys(&mut writer, "\x1b"); // ESC
169-
thread::sleep(Duration::from_millis(100));
170-
write_keys(&mut writer, ":wq\r");
171-
172-
wait_with_timeout(&mut child, Duration::from_secs(5));
143+
let mut vi = ViPtySession::new(&file_path, 25, 80);
144+
vi.sleep_ms(500);
145+
vi.keys("iLine1\rLine2\rLine3\x1b");
146+
vi.sleep_ms(100);
147+
vi.keys(":wq\r");
148+
vi.wait();
173149

174150
let contents = std::fs::read_to_string(&file_path).unwrap();
175151
let lines: Vec<&str> = contents.lines().collect();
@@ -186,38 +162,10 @@ fn test_pty_vi_delete_and_save() {
186162
let file_path = td.path().join("test.txt");
187163
std::fs::write(&file_path, "Line1\nLine2\nLine3\n").unwrap();
188164

189-
let pty_system = native_pty_system();
190-
let pair = pty_system
191-
.openpty(PtySize {
192-
rows: 25,
193-
cols: 80,
194-
pixel_width: 0,
195-
pixel_height: 0,
196-
})
197-
.unwrap();
198-
199-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
200-
cmd.arg(&file_path);
201-
cmd.env("TERM", "vt100");
202-
203-
let mut child = pair.slave.spawn_command(cmd).unwrap();
204-
drop(pair.slave);
205-
206-
let reader = pair.master.try_clone_reader().unwrap();
207-
let _reader_thread = spawn_reader_drain(reader);
208-
let mut writer = pair.master.take_writer().unwrap();
209-
210-
// Wait for vi startup
211-
thread::sleep(Duration::from_millis(500));
212-
213-
// Move down one line (j), delete line (dd), save and quit
214-
write_keys(&mut writer, "j");
215-
thread::sleep(Duration::from_millis(50));
216-
write_keys(&mut writer, "dd");
217-
thread::sleep(Duration::from_millis(100));
218-
write_keys(&mut writer, ":wq\r");
219-
220-
wait_with_timeout(&mut child, Duration::from_secs(5));
165+
let mut vi = ViPtySession::new(&file_path, 25, 80);
166+
vi.sleep_ms(500);
167+
vi.keys("jdd:wq\r");
168+
vi.wait();
221169

222170
let contents = std::fs::read_to_string(&file_path).unwrap();
223171
let lines: Vec<&str> = contents.lines().collect();
@@ -235,38 +183,12 @@ fn test_pty_vi_utf8_display() {
235183
// Cyrillic text "Привет мир" = "Hello world" - each Cyrillic char is 2 bytes
236184
std::fs::write(&file_path, "Привет мир\n").unwrap();
237185

238-
let pty_system = native_pty_system();
239-
let pair = pty_system
240-
.openpty(PtySize {
241-
rows: 10,
242-
cols: 20, // Narrow terminal to force truncation
243-
pixel_width: 0,
244-
pixel_height: 0,
245-
})
246-
.unwrap();
247-
248-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
249-
cmd.arg(&file_path);
250-
cmd.env("TERM", "vt100");
251-
252-
let mut child = pair.slave.spawn_command(cmd).unwrap();
253-
drop(pair.slave);
254-
255-
let reader = pair.master.try_clone_reader().unwrap();
256-
let _reader_thread = spawn_reader_drain(reader);
257-
let mut writer = pair.master.take_writer().unwrap();
258-
259-
// Wait for vi startup
260-
thread::sleep(Duration::from_millis(500));
261-
262-
// Move cursor right a few times (exercises display with UTF-8)
263-
write_keys(&mut writer, "lll");
264-
thread::sleep(Duration::from_millis(100));
265-
266-
// Quit without saving
267-
write_keys(&mut writer, ":q!\r");
268-
269-
wait_with_timeout(&mut child, Duration::from_secs(5));
186+
let mut vi = ViPtySession::new(&file_path, 10, 20); // Narrow terminal to force truncation
187+
vi.sleep_ms(500);
188+
vi.keys("lll");
189+
vi.sleep_ms(100);
190+
vi.keys(":q!\r");
191+
vi.wait();
270192

271193
// If we got here without panic, the test passed
272194
let contents = std::fs::read_to_string(&file_path).unwrap();
@@ -281,46 +203,16 @@ fn test_pty_vi_set_number() {
281203
let file_path = td.path().join("test_number.txt");
282204
std::fs::write(&file_path, "line1\nline2\nline3\n").unwrap();
283205

284-
let pty_system = native_pty_system();
285-
let pair = pty_system
286-
.openpty(PtySize {
287-
rows: 25,
288-
cols: 80,
289-
pixel_width: 0,
290-
pixel_height: 0,
291-
})
292-
.unwrap();
293-
294-
let mut cmd = CommandBuilder::new(env!("CARGO_BIN_EXE_vi"));
295-
cmd.arg(&file_path);
296-
cmd.env("TERM", "vt100");
297-
298-
let mut child = pair.slave.spawn_command(cmd).unwrap();
299-
drop(pair.slave);
300-
301-
let reader = pair.master.try_clone_reader().unwrap();
302-
let _reader_thread = spawn_reader_drain(reader);
303-
let mut writer = pair.master.take_writer().unwrap();
304-
305-
// Wait for vi startup
306-
thread::sleep(Duration::from_millis(500));
307-
308-
// Enable line numbers
309-
write_keys(&mut writer, ":set number\r");
310-
thread::sleep(Duration::from_millis(200));
311-
312-
// Move cursor to verify positioning works with line numbers
313-
write_keys(&mut writer, "jjk");
314-
thread::sleep(Duration::from_millis(100));
315-
316-
// Disable line numbers
317-
write_keys(&mut writer, ":set nonumber\r");
318-
thread::sleep(Duration::from_millis(100));
319-
320-
// Quit without saving
321-
write_keys(&mut writer, ":q!\r");
322-
323-
wait_with_timeout(&mut child, Duration::from_secs(5));
206+
let mut vi = ViPtySession::new(&file_path, 25, 80);
207+
vi.sleep_ms(500);
208+
vi.keys(":set number\r");
209+
vi.sleep_ms(200);
210+
vi.keys("jjk");
211+
vi.sleep_ms(100);
212+
vi.keys(":set nonumber\r");
213+
vi.sleep_ms(100);
214+
vi.keys(":q!\r");
215+
vi.wait();
324216

325217
// File should be unchanged
326218
let contents = std::fs::read_to_string(&file_path).unwrap();

0 commit comments

Comments
 (0)