Skip to content

Conversation

@jgarzik
Copy link
Contributor

@jgarzik jgarzik commented Dec 20, 2025

No description provided.

Phase 0 and Phase 1 are complete. Here's what was accomplished:

  Summary

  Phase 0: Test Structure Migration

  - Created editors/tests/editors-tests.rs harness with only mod statements (following CONTRIBUTING.md pattern)
  - Migrated ex_tests.rs → tests/ex/mod.rs
  - Migrated integration_tests.rs → tests/integration/mod.rs
  - Migrated headless_editor_tests.rs → tests/headless/mod.rs
  - Deleted old standalone test files

  Phase 1: ed Editor Implementation

  Created a working POSIX ed line editor with:

  Files created:
  - editors/ed_main.rs - Entry point with clap CLI
  - editors/ed/mod.rs - Module exports
  - editors/ed/error.rs - Error types
  - editors/ed/buffer.rs - Line buffer with undo support
  - editors/ed/parser.rs - Tokenizer and command parser
  - editors/ed/editor.rs - Main editor logic
  - editors/tests/ed/mod.rs - 27 integration tests

  Commands implemented:
  - Basic: q, Q, a, i, c, p, n, l, d
  - File I/O: e, E, f, r, w, W, wq, x
  - Line operations: j, m, t, k, =, z
  - Substitution: s, &
  - Global: g, G, v, V
  - Other: u (undo), h, H, P, !, #

  Status:
  - ✅ cargo build --release - Compiles without errors or warnings
  - ✅ cargo test --release -p posixutils-editors - All 263 tests pass
  - ✅ cargo fmt --all -- --check - No formatting issues
  - ✅ cargo clippy -p posixutils-editors - No warnings

  The ed editor is now functional for basic POSIX editing tasks. Future phases (2-6) from the plan would add more advanced features like regex search addresses and signal handling.
Fixes for POSIX Compliance:

  1. Semicolon separator semantics (editors/ed/parser.rs, editors/ed/editor.rs):
    - Added relative_to_first field to Address struct
    - Modified parser to set this flag when an address is preceded by a semicolon separator
    - Added resolve_range() helper that handles semicolon semantics - when the second address has relative_to_first = true, it's resolved using the first address's value as the base
    - Added resolve_address_with_base() and search functions with _from variants to support different base lines
  2. Multiple offset parsing fix (editors/ed/parser.rs):
    - Fixed bug where addr_dirty check was running after every token in SepOffCommand state
    - Moved the check inside the specific match arms so addresses are only pushed when we see a separator or command, not after every offset

  New Tests Added (editors/tests/ed/mod.rs):
  - test_ed_semicolon_separator - Tests 2;+1p (prints lines 2 and 3)
  - test_ed_comma_separator - Tests 2,4p (prints lines 2 to 4)
  - test_ed_semicolon_with_offset - Tests 3;+2p (prints lines 3 to 5)
  - test_ed_semicolon_current_line - Tests ;+2p with implicit current line
  - test_ed_edit_command - Tests the e command
  - test_ed_filename_command - Tests the f command
  - test_ed_positive_offset, test_ed_negative_offset, test_ed_multiple_offsets - Tests address offsets
POSIX Compliance Fixes:

  1. List command (editors/ed/editor.rs):
    - Added POSIX-required escape sequences: \a, \f, \r, \v (in addition to \t, \b, \\)
    - Fixed $ escaping within text (\$)
    - Changed non-printable character output from 2-digit hex to 3-digit octal per POSIX
  2. Change command (editors/ed/buffer.rs):
    - Fixed current line handling when no new lines are input per POSIX:
        - If buffer empty: current line = 0
      - If line exists after deleted range: current line = that line
      - If deleted at end: current line = new last line

  New Tests Added (25 tests):
  - Delete command: test_ed_delete_range, test_ed_delete_at_end, test_ed_delete_all
  - Change command: test_ed_change_with_text, test_ed_change_no_text, test_ed_change_range
  - Join command: test_ed_join_single_address, test_ed_join_default_range, test_ed_join_multiple
  - Move command: test_ed_move_to_start, test_ed_move_to_end, test_ed_move_range
  - Copy command: test_ed_copy_to_start, test_ed_copy_range
  - Mark command: test_ed_mark_and_address, test_ed_mark_in_range
  - Number command: test_ed_number_range
  - List command: test_ed_list_tab, test_ed_list_dollar, test_ed_list_backslash
  - Line number command: test_ed_line_number_current_unchanged, test_ed_line_number_default
  - Search address: test_ed_search_forward_in_range, test_ed_search_backward_in_range, test_ed_search_wrap_forward
Phase 5 Summary

  Code Changes

  editors/ed/buffer.rs:
  - Added in_global flag to suppress individual undo saves during global commands
  - Added begin_global() method to save undo once and enable suppression
  - Added end_global() method to re-enable individual undo saves

  editors/ed/editor.rs:
  - Modified resolve_address() and resolve_address_with_base() to &mut self to save patterns
  - POSIX fix: Search addresses (/pattern/, ?pattern?) now save the pattern to last_pattern for subsequent null RE references
  - Updated execute_global() to:
    - Save one undo record for entire operation using begin_global()/end_global()
    - Properly handle empty matching lines (current line unchanged per POSIX)
    - Track current line set by last successful command
    - Added direct handling for 'n' and 'l' commands in global

  New Tests (17 tests added)

  - test_ed_global_print_all_matches - g/pattern/p prints all matches
  - test_ed_global_delete_all_matches - g/pattern/d deletes all matches
  - test_ed_global_number_matches - g/pattern/n with line numbers
  - test_ed_global_substitute - g/pattern/s substitute on matches
  - test_ed_global_invert_print - v/pattern/p prints non-matches
  - test_ed_global_invert_delete - v/pattern/d deletes non-matches
  - test_ed_global_undo_entire_operation - Undo restores entire global operation
  - test_ed_global_empty_command_defaults_to_print - Empty command defaults to 'p'
  - test_ed_global_with_range - Global with address range
  - test_ed_global_no_match_unchanged - No match leaves current line unchanged
  - test_ed_global_current_line_after - Current line set by last command
  - test_ed_global_different_delimiter - Different delimiter character
  - test_ed_global_with_previous_pattern - Empty pattern uses previous RE
  - test_ed_invert_global_with_range - v command with address range
  - test_ed_global_substitute_with_flags - g with substitute and flags
  - test_ed_global_undo_substitute - Undo global substitute
  - test_ed_global_list_command - g with list command
Phase 6 (Help, Shell, Signals) is complete. Here's a summary:

  Phase 6 Summary

  Code Changes

  editors/ed/editor.rs:
  - Added last_shell_command field to store previous shell command for !! repetition
  - Fixed Command::HelpMode (H) to print last error when turning help mode ON (per POSIX)
  - Fixed Command::Shell for full POSIX compliance:
    - % replaced with remembered pathname
    - ! at start replaced with previous shell command text (for !!)
    - If replacements performed, modified line printed before execution
    - ! only printed on completion when not in silent mode
    - Store command for future !! repetition

  editors/ed_main.rs:
  - Added setup_signals() function with SIGQUIT ignored (per POSIX)
  - Note: Full SIGINT/SIGHUP handling would require deeper integration

  New Tests (9 tests added)

  - test_ed_help_command - h command prints last error message
  - test_ed_help_mode_toggle - H command toggles help mode
  - test_ed_help_mode_shows_last_error - H shows last error when turning on
  - test_ed_shell_escape - Basic shell command execution
  - test_ed_shell_escape_silent - Shell command with -s flag (no "!")
  - test_ed_shell_percent_expansion - % replaced with pathname
  - test_ed_shell_repeat - !! repeats last shell command
  - test_ed_prompt_toggle - P command with -p option
  - test_ed_prompt_with_p_option - -p option sets prompt string
Completed POSIX Compliance Fixes

  1. Phase 1.1: Bare newline as .+1p - Already implemented, added tests to verify
  2. Phase 1.2: SIGINT/SIGHUP signal handling - Added proper signal handlers:
    - SIGINT: Prints "?" and continues
    - SIGHUP: Saves buffer to ed.hup before exiting
  3. Phase 2.1-2.3: Shell command integration - Implemented !command syntax for:
    - e !command - Read shell output into buffer
    - r !command - Append shell output after address
    - w !command - Pipe buffer to shell command stdin
  4. Phase 3.2: Nested command check - Added validation to reject g/G/v/V/! in global command lists
  5. Phase 5.1: Long line folding - The l command now folds lines at 72 characters
