@@ -16,6 +16,89 @@ use regex::Regex;
1616use std:: io:: { self , BufRead , Write } ;
1717use std:: sync:: atomic:: Ordering ;
1818
19+ /// Parsed command with optional embedded input for a/i/c in global commands.
20+ struct GlobalCommand {
21+ /// The command text (e.g., "a", "s/foo/bar/", "d")
22+ command : String ,
23+ /// For a/i/c: the input text lines
24+ input_lines : Vec < String > ,
25+ }
26+
27+ /// Find the command character in a line (skipping address prefix).
28+ fn find_command_char ( line : & str ) -> Option < char > {
29+ // Skip leading whitespace and address components
30+ // Look for first command letter
31+ for ch in line. chars ( ) {
32+ if ch. is_ascii_alphabetic ( ) {
33+ return Some ( ch) ;
34+ }
35+ // Continue past address components
36+ if ch. is_ascii_digit ( )
37+ || ch == '.'
38+ || ch == '$'
39+ || ch == '\''
40+ || ch == '/'
41+ || ch == '?'
42+ || ch == '+'
43+ || ch == '-'
44+ || ch == ','
45+ || ch == ';'
46+ || ch. is_whitespace ( )
47+ {
48+ continue ;
49+ }
50+ // Stop at other characters
51+ break ;
52+ }
53+ None
54+ }
55+
56+ /// Parse a global command list string into individual commands.
57+ /// Handles a/i/c commands with embedded input.
58+ fn parse_global_command_list ( commands : & str ) -> Vec < GlobalCommand > {
59+ let lines: Vec < & str > = commands. split ( '\n' ) . collect ( ) ;
60+ let mut result = Vec :: new ( ) ;
61+ let mut i = 0 ;
62+
63+ while i < lines. len ( ) {
64+ let line = lines[ i] ;
65+ if line. is_empty ( ) {
66+ i += 1 ;
67+ continue ;
68+ }
69+
70+ // Check if this is an a, i, or c command (possibly with address prefix)
71+ let cmd_char = find_command_char ( line) ;
72+
73+ if matches ! ( cmd_char, Some ( 'a' ) | Some ( 'i' ) | Some ( 'c' ) ) {
74+ // Collect input lines until '.' or end of list
75+ let mut input_lines = Vec :: new ( ) ;
76+ i += 1 ;
77+ while i < lines. len ( ) {
78+ let input_line = lines[ i] ;
79+ if input_line == "." {
80+ i += 1 ;
81+ break ;
82+ }
83+ input_lines. push ( format ! ( "{}\n " , input_line) ) ;
84+ i += 1 ;
85+ }
86+ result. push ( GlobalCommand {
87+ command : line. to_string ( ) ,
88+ input_lines,
89+ } ) ;
90+ } else {
91+ result. push ( GlobalCommand {
92+ command : line. to_string ( ) ,
93+ input_lines : Vec :: new ( ) ,
94+ } ) ;
95+ i += 1 ;
96+ }
97+ }
98+
99+ result
100+ }
101+
19102/// The ed editor state.
20103pub struct Editor < R : BufRead , W : Write > {
21104 /// The text buffer
@@ -1065,6 +1148,34 @@ impl<R: BufRead, W: Write> Editor<R, W> {
10651148 Ok ( ( ) )
10661149 }
10671150
1151+ /// Execute an a/i/c command with pre-supplied input lines (for global commands).
1152+ /// This is used when a/i/c commands appear in g/v command lists with embedded input.
1153+ fn execute_input_command_with_lines (
1154+ & mut self ,
1155+ command : & str ,
1156+ input_lines : & [ String ] ,
1157+ ) -> EdResult < ( ) > {
1158+ // Parse the command to get the address and command type
1159+ match parse ( command) {
1160+ Ok ( Command :: Append ( addr) ) => {
1161+ let line_num = self . resolve_address ( & addr) ?;
1162+ self . buf . append ( line_num, input_lines) ;
1163+ Ok ( ( ) )
1164+ }
1165+ Ok ( Command :: Insert ( addr) ) => {
1166+ let line_num = self . resolve_address ( & addr) ?;
1167+ self . buf . insert ( line_num, input_lines) ;
1168+ Ok ( ( ) )
1169+ }
1170+ Ok ( Command :: Change ( addr1, addr2) ) => {
1171+ let ( start, end) = self . resolve_range ( & addr1, & addr2) ?;
1172+ self . buf . change ( start, end, input_lines) ?;
1173+ Ok ( ( ) )
1174+ }
1175+ _ => Err ( EdError :: Generic ( "invalid command" . to_string ( ) ) ) ,
1176+ }
1177+ }
1178+
10681179 /// Execute a global command.
10691180 fn execute_global (
10701181 & mut self ,
@@ -1116,96 +1227,135 @@ impl<R: BufRead, W: Write> Editor<R, W> {
11161227 let original_cur_line = self . buf . cur_line ;
11171228 let mut last_successful_line = original_cur_line;
11181229
1230+ // Parse the command list into individual commands (handles a/i/c with embedded input)
1231+ let global_commands = parse_global_command_list ( commands) ;
1232+
11191233 // For delete commands, process in reverse order to avoid line number shifts
1120- let is_delete = commands . trim ( ) == "d" ;
1121- if is_delete {
1234+ let is_delete_only = global_commands . len ( ) == 1 && global_commands [ 0 ] . command . trim ( ) == "d" ;
1235+ if is_delete_only {
11221236 matching_lines. reverse ( ) ;
11231237 }
11241238
1239+ // Track line offset for insert/append operations
1240+ // When we insert N lines, subsequent matching line numbers shift by N
1241+ let mut line_offset: isize = 0 ;
1242+
11251243 // Execute commands on each matching line
11261244 for line_num in matching_lines {
1127- // Adjust line_num for lines that may have been deleted
1128- // For non-delete commands, we need to track line number changes
1129- let adjusted_line = if is_delete {
1245+ // Adjust line_num for lines that may have been deleted or shifted
1246+ let adjusted_line = if is_delete_only {
11301247 line_num
11311248 } else {
1132- // For other commands, check if line still exists
1133- if line_num > self . buf . line_count ( ) {
1249+ // Apply offset for lines that shifted due to insertions
1250+ let shifted = ( line_num as isize + line_offset) as usize ;
1251+ // Check if line still exists
1252+ if shifted > self . buf . line_count ( ) || shifted == 0 {
11341253 continue ;
11351254 }
1136- line_num
1255+ shifted
11371256 } ;
11381257
11391258 // Set current line
11401259 if self . buf . set_cur_line ( adjusted_line) . is_err ( ) {
11411260 continue ; // Line may have been deleted
11421261 }
11431262
1144- // Parse and execute the command
1145- match commands. trim ( ) {
1146- "p" => {
1147- if let Some ( line) = self . buf . get_line ( self . buf . cur_line ) {
1148- write ! ( self . writer, "{}" , line) ?;
1263+ // Track line count before command to detect insertions
1264+ let line_count_before = self . buf . line_count ( ) ;
1265+
1266+ // Execute each command in the command list
1267+ for gc in & global_commands {
1268+ if gc. command . is_empty ( ) {
1269+ continue ;
1270+ }
1271+
1272+ let cmd = gc. command . trim ( ) ;
1273+
1274+ // Check if this is a/i/c with embedded input
1275+ let cmd_char = find_command_char ( cmd) ;
1276+ if matches ! ( cmd_char, Some ( 'a' ) | Some ( 'i' ) | Some ( 'c' ) )
1277+ && !gc. input_lines . is_empty ( )
1278+ {
1279+ // Execute a/i/c with embedded input directly
1280+ if let Err ( e) = self . execute_input_command_with_lines ( cmd, & gc. input_lines ) {
1281+ self . buf . end_global ( ) ;
1282+ return Err ( e) ;
11491283 }
11501284 last_successful_line = self . buf . cur_line ;
1285+ continue ;
11511286 }
1152- "d" => {
1153- let cur = self . buf . cur_line ;
1154- if self . buf . delete ( cur, cur) . is_ok ( ) {
1287+
1288+ // Handle common simple commands inline for performance
1289+ match cmd {
1290+ "p" => {
1291+ if let Some ( line) = self . buf . get_line ( self . buf . cur_line ) {
1292+ write ! ( self . writer, "{}" , line) ?;
1293+ }
11551294 last_successful_line = self . buf . cur_line ;
11561295 }
1157- }
1158- "n" => {
1159- if let Some ( line ) = self . buf . get_line ( self . buf . cur_line ) {
1160- let content = line . trim_end_matches ( '\n' ) ;
1161- writeln ! ( self . writer , "{:6} \t {}" , self . buf . cur_line , content ) ? ;
1296+ "d" => {
1297+ let cur = self . buf . cur_line ;
1298+ if self . buf . delete ( cur , cur ) . is_ok ( ) {
1299+ last_successful_line = self . buf . cur_line ;
1300+ }
11621301 }
1163- last_successful_line = self . buf . cur_line ;
1164- }
1165- "l" => {
1166- if let Some ( line) = self . buf . get_line ( self . buf . cur_line ) {
1167- let content = line. trim_end_matches ( '\n' ) ;
1168- for ch in content. chars ( ) {
1169- match ch {
1170- '\\' => write ! ( self . writer, "\\ \\ " ) ?,
1171- '\x07' => write ! ( self . writer, "\\ a" ) ?,
1172- '\x08' => write ! ( self . writer, "\\ b" ) ?,
1173- '\x0c' => write ! ( self . writer, "\\ f" ) ?,
1174- '\r' => write ! ( self . writer, "\\ r" ) ?,
1175- '\t' => write ! ( self . writer, "\\ t" ) ?,
1176- '\x0b' => write ! ( self . writer, "\\ v" ) ?,
1177- '$' => write ! ( self . writer, "\\ $" ) ?,
1178- c if c. is_control ( ) || !c. is_ascii ( ) => {
1179- for byte in c. to_string ( ) . as_bytes ( ) {
1180- write ! ( self . writer, "\\ {:03o}" , byte) ?;
1302+ "n" => {
1303+ if let Some ( line) = self . buf . get_line ( self . buf . cur_line ) {
1304+ let content = line. trim_end_matches ( '\n' ) ;
1305+ writeln ! ( self . writer, "{:6}\t {}" , self . buf. cur_line, content) ?;
1306+ }
1307+ last_successful_line = self . buf . cur_line ;
1308+ }
1309+ "l" => {
1310+ if let Some ( line) = self . buf . get_line ( self . buf . cur_line ) {
1311+ let content = line. trim_end_matches ( '\n' ) ;
1312+ for ch in content. chars ( ) {
1313+ match ch {
1314+ '\\' => write ! ( self . writer, "\\ \\ " ) ?,
1315+ '\x07' => write ! ( self . writer, "\\ a" ) ?,
1316+ '\x08' => write ! ( self . writer, "\\ b" ) ?,
1317+ '\x0c' => write ! ( self . writer, "\\ f" ) ?,
1318+ '\r' => write ! ( self . writer, "\\ r" ) ?,
1319+ '\t' => write ! ( self . writer, "\\ t" ) ?,
1320+ '\x0b' => write ! ( self . writer, "\\ v" ) ?,
1321+ '$' => write ! ( self . writer, "\\ $" ) ?,
1322+ c if c. is_control ( ) || !c. is_ascii ( ) => {
1323+ for byte in c. to_string ( ) . as_bytes ( ) {
1324+ write ! ( self . writer, "\\ {:03o}" , byte) ?;
1325+ }
11811326 }
1327+ c => write ! ( self . writer, "{}" , c) ?,
11821328 }
1183- c => write ! ( self . writer, "{}" , c) ?,
11841329 }
1330+ writeln ! ( self . writer, "$" ) ?;
11851331 }
1186- writeln ! ( self . writer , "$" ) ? ;
1332+ last_successful_line = self . buf . cur_line ;
11871333 }
1188- last_successful_line = self . buf . cur_line ;
1189- }
1190- cmd if !cmd. is_empty ( ) => {
1191- // Try to parse and execute other commands
1192- match parse ( cmd) {
1193- Ok ( parsed_cmd) => {
1194- if let Err ( e) = self . execute_command ( parsed_cmd) {
1195- // Abort global on error and propagate
1334+ _ => {
1335+ // Try to parse and execute other commands
1336+ match parse ( cmd) {
1337+ Ok ( parsed_cmd) => {
1338+ if let Err ( e) = self . execute_command ( parsed_cmd) {
1339+ // Abort global on error and propagate
1340+ self . buf . end_global ( ) ;
1341+ return Err ( e) ;
1342+ }
1343+ last_successful_line = self . buf . cur_line ;
1344+ }
1345+ Err ( e) => {
1346+ // Abort global on parse error
11961347 self . buf . end_global ( ) ;
11971348 return Err ( e) ;
11981349 }
1199- last_successful_line = self . buf . cur_line ;
1200- }
1201- Err ( e) => {
1202- // Abort global on parse error
1203- self . buf . end_global ( ) ;
1204- return Err ( e) ;
12051350 }
12061351 }
12071352 }
1208- _ => { }
1353+ }
1354+
1355+ // Update line offset based on how many lines were added/removed
1356+ if !is_delete_only {
1357+ let line_count_after = self . buf . line_count ( ) ;
1358+ line_offset += line_count_after as isize - line_count_before as isize ;
12091359 }
12101360 }
12111361
0 commit comments