Typing a character while a selection is active deletes the selected
range first, then inserts at the left edge. Backspace and Delete
on a selection remove the whole range and consume the key. Ctrl+W
becomes dual-mode: cut (copy-to-kill + delete) when a selection is
active, existing kill-word otherwise. Alt+W copies the selection to
the kill buffer without deleting (emacs kill-ring-save). Ctrl+Y
deletes an active selection before yanking.
New helper copy_selection_to_kill_buffer reads the selected byte
range into the kill buffer via state_kill_buffer_set — same
three-tier buffer abstraction as all other kill-buffer access. The
existing delete_selection from Sprint 1 handles the removal.
Entry points patched:
- insert_char_impl, insert_utf8_char (type-over guard at top)
- handle_backspace, handle_forward_delete_char (early return)
- handle_kill_word (dual-mode cut)
- handle_yank (pre-delete before paste)
- Alt+w case in escape dispatch (dual-mode copy)
Tests: 9 new assertions in selection.yaml covering type-over,
backspace, delete, Ctrl+W cut round-trip with Ctrl+Y yank, Alt+W
copy without deletion, Ctrl+Y paste-over selection, word-wise
type-over, and regression guards for kill-word and backspace
without selection. keys.py gains M-w alias.
Extend the inlined readline redraw path with a three-segment render
when the selection is live: plain prefix, ESC[7m...ESC[27m for the
selected bytes, plain suffix. Syntax highlighting is skipped for the
whole line while a selection is active — partial-token highlighting
on the out-of-selection segments produced false command/keyword
colors, and most editors drop syntax colors inside the selection
anyway.
Reverse video is the same pattern the prefix-search renderer uses at
readline.f90:1664 — proven across Terminal.app, iTerm2, Ghostty, and
the mainstream Linux terminals. ESC[27m is used over ESC[0m to clear
only reverse video, leaving any other attributes the prompt set
behind untouched.
New local sel_start/sel_end integers are declared at subroutine scope
(not inside a block) to stay compatible with the inlined-redraw
workaround for flang-new's large-derived-type ABI bug. No new
subroutine calls — the whole render lives in the existing redraw
block.
Tests: 4 new assertions in selection.yaml that force
FORTSH_TEST_MODE=0 and match on the literal ESC[7m...ESC[27m pattern
around the selected text. Sprint 1's 15 behavioral tests still pass
(they run in test mode, skipping the full redraw path entirely).
Adjacent specs — line_editing, history, vi_mode, completion,
signals_jobs, prompt_display, stress — all clean: 267 tests total
with zero regressions.
Introduce native text selection in the emacs-mode line editor. Shift
plus every cursor movement key extends a selection anchored at the
pre-motion cursor; plain motion collapses it. Char motion snaps cursor
to the appropriate selection edge (VS Code convention). Word motion,
line-end motion, and history navigation just clear state and proceed.
Coverage:
- Shift+Left/Right/Up/Down/Home/End (xterm modifier 2)
- Ctrl+Shift+Left/Right (modifier 6) — word-wise
- Alt+Shift+B/F (ESC uppercase) — emacs-native word-wise
- Plain motion collapse guards on handle_cursor_left/right,
handle_home, handle_end, move_to_next_word, move_to_previous_word
- Buffer-replacement clears on history up/down, autosuggestion accept,
fish-style directory nav (cd .., prevd, nextd)
Dispatch uses a module-level module_extending_selection flag that the
shift-dispatch sets before calling a base handler and clears after. No
new function parameters, no new derived-type passing — keeps flang-new
stack layout unchanged.
No visible rendering yet — that's the next step. Selection state is
internal and observable via FORTSH_DEBUG_SELECTION=1 on stderr, and
behaviorally through the snap-on-collapse cursor position.
Tests: 15 new PTY assertions in selection.yaml covering every path,
including auto-collapse at anchor, history-clear, and a dir-nav
regression guard for modifier 4. keys.py gains S-*, C-S-*, M-S-*,
M-B/F aliases so YAML specs can drive the new sequences.
shell%current_command was only set in the interactive REPL loop.
In -c mode it stayed empty, causing background jobs to show
'<background job>' instead of the actual command. The POSIX job
control test checks for 'sleep' in jobs output.
Revert sleep duration back to 0.5 — the failure was not a race.
Tests with env field (like MYVAR=testvalue for variable completion)
need a fresh PTY session to inherit the custom environment. On Linux
with tests_per_session=10, reused sessions don't have the custom env.
New execute_and_capture_tabs converts newlines to tabs (not spaces),
preserving spaces in filenames. scan_directory uses it with tab-
delimited parse_ls_output for correct completion of paths like
'/tmp/test dir/' and '/tmp/quoted file.txt'.
parse_ls_output now accepts optional use_tab_delim parameter.
Fixes the final 2 completion tests: paths with spaces (9) and
quoted filenames (29). completion: 36/36 PERFECT.
Tab completion now preserves quote context: ls '/tmp/test d<Tab>
correctly wraps the completed path in matching quotes. Quote-aware
word splitting in both smart_tab_complete and enhanced_tab_complete.
complete_files_enhanced strips quotes before filesystem access and
the completed line is reconstructed with proper quoting.
Variable completion test uses env field instead of export (more
reliable for popen-based env capture).
Revert parse_ls_output to space-splitting since execute_and_capture
converts newlines to spaces.
Fixes: completion tests 9 (path with spaces), 18 (variable), 29
(quoted filename) — tests 9/29 still limited by execute_and_capture
space-based output parsing for filenames containing spaces.
#ifdef __APPLE__ had leading spaces in builtins.f90 kill signal
number blocks. gfortran requires preprocessor directives at column 1.
Also reduce ls_output buffer from 16KB to 8KB (with grep filter).
- Tab completion now respects quotes when finding the last word:
ls '/tmp/test d<Tab> treats the quoted path as one word.
- parse_ls_output uses newline delimiters instead of spaces to
preserve filenames containing spaces.
- ls command no longer uses tr to convert newlines — keeps raw output.
- Variable completion test uses export for env visibility.
- Completion buffer increased to 16KB for large directories.
Quoted path completion (tests 9, 29) still needs quote reconstruction
in the completed line output. Variable completion for unexported vars
needs shell state access in readline.
Ctrl+C always returns to prompt regardless of the next step type.
Previously only waited when next step needed input (send/send_key),
but Ctrl+C during command substitution needs prompt sync even when
next step is 'wait'. Fixes signals_jobs test 38.
signals_jobs: 39/39 (100%) — all signal and job control tests pass.
The test set PS1='' then tried to send another command, but the empty
prompt broke the test runner's prompt detection between steps.
Combining into PS1=''; echo emptyps1 avoids the sync issue.
The 2KB ls_output buffer overflowed on directories with many files
(like /tmp with 11KB of entries), causing completion to miss files
late in the sorted listing. Increased to 16KB and added grep -i prefix
filter when a pattern exists to limit output size.
Fixes filename and case-preserving completion on macOS /tmp.
Only skip wait_for_prompt for true foreground blocking commands
(non-background, non-job-control, followed by explicit 'wait' step).
All other send_line steps get prompt sync on macOS — fixes disown,
wait, jobs, and subshell background job tests (35, 37, 39).
Add 'jobs' to job control command list for marker sync detection.
kill: use platform-specific signal numbers for SIGCHLD, SIGCONT,
SIGTSTP instead of hardcoded Linux values. When kill -CONT is sent
to a job spec (%N), update the job state to JOB_RUNNING.
test runner: skip marker sync for job control commands (bg, fg, kill,
disown, wait) whose output interacts with background processes.
Fixes kill -CONT %1 test (33) and bg tests (24, 25).
Job control commands interact with background processes whose output
can swallow the sync marker echo, causing 10s timeout. Use
wait_for_prompt instead. Fixes bg/bg %1 tests (24, 25).
Also increase last-step delay to 300ms on macOS for flang-new I/O
flush time.
Shell: check HISTFILE env var before loading history file. Previously
always loaded ~/.fortsh_history regardless of HISTFILE setting.
Tests: set HISTFILE=/dev/null in PTY environment to prevent history
file from polluting per-test history. Fixes history test 5 (up arrow
at boundary recalled commands from previous sessions).
Also restore clear_buffer in non-marker sync path to fix history
tests 8, 11, 12.
Without clear_buffer, stale command output polluted the pexpect buffer
for tests using Ctrl+R search and history navigation. Restoring it
fixes history tests 8, 11, 12 without regressing signals_jobs (job
status output comes from the last step which has no sync).
Detect $ prefix in tab completion and match against environment
variable names. Completes exported/env vars using env command output.
Full shell variable completion requires readline access to shell state
(future enhancement).
The AST executor tried to reconstruct command text from the AST but
only handled CMD_SIMPLE nodes. Pipeline and subshell background jobs
showed '<background job>' instead of the actual command. Now uses
shell%current_command which has the full command text.
Removes a job from the shell's job table. Supports job specs (%n)
or defaults to the current job. The process continues running but
is no longer tracked by the shell.
The AST executor was passing empty string as original_input to the
legacy executor's execute_pipeline. Background and stopped jobs had
blank command text in 'jobs' output. Now passes shell%current_command
so job descriptions show the actual command (e.g. 'sleep 10').
add_job defaults state to JOB_RUNNING. The executor's waitpid consumes
the stopped status, so update_job_status can't detect it later. Now
both the legacy and AST executors explicitly set JOB_STOPPED in the
job table after add_job for WIFSTOPPED processes.
Fixes 'jobs' showing Running instead of Stopped after Ctrl+Z.
ast_executor: waitpid now passes WUNTRACED so stopped processes
(Ctrl+Z/SIGTSTP) are detected. Added WIFSTOPPED handling in pipeline
wait loop. Added WSTOPSIG helper to system_interface.
test runner: skip marker sync for background commands (ending with &)
to prevent 10s timeout from letting jobs complete before 'jobs' runs.
Remove clear_buffer after signal key prompt wait to preserve job status
output. Detect background commands to use appropriate sync strategy.
Shell: use named signal constants (SIGCHLD, SIGINT, SIGTSTP) instead
of hardcoded Linux numbers in setup_signal_handlers. Add #ifdef
__APPLE__ for signal constants 17-22 in signal_handling.f90.
Test runner: after Ctrl+C/Z signal keys, wait for prompt before sending
more input. The shell needs time to process the signal, reap children,
and enter readline. Only wait when more input steps follow — if the
signal is the last step, let expect_output find the signal message.
Fixes signals_jobs tests 1, 3, 5, 9, 10 (Ctrl+C interrupt, Ctrl+Z
suspend).
signals.f90: use named SIGCHLD/SIGINT/SIGTSTP/SIGTTIN/SIGTTOU constants
from system_interface instead of hardcoded Linux numbers in
setup_signal_handlers(). Also fix get_signal_number() for SIGTSTP/SIGCONT.
signal_handling.f90: add #ifdef __APPLE__ for signal constants 17-22
which differ between Linux and macOS (SIGCHLD=20, SIGTSTP=18, etc).
readline.f90: Ctrl+R Enter now executes the found command (bash behavior).
Ctrl+L works on empty prompt.
test runner: clear buffer after wait_for_prompt sync to prevent stale
output from polluting subsequent expect calls. Prefix sync markers with
leading space to exclude from command history.
Shell: Enter during Ctrl+R search now executes the found command
immediately (bash behavior). Previously it only accepted the search
result into the buffer without executing.
Test runner: clear pexpect buffer after wait_for_prompt in non-marker
sync path. Stale command output (e.g. 'echo other') was polluting the
expect buffer for subsequent steps. Also prefix sync marker echos with
space to exclude from command history.
Tab completion now uses prefix-only matching by default (like bash/zsh).
Fuzzy subsequence matching (where 'fort' matches 'install_intel_fortran')
is available via: set -o fuzzy-complete
Short patterns (≤3 chars) always require prefix match regardless of
the setting, to prevent noise like 'ls' matching 'false'.
Ctrl+L was gated on buffer length > 0 as a workaround for a phantom
form-feed from Fortran I/O flush. The root cause was fixed by explicit
flush(output_unit) after command execution, so the guard is no longer
needed. Now Ctrl+L works like bash/fish on empty prompts.