@@ -54,6 +54,8 @@ pub struct Editor<R: BufRead, W: Write> {
5454 pub should_quit : bool ,
5555 /// Quit warning given (for modified buffer)
5656 quit_warning_given : bool ,
57+ /// Previous command for & in interactive global (G/V)
58+ last_interactive_cmd : Option < String > ,
5759}
5860
5961impl < R : BufRead , W : Write > Editor < R , W > {
@@ -78,6 +80,7 @@ impl<R: BufRead, W: Write> Editor<R, W> {
7880 pending_command : None ,
7981 should_quit : false ,
8082 quit_warning_given : false ,
83+ last_interactive_cmd : None ,
8184 }
8285 }
8386
@@ -308,14 +311,57 @@ impl<R: BufRead, W: Write> Editor<R, W> {
308311 }
309312 }
310313
314+ /// Check if a line has a continuation (ends with unescaped backslash).
315+ /// Returns (trimmed_line_without_backslash, needs_continuation).
316+ fn check_line_continuation ( line : & str ) -> ( String , bool ) {
317+ let trimmed = line. trim_end_matches ( '\n' ) ;
318+
319+ // Count trailing backslashes
320+ let mut backslash_count = 0 ;
321+ for ch in trimmed. chars ( ) . rev ( ) {
322+ if ch == '\\' {
323+ backslash_count += 1 ;
324+ } else {
325+ break ;
326+ }
327+ }
328+
329+ // Odd number of trailing backslashes = continuation
330+ if backslash_count > 0 && backslash_count % 2 == 1 {
331+ // Remove the trailing backslash and indicate continuation needed
332+ let without_backslash = & trimmed[ ..trimmed. len ( ) - 1 ] ;
333+ ( without_backslash. to_string ( ) , true )
334+ } else {
335+ ( trimmed. to_string ( ) , false )
336+ }
337+ }
338+
311339 /// Process a line of input.
312340 pub fn process_line ( & mut self , line : & str ) -> io:: Result < bool > {
313341 if self . in_input_mode {
314342 return self . process_input_line ( line) ;
315343 }
316344
317- let trimmed = line. trim_end ( ) ;
318- match parse ( trimmed) {
345+ // Handle line continuation (backslash-newline)
346+ let ( first_part, needs_continuation) = Self :: check_line_continuation ( line) ;
347+ let full_line = if needs_continuation {
348+ let mut accumulated = first_part;
349+ // Keep reading lines until we get one without continuation
350+ while let Some ( next_line) = self . read_line ( ) ? {
351+ let ( part, more) = Self :: check_line_continuation ( & next_line) ;
352+ // Join with embedded newline (the backslash-newline becomes \n in content)
353+ accumulated. push ( '\n' ) ;
354+ accumulated. push_str ( & part) ;
355+ if !more {
356+ break ;
357+ }
358+ }
359+ accumulated
360+ } else {
361+ first_part
362+ } ;
363+
364+ match parse ( & full_line) {
319365 Ok ( cmd) => {
320366 if let Err ( e) = self . execute_command ( cmd) {
321367 self . print_error ( & e) ?;
@@ -432,11 +478,10 @@ impl<R: BufRead, W: Write> Editor<R, W> {
432478 self . execute_global ( addr1, addr2, & pattern, & commands, true )
433479 }
434480 Command :: GlobalInteractive ( addr1, addr2, pattern) => {
435- // For now, treat as non-interactive with 'p' command
436- self . execute_global ( addr1, addr2, & pattern, "p" , false )
481+ self . execute_global_interactive ( addr1, addr2, & pattern, false )
437482 }
438483 Command :: GlobalNotInteractive ( addr1, addr2, pattern) => {
439- self . execute_global ( addr1, addr2, & pattern, "p" , true )
484+ self . execute_global_interactive ( addr1, addr2, & pattern, true )
440485 }
441486 Command :: Help => {
442487 if let Some ( ref err) = self . last_error {
@@ -747,6 +792,7 @@ impl<R: BufRead, W: Write> Editor<R, W> {
747792
748793 /// Convert POSIX ed replacement string to regex crate format.
749794 /// POSIX: & -> matched string, \1-\9 -> back-references, \& -> literal &
795+ /// POSIX: \<newline> -> split line at this point
750796 /// Regex crate: $0 -> matched string, $1-$9 -> back-references, $$ -> literal $
751797 fn convert_replacement ( & self , repl : & str ) -> String {
752798 let mut result = String :: new ( ) ;
@@ -769,6 +815,12 @@ impl<R: BufRead, W: Write> Editor<R, W> {
769815 result. push ( '\\' ) ;
770816 chars. next ( ) ;
771817 }
818+ '\n' => {
819+ // POSIX: \<newline> causes line split
820+ // Preserve the newline in replacement
821+ result. push ( '\n' ) ;
822+ chars. next ( ) ;
823+ }
772824 _ => {
773825 result. push ( '\\' ) ;
774826 }
@@ -850,9 +902,14 @@ impl<R: BufRead, W: Write> Editor<R, W> {
850902
851903 let mut any_match = false ;
852904 let mut last_matched_line = start;
905+ // Track offset when lines are split (inserted lines shift subsequent line numbers)
906+ let mut offset: usize = 0 ;
853907
854908 for i in start..=end {
855- if let Some ( line) = self . buf . get_line ( i) {
909+ // Compute actual buffer position accounting for previously inserted lines
910+ let actual_line = i + offset;
911+
912+ if let Some ( line) = self . buf . get_line ( actual_line) {
856913 let line_content = line. clone ( ) ;
857914 let new_line = if global {
858915 re. replace_all ( & line_content, regex_repl. as_str ( ) )
@@ -874,14 +931,38 @@ impl<R: BufRead, W: Write> Editor<R, W> {
874931
875932 if new_line != line_content {
876933 any_match = true ;
877- last_matched_line = i;
878- // Ensure line ends with newline
879- let final_line = if new_line. ends_with ( '\n' ) {
880- new_line
934+
935+ // Check for internal newlines (line splitting)
936+ // Strip trailing newline first, then check for remaining newlines
937+ let content = new_line. trim_end_matches ( '\n' ) ;
938+ if content. contains ( '\n' ) {
939+ // POSIX: Line splitting via \<newline> is not allowed in g/v commands
940+ if self . buf . is_in_global ( ) {
941+ return Err ( EdError :: Generic (
942+ "cannot split lines in global command" . to_string ( ) ,
943+ ) ) ;
944+ }
945+
946+ // Split into multiple lines, each ending with newline
947+ let lines: Vec < String > =
948+ content. split ( '\n' ) . map ( |s| format ! ( "{}\n " , s) ) . collect ( ) ;
949+
950+ let num_new_lines = lines. len ( ) ;
951+ self . buf . change ( actual_line, actual_line, & lines) ?;
952+ // Update last_matched_line to point to the last inserted line
953+ last_matched_line = actual_line + num_new_lines - 1 ;
954+ // Track extra lines inserted (we replaced 1 line with num_new_lines)
955+ offset += num_new_lines - 1 ;
881956 } else {
882- format ! ( "{}\n " , new_line)
883- } ;
884- self . buf . change ( i, i, & [ final_line] ) ?;
957+ // No line splitting, just replace the single line
958+ let final_line = if new_line. ends_with ( '\n' ) {
959+ new_line
960+ } else {
961+ format ! ( "{}\n " , new_line)
962+ } ;
963+ self . buf . change ( actual_line, actual_line, & [ final_line] ) ?;
964+ last_matched_line = actual_line;
965+ }
885966 }
886967 }
887968 }
@@ -1108,10 +1189,20 @@ impl<R: BufRead, W: Write> Editor<R, W> {
11081189 }
11091190 cmd if !cmd. is_empty ( ) => {
11101191 // Try to parse and execute other commands
1111- if let Ok ( parsed_cmd) = parse ( cmd) {
1112- if self . execute_command ( parsed_cmd) . is_ok ( ) {
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
1196+ self . buf . end_global ( ) ;
1197+ return Err ( e) ;
1198+ }
11131199 last_successful_line = self . buf . cur_line ;
11141200 }
1201+ Err ( e) => {
1202+ // Abort global on parse error
1203+ self . buf . end_global ( ) ;
1204+ return Err ( e) ;
1205+ }
11151206 }
11161207 }
11171208 _ => { }
@@ -1127,6 +1218,152 @@ impl<R: BufRead, W: Write> Editor<R, W> {
11271218 Ok ( ( ) )
11281219 }
11291220
1221+ /// Execute an interactive global command (G or V).
1222+ /// For each matching line: print it, read a command from user, execute.
1223+ fn execute_global_interactive (
1224+ & mut self ,
1225+ addr1 : Address ,
1226+ addr2 : Address ,
1227+ pattern : & str ,
1228+ invert : bool ,
1229+ ) -> EdResult < ( ) > {
1230+ let ( start, end) = self . resolve_range ( & addr1, & addr2) ?;
1231+
1232+ // Use previous pattern if empty
1233+ let pat = if pattern. is_empty ( ) {
1234+ self . last_pattern
1235+ . as_ref ( )
1236+ . ok_or ( EdError :: NoPreviousPattern ) ?
1237+ . clone ( )
1238+ } else {
1239+ pattern. to_string ( )
1240+ } ;
1241+
1242+ self . last_pattern = Some ( pat. clone ( ) ) ;
1243+
1244+ let re = Regex :: new ( & pat) . map_err ( |e| EdError :: Syntax ( e. to_string ( ) ) ) ?;
1245+
1246+ // Collect matching lines first
1247+ let mut matching_lines = Vec :: new ( ) ;
1248+ for i in start..=end {
1249+ if let Some ( line) = self . buf . get_line ( i) {
1250+ let matches = re. is_match ( line) ;
1251+ if matches != invert {
1252+ matching_lines. push ( i) ;
1253+ }
1254+ }
1255+ }
1256+
1257+ if matching_lines. is_empty ( ) {
1258+ return Ok ( ( ) ) ;
1259+ }
1260+
1261+ // Save one undo record for the entire operation
1262+ self . buf . begin_global ( ) ;
1263+
1264+ let mut idx = 0 ;
1265+ while idx < matching_lines. len ( ) {
1266+ // Check for SIGINT
1267+ if crate :: SIGINT_RECEIVED . swap ( false , Ordering :: SeqCst ) {
1268+ self . buf . end_global ( ) ;
1269+ writeln ! ( self . writer, "?" ) ?;
1270+ self . last_error = Some ( "Interrupt" . to_string ( ) ) ;
1271+ return Ok ( ( ) ) ;
1272+ }
1273+
1274+ let line_num = matching_lines[ idx] ;
1275+
1276+ // Check if line still exists (may have been deleted by previous commands)
1277+ if line_num == 0 || line_num > self . buf . line_count ( ) {
1278+ idx += 1 ;
1279+ continue ;
1280+ }
1281+
1282+ // Print the line
1283+ if let Some ( line) = self . buf . get_line ( line_num) {
1284+ write ! ( self . writer, "{}" , line) ?;
1285+ }
1286+ self . writer . flush ( ) ?;
1287+
1288+ // Read command from user
1289+ let cmd_line = match self . read_line ( ) ? {
1290+ Some ( l) => l,
1291+ None => break , // EOF
1292+ } ;
1293+
1294+ let trimmed = cmd_line. trim_end_matches ( '\n' ) ;
1295+
1296+ // Empty line = skip, no action
1297+ if trimmed. is_empty ( ) {
1298+ idx += 1 ;
1299+ continue ;
1300+ }
1301+
1302+ // & = repeat previous command
1303+ let cmd_str = if trimmed == "&" {
1304+ match & self . last_interactive_cmd {
1305+ Some ( prev) => prev. clone ( ) ,
1306+ None => {
1307+ // No previous command
1308+ idx += 1 ;
1309+ continue ;
1310+ }
1311+ }
1312+ } else {
1313+ self . last_interactive_cmd = Some ( trimmed. to_string ( ) ) ;
1314+ trimmed. to_string ( )
1315+ } ;
1316+
1317+ // Set current line and execute
1318+ if self . buf . set_cur_line ( line_num) . is_err ( ) {
1319+ idx += 1 ;
1320+ continue ;
1321+ }
1322+
1323+ // Track line count before and after to adjust remaining line numbers
1324+ let lines_before = self . buf . line_count ( ) ;
1325+
1326+ match parse ( & cmd_str) {
1327+ Ok ( cmd) => {
1328+ // Check for forbidden commands
1329+ if matches ! (
1330+ cmd,
1331+ Command :: Global ( ..)
1332+ | Command :: GlobalNot ( ..)
1333+ | Command :: GlobalInteractive ( ..)
1334+ | Command :: GlobalNotInteractive ( ..)
1335+ | Command :: Shell ( ..)
1336+ ) {
1337+ writeln ! ( self . writer, "?" ) ?;
1338+ self . last_error = Some ( "invalid command" . to_string ( ) ) ;
1339+ } else if let Err ( e) = self . execute_command ( cmd) {
1340+ self . print_error ( & e) ?;
1341+ }
1342+ }
1343+ Err ( e) => {
1344+ self . print_error ( & e) ?;
1345+ }
1346+ }
1347+
1348+ // Adjust remaining line numbers if lines were deleted or added
1349+ let lines_after = self . buf . line_count ( ) ;
1350+ if lines_after != lines_before {
1351+ let diff = lines_after as isize - lines_before as isize ;
1352+ // Adjust all remaining line numbers (those after current position)
1353+ for remaining_line in & mut matching_lines[ ( idx + 1 ) ..] {
1354+ if * remaining_line > line_num {
1355+ * remaining_line = ( * remaining_line as isize + diff) as usize ;
1356+ }
1357+ }
1358+ }
1359+
1360+ idx += 1 ;
1361+ }
1362+
1363+ self . buf . end_global ( ) ;
1364+ Ok ( ( ) )
1365+ }
1366+
11301367 /// Check for and handle SIGINT signal.
11311368 /// Returns true if SIGINT was received and handled.
11321369 fn check_sigint ( & mut self ) -> io:: Result < bool > {
0 commit comments