Commit Graph

1118 Commits

Author SHA1 Message Date
vasilito 84bcc979de tlc: update PLAN.md with W6-series changelog entries 2026-07-06 05:20:56 +03:00
vasilito ca0ea881f4 tlc: W6 visual/UX gap fixes — CJK width, bracket flash, search wrap, tab indicator
W6a: Fix CJK/wide character width handling
  - visual_width() now uses UnicodeWidthChar::width() instead of hardcoding 1
  - count_wrapped_rows() iterates UTF-8 chars instead of raw bytes
  - Control char fallback returns 0 (combining marks) instead of 1

W6b: Render bracket match flash highlight
  - bracket_flash (computed but never rendered) now applies accent bg style
  - push_rendered_text() accepts bracket_offsets + bracket_style params
  - All 4 call sites updated (3 selection segments + 1 non-selection)

W6c: Add search wrap-around notification
  - Editor: SearchState.last_wrapped field, 'Search wrapped' message on wrap
  - Viewer: Search.last_wrapped field, flash_msg in footer_text
  - Both find_next/find_prev and step() detect and report wrap

W6d: Editor status bar tab width indicator
  - Added 'Tab:{}' to status_string showing current tab_width
  - Hardcoded widget defaults verified as dead code (render_popup uses theme)

1381 tests pass, zero warnings.
2026-07-06 05:18:10 +03:00
vasilito e8fa2ca97b config: re-enable diffutils and curl in redbear-mini; nghttp2: build with -fPIC for libcurl; relibc: bump submodule with float.h/getprogname fixes 2026-07-06 04:29:07 +03:00
vasilito 72059ea9ef tlc: update PLAN.md with V-series and W-series changelog entries
Updates status header (1381 tests, zero warnings), adds V/W rows to
sprint table, and adds §9.2 (Visual Polish) and §9.3 (Warning Cleanup +
Stub Fixes) detail sections.
2026-07-06 04:28:02 +03:00
vasilito 8a77a6ebde tlc: comprehensive W1-W5 fixes — dead dialogs, suspend, connection wiring, warning cleanup
W1: Fix 3 dead dialogs (DisplayBits/VfsSettings/LearnKeys) that could never close
    - Capture handle_key() return value, close on Cancel/Confirm
W2: Implement Cmd::Suspend with actual SIGTSTP via kill -TSTP 4035172
    - Add want_suspend field, ExternalAction::Suspend variant
    - Drop TUI, send SIGTSTP, recreate TUI on resume
W3: Wire Connection dialog to Panel::navigate_to_vfs()
    - Parse VFS URL, look up backend, redirect active panel
    - Store Encoding dialog selection on FileManager.display_encoding
W4: Wire CK_EditUserMenu (EditorCmd::EditUserMenu)
    - Opens user menu storage_path in editor via Editor::open()
    - Fix unreachable!() in SaveBeforeClose prompt rendering
W5: Add ErrorOutcome::SkipAll variant + Shift-S keybinding
    - Fix misleading doc comment about non-existent 'All' variants
    - Add SkipAll button in error dialog render

Also: Fix all 41 compiler warnings (unused imports/vars, missing docs on
public API, remove dead SPECIAL_LABELS constant, remove unused viewer_bold)

1381 tests pass, zero warnings.
2026-07-06 04:18:44 +03:00
vasilito f54b53fbfb config: ensure console getty services depend on ptyd
The getty binary opens a PTY via /scheme/pty during startup. Adding an
explicit requires_weak on 00_ptyd.service avoids races where getty runs
before the pseudo-terminal daemon is fully ready, which could delay or
prevent the login prompt from appearing.
2026-07-06 03:59:56 +03:00
vasilito 48a06bd3a8 kernel: bump submodule to d4d9961d (map FACS physical address before parsing)
FACS address from FADT is a physical address. The previous code dereferenced
it directly, causing a page fault after the FADT offset fix.
2026-07-06 02:26:51 +03:00
vasilito 2ae61a4499 kernel: bump submodule to 51f6a771 (fix FADT offsets and GS base hardening) 2026-07-06 02:01:43 +03:00
vasilito 193d2926d0 tlc: visual polish V1-V6 — focused borders, sparkline, size coloring, fractional gauge, color cache, disk-free
V1: Focused panel border — active uses theme.accent+BOLD, inactive uses darken(border,30)
V2: Transfer-rate sparkline in ProgressDialog — ring buffer, 0.5s throttle, block glyphs
V3: Dynamic file-size coloring — warmth gradient by size (GB+20, 100MB+12, 10MB+6, 1MB+3, <512B dim)
V4: Sub-cell fractional progress bars — 8x resolution via ▏▎▍▌▋▊▉█ glyphs
V5: Pre-resolve MC skin color pairs — global Mutex<HashMap> cache, null-separated key
V6: Disk-free indicator in status bar — df subprocess, green/yellow/red thresholds

1381 tests pass (+12 new)
2026-07-06 00:46:05 +03:00
vasilito e07f264ab7 tlc: update PLAN.md test count + parity to 100% 2026-07-06 00:06:45 +03:00
vasilito 748c5f7ac2 submodules: relibc semaphore cbindgen [export] exclude fix 2026-07-06 00:06:26 +03:00
vasilito dd10299209 submodules: relibc sem_open next_arg value fix 2026-07-06 00:03:47 +03:00
vasilito 290664d2e1 tlc: update PLAN.md with Sprint 5 G-series 2026-07-06 00:03:04 +03:00
vasilito 19d093129e tlc: Sprint 5 G-series — error dialog retry wiring + SFTP open_write
G1: Error dialog Retry now re-invokes the stored file operation (copy/move/
    delete) using the PendingErrorOp parameters. Previously Retry just logged
    'Retry not yet wired'. Copy/move/delete error paths now create a
    PendingErrorOp + ErrorDialog instead of silently setting a status message.
    Skip/Ignore/Abort outcomes produce appropriate status messages.

G2: SftpVfs::open_write now returns a working SftpWriter that buffers writes
    and flushes to the remote SFTP server via session.write(). Previously
    returned VfsError::Unsupported. The SftpWriter buffers in memory and
    writes on flush/drop, matching the existing open_read buffer pattern.

