Commit Graph

6 Commits

Author SHA1 Message Date
Levi Neuwirth 8b1eff10df
feat(view): WrapMode, threaded to every viewport and pinned to Truncate
Inert by construction. Adds the type and the Viewport field, sets all
31 construction sites to Truncate, and changes no rendering: --lib is
1900/0 and crdt 2085/0, the same counts as the parent commit.

The field is required rather than defaulted on purpose. A default would
have let 31 sites stay silent about which behavior they meant; a
required field makes each one state it, so the pre-existing sites now
read as deliberately unwrapped rather than merely untouched. The
compiler enumerated them, including five integration tests --- Viewport
is public API, so this is a real break, and the break is the point.

The render driver is pinned to Truncate too. The wrap path does not
exist yet, and exposing a mode before the cursor mapping honors it
would ship a setting that renders one thing and navigates another ---
the shape of defect this lane exists to remove, not add.

Two notes on getting here, since both were nearly landed:

The first mechanical patch matched every `folds,` line and put a wrap
field into function call sites and a FoldStore literal. Scoping the
insertion to Viewport literals cut it from 40 sites to 31. The compiler
caught it, but only because a struct field cannot be mistaken for an
argument; a same-arity call would have compiled.

While rewriting the character walk I changed the wide-character edge
case --- a double-width glyph with one cell left now breaking instead
of painting a lone lead cell. That is arguably better behavior and it
is NOT this commit's to make: Truncate must be byte-identical, and an
"improvement" smuggled in beside a refactor is how identity cases stop
being identity cases. Reverted; the walk is untouched.

Gates: fmt, workspace clippy -D warnings, diff --check, --lib 1900/0,
crdt 2085/0, tab_width 2/0, listview 26/0, compile_mode 73/0,
folding 21/0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bqGA6s9tTUFzYpbeW3tai
2026-08-07 09:48:08 +02:00
Levi Neuwirth 313b1ff77a feat(fold): Stage 2 — grid (daemon-rendered) collapse
Implements docs/folding-stage2-framing.md rev 4. The daemon grid
renderer now consults the fold store: hidden lines are omitted, rows
below shift up, and every consumer that assumed
`display_row = source_line - view_top` routes through one shared
projection. No wire schema change and no protocol bump (Bet B6) — the
collapse is entirely daemon-side; the GPU path is Stage 3.

The spine (Q#FD12) is `src/fold_view.rs`: a `VisibleLineMap` derived
from `FoldRegistry::folds` plus a window's line offsets and never
stored. Its unit is a merged **hidden component** — overlapping OR
adjacent hidden intervals unioned, each keeping the one visible
`head_line` and that line's exact `head_position`. Adjacent intervals
merge because the later fold's head is itself hidden, which is what
makes nesting, shared heads, and crossing overlap all resolve to a
head that can actually render (round-3 F2).

Instances are short-lived and built **per rendered window** and **per
command/event operation**, never once per frame: `paint_frame` renders
several windows that may show different buffers, so a singleton would
leak one pane's folds into another (round-2 F2). The render instance
rides on a lifetime-bearing `Viewport<'a>` as `Option<&'a
VisibleLineMap>` — a shared ref is `Copy`, so `Viewport` stays `Copy`
(Bet B7).

Rendering:
- `TextView::render` walks visible lines; the head line gets a
  trailing content-area ellipsis (Q#FD13).
- The gutter walks visible lines too: Absolute keeps the raw `line+1`,
  Relative/Hybrid measure VISIBLE distance anchored on the cursor's
  visible head (Q#FD14). The fold glyph takes the col-0 sign cell only
  when a gutter exists — line numbers default to Off, so with no gutter
  the ellipsis is the sole marker (Q#FD20, round-1 F3). A diagnostic
  clamped onto the head wins that cell by paint order.
- A diagnostic on a hidden line clamps its SIGN to the outermost
  visible head (most-severe merge); the squiggle needs a real row, so
  only the sign clamps (Q#FD15).
- Caret, local selection endpoints, and peer cursors project via
  `visible_position_of` — the head row AND the head's end-of-content
  column, never an arbitrary column (round-2 F3). Peer presence derives
  the RECIPIENT window's map.
- Style/search/completion overlays route through
  `Viewport::row_offset_of`; the mode-line indicator reckons in
  visible-line space.

Command/event time is scoped per frontend (Q#FD21): a
`fold_projection` flag on `FrontendView`, set at attach from the
negotiated `semantic_render` bit (grid ⇒ true, semantic ⇒ false until
Stage 3, LOCAL ⇒ true) and never inferred from a `FrontendId` (Bet
B8). Without it, shared `EditorCore` motion would make a simultaneous
unfolded GPU session's cursor skip lines it still displays. The map's
two axes stay separate (round-3 F1): the acting frontend supplies the
policy, the operation's TARGET window supplies the buffer — a wheel
event names a pane without activating it.

Motion (Q#FD17, ruled: include), paging, wheel, the click inverse, and
the auto-scroll clamp all step by visible lines under that gate;
motion from a hidden logical cursor normalizes to the visible head
first. `view_top` stays a source-line index (Bet B5), set only via
`clamp_view_top` so it never rests hidden.

Unfold widening (Q#FD19): the pre-edit unfold moves to the top of
`apply_active_edit` — one funnel that subsumes the six primitives'
calls and covers yank + query-replace, both of which place point at
the edit site first. Interactive Lua mutators hook the common
`run_buffer_edit`, above the managed/bypass split, gated on
`InteractiveCommandOrigin` AND the edit targeting that frontend's
active-window buffer. The remote/optimistic-CRDT path stays excluded
(Stage 3); undo/redo unfold stays deferred.

Acceptance: `tests/folding_stage2_acceptance.rs`, 35 tests asserting
on the real `paint_frame` cell grid, covering framing items 1–14
including crossing folds, a nested deeply-hidden cursor, a split of
two different buffers with an inactive-pane wheel, and simultaneous
grid+semantic motion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5BkezMppbpCgGAYk2ftxV
2026-07-23 19:24:07 -04:00
Levi Neuwirth 9f7bc77f44 feat(render): unify tab-width projection
Share one fixed eight-column tab-stop contract across core and GPU renderers. Consolidate byte-to-display-column accounting, expand GPU code tabs with source provenance, align caret/hit/decoration geometry, and refresh minimap projection on edits.
2026-07-22 15:03:30 -04:00
Levi Neuwirth c1122691ad feat(tui): diagnostic gutter signs riding the line-number gutter (sub-arc 2)
Sub-arc 2 of the UX arc, TUI half. When a window reserves a line-number
gutter, lines with diagnostics get a severity-colored sign glyph (E/W/I/H)
in the gutter's leading column — closing the last deferred Task #23 item.
No protocol/daemon change: the per-line severity is already frontend-side
(the diag store the DiagnosticView already reads).

- `Viewport` gains `gutter_w` so overlays can reach the gutter's leading
  column at `cell_origin.col - gutter_w`; the text area is already shifted
  past it, so viewport-relative painters stay gutter-agnostic.
- The gutter's number pass now runs *before* the overlays (was after), so
  the DiagnosticView can draw its sign into the gutter's blanked leading
  column without the number pass erasing it.
- DiagnosticView: with a gutter, draw the severity sign glyph colored by
  `underline_color()`; without one, keep the legacy column-0 background
  marker (the "fake gutter" that predates a real gutter column). Extracted
  to `paint_line_markers` to keep `render` under the line cap.

The number never reaches column 0 (>=1 leading pad by construction), so
sign and number coexist. Diagnostic signs currently ride the line-number
gutter (visible when line numbers are on); a signs-without-numbers mode is
deferred.

Test: gutter_sign_replaces_the_column_marker_when_a_gutter_is_reserved.
Validated: fmt + clippy --all-targets clean both flavors; 1441 lib + 22
diag tests pass. Needs a TUI eyeball before the GPU half.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U
2026-07-06 19:17:43 -04:00
Levi Neuwirth 8ed15d86a1 optimistic-apply daemon support — Loro text-delta hot path for remote inserts
The GPU frontend's per-keystroke edits arrive as FrontendEvent::CrdtOp.
The old apply path materialized the whole document and diffed it per
op — O(file) per typed character on the daemon main thread.

- CrdtState: persistent text-projection subscription (capture gated by
  an AtomicBool so per-keystroke imports don't register/drop
  callbacks); import_updates_with_text_deltas returns Loro's deltas;
  unicode_to_utf8_pos converts the insert point.
- Buffer::apply_remote_crdt_op: the common single-insert delta applies
  straight to the rope; deletes/compound updates keep the conservative
  materialize+diff fallback. UTF-8 position regression test included.
- SyntaxRegistry::has_pending_parse_job_for: the main-thread
  "parse in flight" bit render producers need for settle-gating.
- TextView::pos_to_display: stack buffer for short line prefixes +
  valid_up_to() boundary trim — removes a per-cursor-move allocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:45:23 -04:00
Levi Neuwirth 4da4b09d5d Initial commit: v0.1.0 2026-05-03 19:51:06 -04:00