6caa82597ea35ae2c35a0c6048fa3545946679de
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6caa82597e |
fix dangling tail anchors in reflow on partially-filled lines (T-92)
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
`_LineReflow._addPart`'s post-loop block reparents anchors past the
source line's trimmed content onto whatever `_builder._result` was
active at that moment. When no further content lands in the builder
(non-wrapped lines, or the last logical line of a wrapped run),
`finish()` was emitting only when `_builder.isNotEmpty` — leaving
the empty result line with the reparented anchor unappended. The
anchor then pointed to a `BufferLine` that the reflow output never
included, `lines.replaceWith(reflowResult)` discarded it, and
`CellAnchor.attached` returned false. The selection controller's
`extent.attached` null-check then dropped the selection silently
on resize.
The fix adds a `_LineBuilder.hasAnchors` getter and uses it in
`finish()` so the builder line is also emitted when it's carrying
an anchor — even when otherwise empty. Trade-off: an extra trailing
line in the reflow output when (and only when) a tail anchor would
have dangled. `Buffer.resize` already pads the result to `newHeight`
afterward, so for the common case (resize fits inside view height)
the total ring length is unchanged; only when the result already
meets / exceeds `newHeight` does the buffer grow by one. Acceptable
in exchange for selections surviving a width change.
User-visible trigger paths:
- `SelectAllTextIntent` (Ctrl+A) creates an end anchor at
`x = viewWidth` on the last buffer line — exactly the past-
trimmed-length position. Resizing narrower while the selection
was active dropped it.
- Mouse drag selections past the end of a partially-filled line
hit the same shape.
Tests:
- The pre-existing `reflow anchors on the source line tail (past
trimmedLength) get reparented` test was originally written to
document the buggy behaviour ("anchor moves off the source onto
a dangling builder line"). Updated to assert the post-fix
contract: `out.contains(tail.line)` is true.
- New `SelectAllTextIntent-shaped end anchor survives shrink`
regression test that mirrors the actual production trigger
(anchor at `x = viewWidth` on a partially-filled line, narrower
reflow).
reflow.dart 71/71 → 72/72 (the new getter is a one-liner). Project
coverage 54.62% unchanged within rounding.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
3196c4957f |
test sweep: cover core/(root) — cell, cursor, charset, tabs, reflow (T-91)
Adds test/terminal/core/core_test.dart — 36 unit tests across the small standalone files that sit directly under `lib/src/terminal/src/core/*.dart`: - CellData (constructor + empty + getHash + toString), - CursorStyle (default ctor, every set/unset attr getter pair, all three colour-mode setters per channel, reset, the .empty singleton) + CursorPosition, - Charset (translate, designate/use, save/restore, asciiTranslator, decSpecGraphicsTranslator with in-table, out-of-table, and high- codepoint paths), - TabStops (default 8-column grid, find with empty-range / out-of- bounds / no-stop-in-range cases, setAt/clearAt/clearAll/reset), - reflow (empty input, single-line passthrough, grow, shrink-with- split, wrapped-run continuation, wide-char boundary on the new width, inner wide-char clamp during _addPart, anchor reparent on the main path, anchor reparent past trimmedLength). Two source-side cleanups folded in: - `CursorStyle.isItalis` was a defined-but-never-called getter with a typo. No external callers reference it; renamed to `isItalic` in the same change as the test that exercises it. - `_LineBuilder.isEmpty` in reflow.dart was dead — the only callers use `isNotEmpty` or check `_lines.isNotEmpty` separately. Removed. Coverage delta: - cell.dart: 3/7 → 7/7. - charset.dart: 12/25 → 25/25. - cursor.dart: 2/62 → 62/62. - tabs.dart: 0/23 → 23/23. - reflow.dart: 24/72 → 71/71 (file shrank by one line after the isEmpty getter removal). - Total project: 52.72% → 54.62%; coverage_floor bumped 52 → 54. Note for follow-up (not blocking): the post-loop "anchor.x >= to" branch in reflow's `_addPart` reparents anchors past trimmedLength onto whatever builder line is active at that moment. If no subsequent content is added (no wrapped continuations after the last shrink iteration), that builder line is never emitted by `finish()` and the anchor lands on a dangling reference. The path is exercised by the new test, but the contract it implements is arguably broken — anchors that should follow the source content end up off the visible buffer. Worth a separate ticket if real terminals trip it. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
f3e164b834 |
test sweep: cover core/input/ keytab + handlers (T-91)
Adds test/terminal/input/input_test.dart — 58 unit tests covering the keytab tokenizer, parser, unescape helper, KeytabRecord toString shapes, Keytab.find modifier-matching rules, and the four TerminalInputHandler implementations (CascadeInputHandler, KeytabInputHandler, CtrlInputHandler, AltInputHandler). Highlights: - keytabUnescape: every documented backslash escape + \xHH hex. - LineReader: peek/take/done, whitespace skip, readString (alphanumeric/underscore), readUntil (both exclusive and inclusive variants). - tokenize: keyboard-name and key-define lines, comment + blank stripping, shortcut vs string actions, error paths on malformed input. - KeytabParser: full mode-flag matrix, error paths on every defensive throw reachable through the public addTokens API (stray non-keyboard token, missing colon, modeStatus value other than '+'/'-', non-mode token after modeStatus, action token of wrong type, second token of wrong kind for both _parseName and _parseKeyDefine). - KeytabRecord.toString covers every supported flag (Alt, Control, Shift, AnyMod, Ansi, AppScreen, KeyPad, AppCuKeys, AppKeyPad, NewLine, Mac). - Keytab.find: -Shift / +AnyMod / -AnyMod gating, mode-flag filters (newLine, appKeyPad, appScreen, macos, appCursorKeys, keyPad), -Ansi (VT52) skip, fallthrough to fallback record, null when no key matches. - KeytabInputHandler: every modifier combination's `*` placeholder expansion (1..8 inclusive), default-keytab fallback, no-match null, no-* passthrough. - CtrlInputHandler: A..Z → 0x01..0x1A; null without ctrl, with shift / alt, or on non-letter keys. - AltInputHandler: A..Z → ESC + uppercase; null without alt, with shift / ctrl, on macOS, or on non-letter keys. - defaultInputHandler integration: keytab routing, fallthrough to CtrlInputHandler. Coverage delta: - core/input/handler.dart: 4/54 → 54/54. - keytab.dart: 0/29 → 29/29. - keytab_record.dart: 0/44 → 44/44. - keytab_token.dart: 0/82 → 80/82 (the two remaining lines are defensive throws inside `_parseKeyboardNameDefine` / `_parseKeyDefine` that are unreachable from tokenize() — the callers only enter those functions after the `_isKeyboardNameDefine` / `_isKeyDefine` guards in the same file, so the inner readString always matches). - keytab_parse.dart: 0/65 → 63/65 (the two remaining lines mirror the same shape — _parseName and _parseKeyDefine both check the first token's type, but addTokens only delegates to them after matching that type, so the throws are dead defensive code). - keytab_default.dart: 0/4 unchanged — that's the file's own `void main()` debug entrypoint that prints the parsed default keytab; not part of the runtime contract. - keytab_escape.dart: 0/14 → 14/14. - Total project: 49.31% → 52.53%; coverage_floor bumped 49 → 52. The 4 dead defensive throws are flagged but not removed in this commit — they're a code-style call (defensive paranoia vs. dead- code cleanup) that belongs in a separate review, not folded into a test sweep. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
9331055b44 |
test sweep: cover EscapeParser + EscapeEmitter (T-91)
Adds test/terminal/escape/parser_test.dart — 70 unit tests covering the parser's dispatch surface end-to-end: - single-byte controls (BEL, BS, HT, LF/VT/FF, CR, SO, SI), - ESC sequences (D, E, H, M, =, >, 7, 8, ( name, ) name, unknown), - CSI cursor moves (A/B/C/D/E/F/G/H/d/f) with default + 0-as-1 fallback semantics, - erase / scroll / line-insert/delete / chars (J, K, L, M, P, S, T, X, @, b, g, r), - device attributes (c / >c / =c) and DSR (5, 6), - window manipulation (CSI 8 t resize, CSI 18 t sendSize, ignored no-op codes, malformed CSI 8 t), - mode set/reset (h/l, ? prefix for DEC modes — covering ?1, ?3, ?5, ?6, ?7, ?9, ?12, ?25, ?47, ?66, ?1000, ?1002, ?1003, ?1004, ?1005, ?1006, ?1007, ?1015, ?1047, ?1048, ?1049, ?2004, + unknown fallback), - SGR styling (resets, set/unset for every attr, 16-colour foreground + background, 256-colour, 24-bit RGB, 39 / 49 resets, unknown → unsupportedStyle), - OSC 0/1/2 (BEL- and ST-terminated), unknown OSC, incomplete sequence held back across writes, - unknown CSI final byte → unknownCSI, - token bookkeeping (tokenBegin / tokenEnd advance with consumed bytes). Plus EscapeEmitter — every reply string format (primary / secondary / tertiary device attributes, operating status, cursor position, bracketed paste, size). Coverage delta: - parser.dart: 0 / 462 → 504 / 514 (98.1%; the remaining 10 lines sit inside the `// ignore: dead_code` SGR loop, which I'm surfacing for separate review rather than extending tests around). - emitter.dart: 1 / 11 → 11 / 11. - Total project: 43.20% → 49.38%; coverage_floor bumped 43 → 49. Two real source-code issues found while writing tests, fixed in the same commit: 1. Swapped docstrings on `_escHandleSetAppKeypadMode` / `_escHandleResetAppKeypadMode`. The function names + dispatch table + bodies all match the VT spec correctly (ESC = enables, ESC > disables); only the doc-comments were swapped. Now read the right way around. 2. `case 10061000:` in `_setDecMode` was unreachable (no DEC mode has that value). Almost certainly a typo where `case 1006:` meant to glue onto `case 1000:` but a newline went missing. Mode 1006 is already handled separately at its own clause as `MouseReportMode.sgr`. Removed. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
c233b7607d |
test sweep: cover Buffer (T-91)
Adds test/terminal/buffer/buffer_test.dart — 67 unit tests against the Buffer orchestrator on top of BufferLine. Drives a fake TerminalState through writes, cursor moves, scroll regions, erase commands, line insert/delete, resize (with and without reflow), word-boundary lookup, getText, and the toString debug dump. Coverage delta: - buffer.dart: 0 / 260 → 260 / 261 (one while-loop-body line Dart coverage doesn't instrument distinctly; the loop's effect is exercised end-to-end by the reflow-pad test). - Total project: 39.12% → 43.20%. - pubspec.yaml `coverage_floor:` bumped 39 → 43. Notes: - The fake TerminalState (`_State`) is a per-file impl rather than a shared fixture; it stays close to the test that exercises it and avoids forcing other terminal tests to depend on a one-shape- fits-all stub. - Tests that walk through `lineFeed` use `lineFeedMode: true` so the column resets between newlines — otherwise the saturated cursor X from a previous full-width write spills the next write onto an extra line via `writeChar`'s autoWrap branch. This closes the `core/buffer/` sub-area for T-91 — every leaf file in `lib/src/terminal/src/core/buffer/` is now at >= 96% line coverage; the only outliers are Dart-coverage-instrumentation quirks, not real gaps. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
c744c8ce4b |
test sweep: cover BufferLine + CellAnchor (T-91)
Adds test/terminal/buffer/line_test.dart — 51 unit tests covering BufferLine and CellAnchor, hitting every reachable line in lib/src/terminal/src/core/buffer/line.dart (192 / 192). Coverage delta: - line.dart: 0 / 194 → 192 / 192 (file shrank by two lines after the prior commit's iteration fix folded two for-loop heads into for-each-toList). - Total project: 36.39% → 39.12%. - pubspec.yaml `coverage_floor:` bumped 36 → 39 in lockstep. Highlights: - All packed-cell encodings (foreground/background/attrs/content channels, codepoint+width packing, CellData round-trips). - `eraseRange` wide-char neighbor extension on both ends. - `removeCells` / `insertCells` shift logic, anchor reposition, and the wide-tail-erase branch (insertCells case where the post-shift last cell carries a wide marker). - `resize` exercising the [64, 256) capacity-doubling branch and the >=256 +32 branch separately. - `getTrimmedLength` cols-clamp behaviour for null/over-capacity. - `getText` skip-trailing-wide-char branch. - `CellAnchor` lifecycle: detached construction, `reposition`, `reparent` (both detached→attached and between owners), `dispose`, attached y/offset via a real IndexAwareCircularBuffer. Also cleans up five `unrelated_type_equality_checks` analyze infos in test/terminal/buffer/range_test.dart by typing the RHS as Object when intentionally probing the type-mismatch branch of operator==. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
eb32396bd4 |
test sweep: cover BufferRange family (T-91)
Adds test/terminal/buffer/range_test.dart — 38 unit tests covering the small pure-Dart files in lib/src/terminal/src/core/buffer/: - cell_offset.dart 23 / 23 (was 0 / 23) - range.dart 13 / 13 (was 0 / 13) - segment.dart 13 / 13 (was 0 / 13) - range_line.dart 30 / 30 (was 0 / 30) - range_block.dart 48 / 48 (was 0 / 48) Total project line coverage 34.90% → 36.39%; coverage_floor in pubspec.yaml bumped 34 → 36 in lockstep. Tests exercise the abstract BufferRange operator==/hashCode/toString via a local _StubRange (BufferRangeLine and BufferRangeBlock both override those, so the base versions are otherwise unreachable — worth a stub rather than carving the lines out of coverage). The denormalized-input branches in Block contain/toSegments/extend get explicit cases too. Pure Dart, no Flutter dependency — uses package:test/test.dart and runs in <100ms. First batch under T-91; line.dart and buffer.dart land in subsequent commits with their own floor bumps. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
a67a768592 |
restore semantic bold rendering in terminal panes (T-73)
Bold attributes from terminal escapes now render in a real bold
weight instead of being silently flattened.
- pubspec.yaml: register JetBrainsMono Bold + BoldItalic at
weight 700 under family JetBrainsMono. Files already shipped on
disk; only the registration was missing.
- assets/licenses.yaml: bump JetBrainsMono weights_bundled to
[Regular, Italic, Bold, BoldItalic] per D-42 (the entry must
match what is actually wired into the family).
- lib/src/terminal/src/ui/painter.dart: revert the `bold: false`
override and drop the workaround comment. Bold now flows from
CellFlags.bold to TextStyle.fontWeight.
- test/terminal/painter_bold_metrics_test.dart: load Regular and
Bold via FontLoader and assert paragraph maxIntrinsicWidth is
identical (cell-grid drift = 0). JetBrainsMono Bold's monospace
by spec; this test is the canary for the day someone swaps the
font.
- test/goldens/goldens/{ci,linux}/clide_button.png: regenerate.
ClideButton's label renders slightly heavier on the bold variant
(expected — 0.28% pixel diff before regen).
Earlier perception of over-bolding in the Claude pane was
synthetic-bold smearing (Flutter overpaints when no Bold.ttf is
registered for the family), not legitimate bold rendering. Visual
A/B confirms a real Bold face renders crisp emphasis without the
smear, so no per-pane renderer config is needed.
Co-Authored-By: Claude <noreply@anthropic.com>
|