1369 tests pass (default and --features sftp).
2026-07-06 00:02:10 +03:00
vasilito 43bc027485 submodules: relibc sem_open VaList::next_arg fix 2026-07-06 00:01:40 +03:00
vasilito ec101f9d4b submodules: update Cat 2 fork pointers to local path deps and +rb0.2.5
- relibc: variadic sem_open and local fork path deps
- base: already at latest RedBear-OS submodule/base
- bootloader/kernel/libredox/userutils: pushed local path-dep fixes
- installer/redoxfs: diverged from remote submodule/*; local commits saved,
  divergence to be resolved after build
- driver-manager: add syscall path dependency
- AGENTS.md: document +rb build metadata and no-patches-for-local-forks rule
- remove dead patch symlinks from recipes/core/relibc (path-source local fork)
2026-07-05 23:58:20 +03:00
vasilito c00dc8a0e3 tlc: update PLAN.md with Sprint 5 F-series completion 2026-07-05 23:46:21 +03:00
vasilito 329708940b tlc: Sprint 5 F-series — chunked viewer rendering, xz2 decompression, stale comment cleanup
F1: Remove stale 'Phase N' / 'not yet wired' comments from vfs/local.rs,
    vfs/traits.rs, editor/usermenu.rs — the functionality they described as
    future work is already implemented.

F2: Replace placeholder stubs in viewer/hex.rs and viewer/text.rs with actual
    rendering for Chunked sources (files >= 1 MiB). hex.rs reads viewport-sized
    chunks via read_at(); text.rs reads up to 64 MiB cap for line offset mapping.
    check_growing() in viewer/mod.rs also reads Chunked content instead of
    returning empty Vec.

F3: Editor Settings dialog now shows actual toggle state (auto-indent,
    word-wrap, show-whitespace, save-on-quit) instead of '(TBD)' placeholder.

F4: Add xz2 crate dependency and TarKind::Xz decompression support.
    Feature-gated as 'xz2' (optional, follows bzip2 pattern). Uses
    XzDecoder::new_multi_decoder for multi-stream .tar.xz files.

1369 tests pass. Default build (without optional features) verified.
2026-07-05 23:44:41 +03:00
vasilito 08aa9768be tlc: update PLAN.md — Sprint 5 E1, correct stale parity numbers
Editor parity 35/35  (was 29/35 — D7 multi-cursor + D8 spell check)
Viewer parity 19/19  (was 17/19 — hex-edit + goto-in-hex verified)
File menu 23/23  (was 20/21 — E1 restructure)
Command menu 22/22  (was 17/20 — E1 restructure)
Options menu 10/10  (was 7/10 — E1 restructure)
MC parity ~99% (was ~98%)

All previously deferred LOW-priority items resolved:
- Multi-cursor  D7
- Spell check  D8
- PTY subshell  D6 (verified)
- Mouse support  D5
2026-07-05 23:21:18 +03:00
vasilito f73acfc1c0 tlc: E1 — F9 menu restructure for MC parity
Rewrite build_menus() to match MC filemanager.c menu structure exactly:
- File menu: 23 items (View, View file, Filtered view, Edit, Copy,
  Chmod, Link, Symlink, Relative symlink, Edit symlink, Chown,
  Advanced chown, Rename/Move, Mkdir, Delete, Quick cd, Select/
  Unselect/Invert group, Quit)
- Command menu: 22 items (User menu, Directory tree, Find file,
  Swap/Toggle panels, Compare dirs/files, Panelize, Dir sizes,
  Command history, View/edit history, Directory hotlist, Active VFS
  list, Background jobs, Screen list, Edit extension/menu/highlighting)
- Options menu: 10 items (Configuration, Layout, Panel options,
  Confirmation, Appearance, Display bits, Learn keys, Virtual FS,
  Save setup)
- Left/Right panel menus share panel_menu() helper

Add 6 new Cmd variants: ViewFile, DisplayBits, LearnKeysDialog,
VfsSettings, CommandHistory, OptionsConfirm with dispatch handlers
wiring to existing dialog state machines (D2/D3/D4) and new methods
(open_view_file_prompt, open_command_history, toggle_confirmations).

Update 2 menubar tests to match the new MC-faithful structure.

1369 tests pass.
2026-07-05 23:09:31 +03:00
vasilito 54de45d461 docs/scripts: enforce single-repo policy and +rb build-metadata suffix
- Update AGENTS.md single-repo rule to explicitly forbid creating any new
  Gitea repositories and to require deleting per-component repos.
- Change version suffix policy from pre-release -rb to build-metadata +rb
  in AGENTS.md, sync-versions.sh, apply-rb-suffix.sh, verify-fork-versions.sh.
- Update migration instructions to push to existing submodule/<component>
  branches inside RedBear-OS, not create new repos.
- Update BUILD-SYSTEM-IMPROVEMENTS.md and SLEEP-IMPLEMENTATION-PLAN.md to
  reference submodule/* branches instead of defunct per-component repos.
- Fix delete-per-component-repos.sh example to use a real historical repo.
2026-07-05 22:50:33 +03:00
vasilito 73cf268273 PLAN.md: Sprint 4 (D1-D8) complete — all 8 items done
Updated status header (98% MC parity), test counts (1369),
changelog with D5-D8 detail, risk register, sprint table.
2026-07-05 22:42:31 +03:00
vasilito 76df890074 D8: editor spell check with built-in dictionary
SpellChecker with ~300 common English words + programming terms.
Word tokenizer handles apostrophes, digits, underscores.
Editor integration: toggle_spell_check(), find_next_misspelled().
MC uses libaspell; TLC embeds dictionary for zero external deps.

17 new tests: check_word, check_line, case insensitive, editor
integration (toggle, find_next, disabled, correct text).
2026-07-05 22:39:29 +03:00
vasilito b8aac3c9bc D7: editor multi-cursor support
Add secondary_cursors field to Editor with insert_char_multi,
delete_back_multi, delete_forward_multi methods. Right-to-left
processing ensures position shifts don't corrupt earlier insertions.

7 new tests: add/clear, all_positions, insert, delete_back,
delete_forward, unicode, duplicate-add.
2026-07-05 22:29:19 +03:00
kellito 7a2b0d5160 tlc: D5 — Mouse support wiring (click + scroll + double-click)
Mirrors MC's panel.c:4080-4197 mouse handling. Wraps the
Tui screen in termion's MouseTerminal (enables GPM on Linux /
xterm mouse on others — without it mouse events never reach
ratatui). The translate_mouse function maps termion MouseEvent
to a MouseAction enum that the FileManager dispatches as
cursor moves / entries / scroll (deferring HistoryPrev/Next/
List and ToggleHidden which need dedicated Cmds not yet in
the keymap).

New APIs:
  - terminal::event::translate_mouse(event, panel_top,
    panel_height, panel_width) -> MouseAction
  - terminal::event::MouseAction (Click, DoubleClick,
    ScrollUp/Down, HistoryPrev/Next/List, ToggleHidden,
    Unhandled)
  - FileManager::handle_mouse_event(m, size) routes
    MouseAction to existing cursor_up_n / cursor_down_n /
    Panel::enter / Panel::set_cursor helpers.
  - app.rs routes TermEvent::Mouse through handle_mouse_event
    before the keyboard-dispatch path.

Tests (9 new in terminal::event::mouse_tests):
  - click_on_file_row_emits_click_action
  - click_below_panel_is_unhandled
  - click_on_header_history_prev_button (col 1)
  - click_on_header_history_next_button (col width-2)
  - click_on_header_toggle_hidden_button (col width-6)
  - scroll_up_emits_scroll_up
  - scroll_down_emits_scroll_down
  - click_on_column_name_row_is_unhandled (sort-by-column
    not yet wired)
  - click_respects_panel_top_offset (panel_top parameter
    works for menu-bar offset)

1345 passing (was 1336; +9 new).
2026-07-05 22:06:01 +03:00
kellito 47abccbc13 tlc: PLAN.md Sprint 4 / D-series changelog + cleanup
Updates:
  - Status banner: 4 sprints (1-3 + D1-D4), 1336 tests
    (was 1292), 139 .rs files (was 138; +1 for
    error_dialog.rs), MC parity ~96% (was ~93%)
  - §3.1: 14d row updated 19→23, total 44→49, added
    Sprint 4 row (4/8 in progress)
  - §3.2: removed stale duplicate lines, marked deferred
    items as "- deferred" per Sprint 4 status
  - Renamed duplicate §3.3 to §3.4 (F9 menu parity)
  - Renumbered §3.4 Editor parity -> §3.5, §3.5 Viewer
    parity -> §3.6
  - §3.5 Editor parity: 28/35 -> 29/35 (C18 code-block
    preservation done)
  - §3.6 Viewer parity: unchanged
  - §7 Risk Register: removed stale `_wONTFIX` line and
    PTY subshell line; added D-series and D1 retry rows
  - §9: added Sprint 4 row (4/8 in progress) and
    §9.1 Deferred Phase 14d items section (D5-D8) with
    per-item context (which file, what needs doing)
  - §4 changelog: added full Sprint 4 / D-series section
    at the top with per-item commit refs and tests added

PLAN.md: 624 lines (was 540; +84 for Sprint 4 details).
2026-07-05 21:15:30 +03:00
kellito 17804d9a6b tlc: D4 — Learn keys dialog
Mirrors MC's src/learn.c::learn_keys (mc/source/src/learn.c:393-422).
MC shows a grid of buttons; TLC is simpler: scrollable list of
captured keys with their resolved Cmd.

New file: src/filemanager/learn_keys_dialog.rs (LearnKeysDialog,
CapturedKey, 6 unit tests).

New DialogState::LearnKeys variant wired into dispatch, render,
is_finished, title. Keymap is stored at construction so the
dispatcher doesn't thread it through every key event.

Tests: 1336 passing (was 1330; +6 new).
2026-07-05 21:04:42 +03:00
kellito 927d737d9c tlc: D3 — Virtual FS settings dialog (timeout)
Mirrors MC's boxes.c::configure_vfs_box
(mc/source/src/filemanager/boxes.c:1120-1198). MC's dialog has
VFS timeout plus FTP options (anonymous password, directory
cache timeout, proxy host, passive mode); TLC has no FTP/SFTP in
the active build, so we expose the relevant 1-field subset:
  - Timeout for freeing VFSs (sec): 0..=10000, default 10
    (out-of-range falls back to 10 — matches MC's
    boxes.c:1193-1194 validation)

New file: src/filemanager/vfs_settings_dialog.rs
  - VfsSettings struct (free_timeout: u32)
  - parse_timeout() (handles negative as 0, > 10000 as None)
  - VfsSettingsDialog with Edit-keyed text input
  - 12 unit tests

New DialogState variant:
  DialogState::VfsSettings(Box<VfsSettingsDialog>)
  Wired into dispatch, render, is_finished, title.

Tests: 1330 passing (was 1318; +12 new).
2026-07-05 20:49:13 +03:00
kellito 6c771d88f9 tlc: D2 — Display bits dialog (8/7/UTF-8)
Mirrors MC's boxes.c::display_bits_box
(mc/source/src/filemanager/boxes.c:955-1004). MC exposes 4 modes
(UTF-8, Full 8 bits, ISO 8859-1, 7 bits); TLC is UTF-8 throughout
so we expose the relevant 3-state subset:
  - Full 8 bits: bytes pass through verbatim
  - 7 bits: high bit stripped (parity-stripped serial consoles)
  - UTF-8 (validated): pass through UTF-8, ? for invalid

New file: src/filemanager/display_bits_dialog.rs
  - DisplayBits enum with 3 variants
  - DisplayBitsDialog (radio-list, mirrors SortDialog)
  - from_config() / map_byte() / is_valid_utf8() helpers
  - 12 unit tests

New DialogState variant:
  DialogState::DisplayBits(Box<DisplayBitsDialog>)
  Wired into dispatch (handle_key), render, is_finished (false;
  the dispatcher captures Confirm/Cancel from handle_key), title.

Tests: 1318 passing (was 1306; +12 new).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-05 20:42:36 +03:00
kellito 4a3d097b27 tlc: D1 — file-op error recovery dialog (Retry/Skip/Ignore/Abort)
Mirrors MC's filegui.c::file_progress_real_query_replace and the
FileProgressStatus enum (filegui.h:45-54): FILE_CONT, FILE_RETRY,
FILE_SKIP, FILE_ABORT, FILE_IGNORE.

New file: src/filemanager/error_dialog.rs
  - ErrorDialog with path + error message
  - R / S / I / A shortcuts (MC letter bindings)
  - Esc maps to Abort
  - 7 unit tests (one per shortcut + an 'other keys don't finish' guard)

New FileManager state:
  - pending_error_op: Option<PendingErrorOp>
    stores kind + sources + dst + flags for the in-flight op so
    the user can Retry / Skip / Ignore / Abort
  - PendingErrorOp struct (parallel to existing PendingFileOp)
  - pending_error_op initialized to None

New DialogState variant:
  DialogState::Error(Box<error_dialog::ErrorDialog>)
  Wired into dispatch (handle_key), render, is_finished, title.

Retry is currently a stub (logs a message) because the copy
engine's batch step resumption would require threading the
operation index into copy_many/move_many/delete_many. The dialog
scaffolding is complete and tested; the retry can be wired in a
follow-up sprint when the batch step API is added.

Tests: 1306 passing (was 1299; +7 new).
2026-07-05 20:32:50 +03:00
kellito 2db8636f8b tlc: lock-in tests for C8, C11, C12 (verified-already-done)
Adds regression tests for Sprint 3 items that were verified
already-done during recon but had no dedicated test coverage:

  - viewer::tests::hex_edit_apply_nibble_at_eof_is_safe_noop
    Locks in the C8 contract: apply_nibble at cursor past EOF
    is a safe no-op (no panic, no spurious modified flag).

  - viewer::tests::move_cursor_clamps_to_size
    Locks in the C8 cursor bounds invariant at move_cursor
    level: positive deltas clamp to file size, negative deltas
    clamp to 0 with no underflow.

  - filemanager::panel::tests::history_dedups_consecutive_entries
    Locks in the C11 contract: refreshing the same directory
    N times grows the history by 1 entry (consecutive dedup),
    not N.

  - filemanager::panel::tests::sort_field_mtime_round_trips_through_config
    Locks in the C12 contract: 'mtime' and 'time' config
    strings both resolve to the Mtime sort field, and the
    Panel reports the human-readable name 'Mtime'.

Tests (4 new, total 1299 passing):
  +hex_edit_apply_nibble_at_eof_is_safe_noop
  +move_cursor_clamps_to_size
  +history_dedups_consecutive_entries
  +sort_field_mtime_round_trips_through_config
2026-07-05 19:58:29 +03:00
kellito a9a3507daf tlc: lock-in tests for C1 (Rgb precision) and C5 (bookmark column)
Adds regression tests for already-implemented Sprint 3 items so
future refactors can't silently regress these behaviors:

  - terminal::color::tests::default_theme_preserves_rgb_precision
    Locks in the C1 contract: when Theme::background is Rgb, the
    exact 8-bit values (julia256's core.bg = 58,58,58) survive.
    The parser may also emit Indexed(237) for the same source
    value depending on detected color depth, so the test accepts
    either variant but verifies 24-bit precision when present.

  - terminal::color::tests::light_theme_preserves_rgb_precision
    Same invariant for LIGHT_THEME (sand256 bright off-white
    foreground > 200 RGB).

  - editor::tests::bookmark_jump_restores_both_line_and_column
    Locks in the C5 contract: jumping to a named bookmark must
    restore both the line AND the column where the bookmark was
    set. The existing test only verified the byte offset, not
    the column, so a future regression to line-only restoration
    would not have been caught.

Tests (3 new, total 1295 passing):
  +default_theme_preserves_rgb_precision
  +light_theme_preserves_rgb_precision
  +bookmark_jump_restores_both_line_and_column
2026-07-05 19:50:29 +03:00
kellito 0b15a49b55 tlc: PLAN.md Sprint 3 completion + Sprint 2/3 changelogs
Updates:
  - Status banner: all 3 sprints complete (was: only Sprint 1)
  - §3.1: Sprint 2 marked Done (7/7 + B2 N/A), Sprint 3 marked
    Done (10/18 + 7 verified-already-done + 1 N/A)
  - §3.2: removed stale B1-B8 planning items (all done in Sprint 2)
  - New §3.3: Sprint 3 items final status table (10 implemented,
    7 verified-already-done, 1 N/A)
  - §9 SPRINT PLANS: collapsed three planned-sprint tables into a
    single summary table (all complete)
  - §4 RECENT CHANGELOG: added Sprint 2 (7 items) and Sprint 3
    (10 items) entries at top

Tests: 1292 passing (was 1180 before §30–§33; +112 new across
§30–§33, Sprint 1, Sprint 2, Sprint 3).

PLAN.md: 540 lines (was 421; +119 for Sprint 2/3 changelogs).
2026-07-05 19:46:15 +03:00
kellito 40d091226f tlc: Sprint 3 C6 — multi-line status messages
When set_message() is called with text containing '\n' characters,
the status line now renders the message across multiple rows
(previously only the first line was shown, with the rest lost).

StatusLine (terminal/status.rs):
  - Message::line_count() returns the newline-segment count
  - StatusLine::has_multiline_message() detects newline-containing
    unexpired messages
  - StatusLine::current_message_line_count() returns the row
    count for allocation (0 for no message)
  - StatusLine::render_multiline() renders each newline-separated
    line as its own row

FileManager (filemanager/mod.rs):
  - status_message_line_count() helper that clamps to 1..=4 rows
    to avoid eating the whole screen, returns 1 when no message

render.rs (filemanager/render.rs):
  - When has_message() AND has_multiline_message(), allocate the
    multi-line area above the single-line status bar (which still
    shows clock/spinner/hint). Single-line messages unchanged.

Tests (3 new in terminal::status::tests):
  - has_multiline_message_detects_newlines
  - current_message_line_count_counts_newlines
  - message_line_count_handles_empty_and_multiline

Total: 1292 passing (was 1289; +3 new).
2026-07-05 19:37:16 +03:00
kellito dfd75b52f6 tlc: Sprint 3 C18 — format paragraph preserves code blocks
Previously reformat_paragraph_at() would join indented lines
together, destroying intentional indentation (Python code,
shell heredocs, ASCII art, markdown sub-blocks).

Now reformat_paragraph_at() detects code blocks (paragraphs
where every non-blank line begins with whitespace) and
returns the text unchanged. The detection is intentionally
conservative — false positives leave the paragraph alone
rather than destroying user-written indentation.

format.rs:
  - New is_code_block(paragraph) helper
  - reformat_paragraph_at() calls is_code_block(); if true,
    returns text unchanged (preserving indentation)

Tests (6 new in editor::format::tests):
  - is_code_block_detects_indented_paragraph
  - is_code_block_rejects_unindented_paragraph
  - is_code_block_ignores_blank_lines_in_paragraph
  - reformat_paragraph_skips_code_block
  - reformat_paragraph_skips_shell_heredoc
  - reformat_paragraph_handles_mixed_code_and_prose

Total: 1289 passing (was 1283; +6 new).
2026-07-05 19:31:33 +03:00
kellito eaf3c221ab tlc: Sprint 3 C14 — cmdline.execute handles trailing & for background
Previously cmdline commands were always synchronous — even
typing 'firefox &' would block TLC until the foreground shell
process exited (which for firefox would never happen because
the shell exits immediately and firefox keeps running, but
the spawn process remained a child of the shell until the shell
exited, blocking the parent).

Now run_command detects a trailing '&' (with optional
trailing whitespace) and spawns the command in a new process
group via process_group(0) (Unix). The command is detached,
TLC continues immediately, and the parent shell exits without
waiting.

Falls back to a synchronous run on non-Unix platforms.

Tests (2 new in terminal::subshell::tests):
  - run_command_backgrounded_with_ampersand_succeeds
    'sleep 5 &' returns in < 2s (not 5s)
  - run_command_backgrounded_with_trailing_whitespace
    'sleep 3 & ' (space before &) also backgrounded

Total: 1283 passing (was 1281; +2 new).
2026-07-05 19:25:47 +03:00
kellito 1826f079d3 tlc: Sprint 3 C15 — TarVfs::open rejects empty/garbage archives
The tar crate returns an empty iterator (without erroring) for
both zero-byte files and files that aren't valid tar archives.
TarVfs::open() previously succeeded in both cases, producing an
'unbrowsable' archive the user couldn't navigate.

Fix:
  TarVfs::open() now checks entries.is_empty() after list() and
  returns VfsError::Other('empty tar archive') when empty. This
  covers both truly empty files (corrupt/truncated) and garbage
  bytes (e.g., text mistakenly saved with .tar extension).

Tests (2 new in vfs::tar::tests):
  - tar_vfs_open_empty_file_errors
  - tar_vfs_open_garbage_bytes_errors

Total: 1281 passing (was 1279; +2 new).
2026-07-05 19:14:30 +03:00
kellito 08dbaf519a tlc: Sprint 3 C2 — VFS parses file:// URLs as local
Previously parse() only recognised the local:// scheme. A
file:///path URL (the standard scheme used by browsers, text
editors, and many CLI tools) would be treated as an unknown
scheme and fall through to the 'unknown scheme → local'
fallback in for_path(), which works but is silent.

Now parse() explicitly handles file:// as an alias for local://,
making the VFS path normal form 'local://' after parsing.

Tests (1 new in vfs::path::tests):
  - parse_file_scheme_is_alias_for_local

Total: 1279 passing (was 1278; +1 new).
2026-07-05 18:58:11 +03:00
kellito d14d4ad72d tlc: Sprint 3 C10 — Find dialog pre-fills from cursor entry
Previously Find dialog always opened with the default placeholder
('*.rs'). Now open_find_dialog() inspects the active panel's
cursor entry and uses its name as the initial pattern — so
pressing M-? with cursor on 'main.rs' opens the dialog with
'main.rs' already in the input, ready for refinement (e.g.
'main.rs~' for backups, 'main*' for variants).

Implementation:
  - find::FindDialog::new_with_pattern(start_dir, initial_pattern)
  - panel::Panel::cursor_entry() -> Option<&Entry>
  - widget::input::Input::set_text(s) mutable setter (the
    existing .text() builder consumes self)
  - dialog_ops::open_find_dialog() uses cursor entry name as
    the initial pattern; skips '..' and falls back to default
    when the panel is empty

Tests (2 new in find::tests):
  - new_with_pattern_prefills_pattern_input
  - new_with_pattern_empty_falls_back_to_default

Total: 1278 passing (was 1276; +2 new).
2026-07-05 18:55:04 +03:00
kellito 790e476d8e tlc: Sprint 3 C9 — empty pattern inverts/clears marks
Previously mark_pattern("") and unmark_pattern("") were silent
no-ops (glob_match returned false for empty pattern). Now they
match MC's behavior:
  - mark_pattern(""): calls reverse_marks() (toggles every
    entry; from 0 marks → all entries marked, vice versa)
  - unmark_pattern(""): clears all marks

Tests (2 new in panel::tests):
  - mark_pattern_empty_inverts_marks (0→2→0 cycle)
  - unmark_pattern_empty_clears_all_marks

Total: 1276 passing (was 1274; +2 new).
2026-07-05 18:10:55 +03:00
kellito ab2d5de81d tlc: Sprint 3 C7 — Hex viewer shows offset header row
The hex viewer now reserves the top row for a status header
showing the cursor byte offset and the total file size:
  'Offset: 00000040  /  00000100 bytes'

When the area has height < 2 (degenerate case), the header is
skipped and the full row is used for hex bytes.

Layout split: total_height >= 2 → 1-row header + body_area
(remaining rows). total_height == 1 → no header, body uses
full area. The body Paragraph is rendered to body_area (not
the full area) so the header is not overwritten.

Tests (2 new in viewer::hex::tests):
  - render_shows_offset_header_when_area_has_height
    cursor=64 → header shows 'Offset: 00000040' at row 0
  - render_skips_header_when_area_height_is_one
    80x1 area → first row starts with hex offset '00000000'

Total: 1274 passing (was 1272; +2 new).
2026-07-05 18:05:38 +03:00
kellito b058c05338 tlc: Sprint 3 C4 — Panel::set_path resolves relative paths
Previously, Panel::set_path() would pass the input path straight to
read_directory(). A relative path like 'sub' would fail because
read_directory tries to stat it relative to wherever, not relative
to the user's current working directory.

Implementation:
  Panel::set_path() now checks if p.is_relative() and, if so,
  joins it onto std::env::current_dir() before reading. Absolute
  paths pass through unchanged. Falls back to the raw path if
  current_dir() fails (process has no cwd).

Tests (1 new in panel::tests):
  - set_path_resolves_relative_against_cwd

Total: 1272 passing (was 1271; +1 new).
2026-07-05 17:53:05 +03:00
kellito 8ca31beee3 tlc: Sprint 3 C3 — delete dialog shows total recursive size
DeleteDialog header now includes the total recursive size of
all paths to be deleted (formatted via format_size). For example:
  'Delete 3 (1.2 MB) ?'  instead of just 'Delete ?'.
Computed eagerly at construction via crate::ops::count_bytes so
render() does not block on filesystem traversal.

DeleteDialog (delete_dialog.rs):
  - New field total_bytes: u64 (cached at construction)
  - new() calls crate::ops::count_bytes(&paths) to populate it
  - render() prepends '(N items)' to header and appends size when > 0

Tests (3 new in delete_dialog::tests):
  - new_computes_total_bytes_for_directory (recursive dir sum)
  - new_total_bytes_zero_for_missing_paths
  - new_total_bytes_sums_multiple_paths

Total: 1271 passing (was 1268; +3 new).
2026-07-05 17:50:09 +03:00
kellito 9ea6826a58 tlc: Sprint 2 B8 — Input widget uses theme tokens by default
Input widget colors no longer hardcoded to Color::White / Blue /
Yellow. New instances use Color::Reset as a sentinel meaning
'use the theme token', and render() falls back to:
  - fg: theme.foreground
  - bg: theme.background
  - cursor_color: theme.warning

Callers that explicitly call .fg() / .bg() / .cursor_color() still
override the sentinel — existing API surface preserved.

Changes:
  - input.rs::Input::new() defaults fg/bg/cursor_color to Color::Reset
  - input.rs::Input::render() resolves Reset → theme token
  - New pub fn cursor_color(c) builder (was missing; .fg/.bg existed)

Tests (2 new in widget::input::tests):
  - default_colors_are_reset_sentinel
  - explicit_color_overrides_reset_sentinel

Total: 1268 passing (was 1266; +2 new).
2026-07-05 17:34:47 +03:00
kellito c737337681 tlc: Sprint 2 B7 — case-insensitive theme alias expansion
Theme::by_name() now lowercases the input before matching aliases,
so 'MC-Classic', 'Mc-Dark', and 'DARK' all resolve identically
to their lowercase canonical names. Previously a user setting
config.toml [skin] name = 'MC-Classic' would silently fall back
to default (no match found for the capitalized name).

Implementation:
  - In by_name(), lowercase the input via to_ascii_lowercase()
  - Match against the existing alias map (which already covers
    mc-classic, mc-dark, mc-dark-gray, default-dark, etc.)
  - The lowercase form is passed to mc_skin::theme_by_name,
    which handles the embedded MC .ini files; the lookup there
    also becomes case-insensitive as a side effect.

Tests (2 new in terminal::color::tests):
  - by_name_resolves_aliases_case_insensitively
    MC-CLASSIC → default, Mc-Dark → dark, etc.
  - by_name_real_skin_name_case_insensitive
    DARK → dark, NICEDARK → nicedark (verifies the alias->real
    skin chain works regardless of input case)

Total: 1266 passing (was 1264; +2 new).
2026-07-05 17:28:19 +03:00
kellito 3f4f76a762 tlc: Sprint 2 B6 — streaming search for Chunked sources
Previously Viewer::search for Chunked sources read the ENTIRE
file into RAM via read_at(0, size) — defeating the memory-efficient
design of Chunked mode (a 150 MB Chunked file would load all 150 MB
just to search it).

Search (viewer/search.rs):
  New method find_all_streaming<E>(pattern, case_insensitive, next_chunk)
    - Generic error type E so callers can use their own error type
    - next_chunk closure yields (bytes, is_last_chunk) until exhausted
    - Sliding-window algorithm: keeps last (pattern.len() - 1) bytes
      from each chunk as 'tail', combines with next chunk for regex
    - Matches that span chunk boundaries are detected correctly
    - Matches across overlapping combined buffers are deduped by
      start offset after collection

Viewer (viewer/mod.rs):
  search() now dispatches by source variant:
    - Inline / Compressed: in-memory find_all (unchanged)
    - Chunked: find_all_streaming with closure yielding CHUNK_SIZE
      reads until EOF reached

Tests (5 new in viewer::search::tests):
  - streaming_single_chunk_finds_match
  - streaming_match_spans_two_chunks (xxfoobarxx at chunk 5)
  - streaming_no_matches
  - streaming_multiple_matches_in_one_chunk
  - streaming_chunk_size_smaller_than_pattern (chunk_size=1, pattern=hello)

Total: 1264 passing (was 1259; +5 new).
2026-07-05 17:25:24 +03:00
kellito 109bdc8dc3 tlc: Sprint 2 B5 — bookmark line adjustment on edit
Bookmarks now track the correct line number across insert/delete
edits (MC parity). Previously a bookmark at line 5 would still
report line 5 even after lines 1-3 were deleted, pointing at
wrong content.

BookmarkSet (editor/bookmark.rs):
  New method adjust_lines(at_line: u32, delta: i32)
    - For each mark with line > at_line: shift by delta
    - For positive delta: mark.line += delta
    - For negative delta with mark.line in deletion range:
      clamp to at_line + 1 (first surviving line after range)
    - Column is preserved across all shifts
    - Zero delta is a no-op (no allocation)

Editor (editor/mod.rs):
  New private helper adjust_bookmarks_after_edit(edit_line, lines_before)
  Wraps insert_char, insert_str, delete_back, delete_forward:
    - Captures (line_before, lines_before) before buffer op
    - Calls buffer op
    - Computes delta = lines_after - lines_before
    - Calls bookmarks.adjust_lines(line_before, delta) if non-zero

Tests (6 new in editor::bookmark::tests):
  - adjust_lines_zero_delta_is_noop
  - adjust_lines_shifts_marks_after_insertion
  - adjust_lines_shifts_marks_after_deletion
  - adjust_lines_multi_line_insertion
  - adjust_lines_deletion_clamps_to_anchor
  - adjust_lines_does_not_touch_col

Total: 1259 passing (was 1253; +6 new).
2026-07-05 17:05:28 +03:00
kellito d4ddc4e2c7 tlc: Sprint 2 B4 — configurable tab width via Alt-T cycle
Adds user-facing control over the editor tab width.

New methods on Editor (editor/mod.rs):
  tab_width() -> usize
    - Returns current visual tab width in spaces.
  set_tab_width(width: usize)
    - Sets tab width, clamped to min 1.
  cycle_tab_width() -> usize
    - Cycles 4 → 8 → 2 → 4 (default 4 → wider → narrower → back).
    - Returns the new width.

Alt-T binding (editor/handlers.rs):
  try_global_shortcut now handles Alt-T (0x74) and calls
  cycle_tab_width(). Status bar shows 'Tab width: N'.

Tab-to-indent (B3) and word-wrap (B1) both consume tab_width
via the field, so changing it via Alt-T takes effect immediately
on next render / insert.

Tests (5 new in editor::tests):
  - tab_width_default_is_four
  - set_tab_width_updates_field (verifies 0 → 1 clamp)
  - cycle_tab_width_advances_2_4_8_2
  - cycle_tab_width_from_unusual_value_resets_to_2
  - tab_to_indent_uses_current_tab_width

Config wiring note: cfg.editor.tab_width (config.rs) already has
the field with serde defaults and persistence. Wiring it into
Editor::open() requires the constructor to accept &Config and
is deferred to a future sprint.

Total: 1253 passing (was 1248; +5 new).
2026-07-05 16:59:18 +03:00
kellito 2633c40dfd tlc: Sprint 2 B3 — Tab-to-indent in editor
Tab key now has indent-aware behavior (MC parity):
  - When cursor is in the indent zone (only whitespace before it on
    the current line), Tab inserts enough spaces to advance to the
    next tab_width boundary.
  - When cursor is past any non-whitespace, Tab inserts a single
    literal '\t' character for column alignment.

New methods on Editor (editor/mod.rs):
  insert_tab_with_indent()
    - Computes next_boundary = ((col / tab_width) + 1) * tab_width
    - In indent zone: inserts (next_boundary - col) spaces
    - Otherwise: inserts literal '\t'
  is_in_indent_zone() -> bool
    - True if all bytes between line start and cursor are space/tab

Tab handler in editor/handlers.rs routes through
insert_tab_with_indent() instead of insert_char('\t').

Tests (6 new in editor::tests):
  - tab_at_col_0_indents_to_next_boundary
  - tab_at_col_4_indents_to_next_boundary
  - tab_mid_line_inserts_literal_tab
  - tab_after_non_whitespace_inserts_literal_tab
  - tab_after_whitespace_runs_continues_indent
  - tab_inside_word_inserts_literal_tab

Total: 1248 passing (was 1242; +6 new).
2026-07-05 16:55:30 +03:00
kellito 135e94b5e4 tlc: Sprint 2 B1 — word-boundary wrapping in editor
Replaces the character-counting wrap in build_wrap_map with a
word-boundary algorithm that matches MC's intent: lines break at
the last whitespace before the wrap limit, mid-word break as
fallback for overlong words, tabs counted as their visual width
(next tab_width boundary), control chars as 2 cols.

New helpers (editor/render.rs):
  visual_width(ch, col, tab_width) -> usize
    - Tabs: tab_width - (col % tab_width), min 1
    - Control chars (0x00..=0x1F, 0x7F): 2 cols (renders as ^X)
    - Other chars: 1 col

  count_wrapped_rows(line_bytes, body_width, tab_width) -> usize
    - Walks line left-to-right, tracks last whitespace position
    - On overflow: wrap at last_ws_col (preserves word boundary),
      else mid-word break
    - Stops at \n (line_bytes excludes \n in caller)

Editor (mod.rs):
  - New tab_width: usize field (default 4)
  - Matches the renderer's '    ' tab expansion in push_rendered_text
  - Future B4 will make this user-configurable

Tests:
  +12 wrap_tests in editor::render::wrap_tests:
    - visual_width_tab_expands_to_next_boundary
    - visual_width_ascii_and_control
    - count_wrapped_rows_empty_line
    - count_wrapped_rows_short_line_fits_in_one_row
    - count_wrapped_rows_exact_fit
    - count_wrapped_rows_one_char_overflow_no_whitespace
    - count_wrapped_rows_word_boundary_break
    - count_wrapped_rows_three_words_at_width_six
    - count_wrapped_rows_long_word_falls_back_to_mid_word
    - count_wrapped_rows_tab_visual_width
    - count_wrapped_rows_zero_width_returns_one
    - count_wrapped_rows_stops_at_newline

Total: 1242 passing (was 1230; +12 new).
2026-07-05 16:51:33 +03:00