Completed Features

  Phase 4.1: Escaped newline in substitute replacement (line splitting)
  - Modified convert_replacement() to preserve \<newline> sequences
  - Modified execute_substitute() to split lines when replacement contains internal newlines
  - Added line number tracking to handle multiple line splits in a range
  - Added guard to prevent line splitting in global command context (POSIX requirement)
  - Files modified: editors/ed/editor.rs, editors/ed/buffer.rs

  Phase 3.1: Multi-line command lists for g/v
  - Added check_line_continuation() function to detect backslash-newline continuation
  - Modified process_line() to accumulate continued lines before parsing
  - This enables both substitute replacement continuation and g/v command list continuation
  - Files modified: editors/ed/editor.rs

  Phase 3.3 & 3.4: Interactive G/V commands
  - Implemented execute_global_interactive() function
  - For each matching line: prints the line, reads a command from user
  - Handles empty line (skip), & (repeat previous command), and regular commands
  - Tracks line number shifts when lines are deleted/added
  - Checks for SIGINT to abort
  - Added last_interactive_cmd field to track previous command for &
  - Files modified: editors/ed/editor.rs
Phase 3.5: a/i/c with Input in Global Command Lists

  POSIX Requirement: The a, i, and c commands can include text lines embedded in a global (g/v) command list, terminated by . (or optional if last command).

  Changes Made:

  1. editors/ed/editor.rs:
    - Added GlobalCommand struct to represent a parsed command with optional embedded input lines
    - Added find_command_char() helper to identify command character skipping address prefix
    - Added parse_global_command_list() to parse multi-line command lists and extract a/i/c input
    - Added execute_input_command_with_lines() helper to execute a/i/c with pre-supplied input
    - Modified execute_global() to use the new parsing and handle embedded input
    - Added line offset tracking to correctly adjust line numbers when insert/append operations shift buffer lines
  2. editors/tests/ed/mod.rs:
    - Added 6 test cases for global append/insert/change with embedded input
    - Tests cover: basic append/insert/change, optional . terminator, multiple input lines, mixed commands
@jgarzik jgarzik requested a review from Copilot December 20, 2025 15:42
@jgarzik jgarzik self-assigned this Dec 20, 2025
@jgarzik jgarzik added the enhancement New feature or request label Dec 20, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a new ed line editor for the posixutils-rs project, aiming for POSIX.1-2024 compliance. The implementation includes the core ed functionality with comprehensive integration tests.

Key changes:

  • New ed line editor implementation with buffer management, command parsing, and editor logic
  • Comprehensive integration tests for ed, ex, and vi components
  • Updates to build configuration and documentation to include the new ed utility

Reviewed changes

Copilot reviewed 12 out of 16 changed files in this pull request and generated no comments.

Show a summary per file
File Description
editors/ed_main.rs Entry point for ed binary with signal handling and CLI argument parsing
editors/ed/mod.rs Module organization for ed implementation
editors/ed/error.rs Error type definitions for ed operations
editors/ed/parser.rs Command line parser with tokenizer and command parsing logic
editors/ed/editor.rs Main editor logic including command execution and global commands
editors/ed/buffer.rs Buffer management with undo support and file I/O
editors/tests/ed/mod.rs Comprehensive integration tests for ed (1394 lines)
editors/tests/ex/mod.rs Integration tests for ex editor
editors/tests/headless/mod.rs Headless editor tests for vi
editors/tests/integration/mod.rs Integration tests for vi components
editors/tests/editors-tests.rs Test harness with mod statements
editors/Cargo.toml Added clap dependency and ed binary configuration
lib/utils.tsv Added ed entry to utilities list
README.md Updated checklist to mark ed as completed
Cargo.lock Updated with clap dependency for editors
m4/tests/integration_test.rs Simplified build/test logic (removed BUILD_ONCE)

@jgarzik jgarzik merged commit 0a79367 into main Dec 20, 2025
10 checks passed
@jgarzik jgarzik deleted the new-ed branch December 20, 2025 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants