Skip to content

perf(tui): keystroke latency: @file completion from one fd listing, long-line fast paths, regular-mode frames - #77

Merged
Kushalkhemka merged 3 commits into
pi-boltfrom
perf/tui-input-096
Oct 20, 2026
Merged

Kushalkhemka merged 3 commits into
pi-boltfrom
perf/tui-input-096

Conversation

@Kushalkhemka

Copy link
Copy Markdown
Member

Problem

Typing in the interactive editor did work in proportion to things other than the keystroke:

  • @file completion started two fd processes per key (after a 20 ms debounce): 14-31 ms per key on a 2.7K-file repo, 0.2-1.3 s per key on a 1.46M-file tree when few names match.
  • A long prompt line was segmented with Intl.Segmenter on every key (wrap, width, Backspace/Delete/Left/Right): 5.0M instructions per key+render at 2,000 characters, 18.3M at 8,000.
  • Regular (non-fullscreen) mode scanned every transcript line for fake cursors on every frame: 1.55 ms per frame at 27.5K lines.

Fix

  1. @file completion from one fd listing per completion (FdFolderIndex in autocomplete.ts). The first key of an @ token starts one fd walk of the folder (same options, no pattern). Later keys filter it in-process with fd's semantics: regex search in the file name, . matches any character (fd sets dot_matches_new_line), smart case, Unicode simple case folding. The 100-result caps and the ranking are unchanged. A query that extends the previous one filters the previous matches.
    • Only plain queries are filtered (no regex syntax but ., no leading -, no /); others run fd.
    • A new token walks again, so added and removed files show up. A listing older than 3 s is walked again in the background.
    • Until the listing is ready, a key runs fd as before, racing the listing.
    • Folders over 100,000 entries keep fd per key; complete results (under 100) are filtered in-process for the keys that extend them.
  2. Editor long lines (editor.ts). A line whose characters are each a grapheme of their own is wrapped and measured without segmenting it: ASCII, Latin, typographic punctuation, CJK ideographs, kana, Hangul syllables and fullwidth forms. The grapheme at the cursor is looked up with Segments.containing(). JSC's containing() is wrong on the first half of a surrogate pair: for "b👍" at index 1 it returns "b👍" at 0 (V8 gets this right). The lookup steps to the second half, and a test covers it under bun. Text without a trigger character skips the autocomplete trigger regex. Paste markers keep the old path.
  3. Regular-mode frames (tui-main-screen.ts). Fake-cursor resolution reuses the previous frame's result for unchanged lines. It returns a copy, because extractCursorPosition() edits the array; a test covers this.

Numbers (0.9.5 runtime, AOT, JIT off; base = 16807c4)

before after
@file key after the first, 2.7K files (in-process wall) 16-22 ms 0.15-0.7 ms
@file first key of a completion 13.5-16 ms 17-25 ms
@file no-match keys, 1.46M-file tree, after a complete result 176-1300 ms 0.1-0.2 ms
real TUI (tmux) @file: key to last screen change, later keys (incl. 20 ms debounce + frame) 67 ms 41 ms
real TUI @file: Pi CPU for "@editor" (fd CPU not counted) 29.2 ms 15.3 ms
editor, 2K ASCII line, key+render 5.0M instr 0.62M instr
editor, 8K ASCII line, key+render 18.3M instr 1.35M instr
editor, 8K ASCII line, Left/Right+render 3.9M instr 0.85M instr
editor, 8K CJK line, Left/Right+render 3.8M instr 0.36M instr
editor, empty prompt, key+render 125-153K instr 110-154K instr
real TUI 2K line: Pi CPU per letter / backspace / left 0.62 / 0.86 / 0.48 ms 0.33 / 0.47 / 0.31 ms
regular mode frame, 27.5K transcript lines 1.55 ms 1.1 ms

Identical behaviour

  • editor-long-line.test.ts checks the new paths against the old ones.
    • Random lines wrap exactly as the segment-based path: ASCII, Latin, CJK, combining marks, emoji, ZWJ sequences, flags.
    • Every code point in the fast-path set is checked for its width and for not joining its neighbours.
    • Grapheme lookups match full segmentation, with and without paste markers.
    • Random edits and cursor moves match a reference model.
    • It passes under node and under bun.
  • A differential fuzz of the old and new Editor (250K+ random keys under bun: up/down, word moves, pastes, width changes) gave identical renders, text and cursor.
  • autocomplete-fd-index.test.ts checks completion against fd.
    • The in-process matcher gives the same names as fd's own output for 33 queries (case, folding, dots, CJK).
    • Typed, edited, scoped and quoted sequences give the same suggestions as the per-key fd path.
    • A new token sees added and removed files, and an old listing is refreshed in the background.
    • When fd refuses the listing, completion falls back to fd per key.
  • A differential of the old and new providers on two real trees compared 740 keys whose fd results are complete: 0 differ. Keys with 100 or more matches were skipped, because fd's cap keeps an arbitrary 100.
  • npm run check is clean, and all 1,156 tui tests pass.

Not done

  • Typing on long CJK lines is still O(line): 5.0M instructions per key at 2K, down from 6.1M. Cursor moves are fixed.
  • Regular-mode frames still make O(transcript) identity passes. That fix would touch the render path during streaming.

…ting, long-line fast paths, regular-mode frames

- @file completion: one fd walk of the folder per completion (the @ token being typed), filtered in-process for each
  key with fd's own matching (regex search in the file name, '.' matches any character, smart case) and caps; until the
  listing is ready, keys run fd as before. Folders over 100,000 entries keep fd per key, filtering complete results for
  the keys that extend the query. 14-31 ms per key becomes 0.1-0.7 ms after the first key.
- Editor: lines whose characters are graphemes of their own (ASCII, Latin, typographic punctuation, CJK, kana, Hangul,
  fullwidth) wrap without Intl.Segmenter; the grapheme at the cursor is looked up directly (stepping off the first half
  of a surrogate pair, where JSC's Segments.containing() returns the grapheme before as well); the autocomplete trigger
  regex is skipped for text without a trigger character.
- Regular mode: fake-cursor resolution reuses the previous frame's result for unchanged lines.
Copilot AI balanced review requested due to automatic review settings October 20, 2026 01:14

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, someone with write access to this repository can comment @claude review on this pull request to trigger a review.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T01:18:17.104017Z 6c51fd9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c51fd9bed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/tui/src/autocomplete.ts
A second @ token started before the first folder listing finished started a new walk, and the first fd kept walking (up
to 100,001 entries). Each listing now has an AbortController: a new walk of the folder, a completion in another folder,
or the folder's eviction kills the previous fd. An aborted walk does not mark the folder as unlistable.
@Kushalkhemka

Copy link
Copy Markdown
Member Author

Fixed in 5c30306. Each folder listing now has an AbortController, and its fd is killed when a new walk of the same folder starts, when a completion starts in another folder, or when the folder is evicted. An aborted walk no longer marks the folder as unlistable.

The new test uses an fd wrapper whose listing never ends. It checks that each superseded listing process is killed and that only the current one keeps running. Without the fix the test fails. npm run check passes, and there was no conflict with origin/pi-bolt.

@Kushalkhemka
Kushalkhemka merged commit 016595a into pi-bolt Oct 20, 2026
12 checks passed
@Kushalkhemka
Kushalkhemka deleted the perf/tui-input-096 branch October 20, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants