Skip to content

feat: add Pane Search Phase 1 checkpoint (v1.3.1) - #44

Merged
HelloThisWorld merged 3 commits into
mainfrom
feature/v1.3.1-pane-search-phase1
Aug 11, 2026
Merged

feat: add Pane Search Phase 1 checkpoint (v1.3.1)#44
HelloThisWorld merged 3 commits into
mainfrom
feature/v1.3.1-pane-search-phase1

Conversation

@HelloThisWorld

Copy link
Copy Markdown
Owner

Summary

Phase 1 of the Pane Search roadmap toward v1.4: Ctrl+F now opens Find in
the active pane. This is engineering checkpoint 1.3.1 — not an alpha,
not a beta, and not a downloadable milestone. GitHub Latest and WinGet keep
pointing at v1.2.0, and v1.3.0-beta3 remains the newest published
prerelease.

Root cause of "Ctrl+F does nothing": defaults.json never bound ctrl+f
to anything, so the chord fell through the ActionMap and TermControl
forwarded a raw ^F (0x06) to the shell connection. The mature upstream
search pipeline behind Ctrl+Shift+F was fully functional the whole time.

Do not add the build label — Phase 1 is not a downloadable milestone,
and quick validation is the intended CI depth for this PR.

Related issues

None — roadmap work item (Pane Search Phase 1).

Detailed changes

Implementation

The feature change is a single default keybinding in
src/cascadia/TerminalSettingsModel/defaults.json:

{ "keys": "ctrl+f", "id": "Terminal.FindText" },
{ "keys": "ctrl+shift+f", "id": "Terminal.FindText" },

Ctrl+Shift+F stays bound as a compatibility alias — the ActionMap
natively supports several chords per action (CopyToClipboard already has
three). Both chords remain ordinary remappable keybindings; a user who
needs a literal ^F for a terminal application can unbind ctrl+f
({ "command": "unbound", "keys": "ctrl+f" }) and keep Find on the alias
or the Command Palette. No XAML key handling was added; dispatch uses the
normal action pipeline. The winTerm Ctrl+C reservation fixup is scoped to
origin == User && id == Terminal.CopyToClipboard && keys == "ctrl+c" and
does not touch this binding, and ctrl+f collides with no winTerm custom
shortcut (ctrl+t, ctrl+tab, ctrl+,, ctrl+0, …).

Search architecture reused

No second search engine, no index, no buffer duplication, no
src/winterm/Search subsystem. The existing pipeline carries everything:

  • SearchBoxControl — the pane-local overlay (x:Load="False" lazy XAML
    element inside TermControl), opened by
    TermControl::CreateSearchBoxControl(), which focuses the TextBox and
    preserves the single-line selection-populates-search behavior.
  • Live search — TextBoxTextChangedSearchChanged
    TermControl::_SearchChangedControlCore::Search(ExecuteSearch=false)
    Search::ResetTextBuffer::SearchText over the full searchable
    buffer including scrollback.
  • All-match highlighting — Search::Results() (a
    std::vector<til::point_span> of every match) flows into
    Terminal::SetSearchHighlights; SetSearchHighlightFocused marks the
    current match and Renderer::TriggerSearchHighlight invalidates the
    affected regions. The existing scrollbar pips read the same rows.
  • Navigation — Enter/Shift+Enter raise Search
    ControlCore::Search(ExecuteSearch=true)Search::FindNext, which
    wraps modulo the match count.
  • Cleanup — Esc/close → _CloseSearchBoxControl
    ControlCore::ClearSearch() clears highlights, resets the searcher, and
    refocuses the terminal.
  • Input isolation — TermControl::_KeyHandler returns early while
    _searchBox->ContainsFocus(), so search-box keystrokes never reach the
    connection; the TSF/IME focus guard does the same for composition input.
  • Empty query — TextBuffer::SearchText returns no results for empty or
    all-whitespace needles without doing any expensive work, so an emptied
    search box drops all highlights.

Search stays demand-driven: with the box closed there is no polling, no
timer, and no background scan (Search::IsStale re-evaluates only on the
next explicit search request).

Active-pane behavior

ShortcutAction::FindTerminalPage::_HandleFind
_senderOrFocusedTab()TerminalPage::_Find(tab)
Tab::GetActiveTerminalControl()
_activePane->GetLastFocusedTerminalControl()
CreateSearchBoxControl(). The existing pane model stays the single source
of truth — no new "active search pane" state was introduced. Each
TermControl owns its own SearchBoxControl and its ControlCore owns
its own Search instance, so search state in one split pane cannot bleed
into a sibling. Pressing Ctrl+F after moving pane focus targets the newly
active pane.

Version change

1.3.0-beta31.3.1, an engineering checkpoint following the exact
v1.2.1–v1.2.4 Command Timeline precedent: plain version,
channel: stable, empty module prerelease, package version 1.3.1.0, and
tag v1.3.1 added to the release workflow's checkpoint-tag list, so a
pushed checkpoint tag runs quick validation only and can never produce
release artifacts or move Latest. All version surfaces advanced together:
both version.json files, ReleaseMetadata.h, the MSIX manifest, three
.rc files, the PowerShell module manifest and runtime, the workspace
descriptor/serializer fallbacks, the pinned literals in the verification
scripts (verify-version, verify-branding, test, test-visual-progress,
package-shell-assets, test-release-workflow), both READMEs'
source-version lines, docs/current-progress.md, and CHANGELOG.md.

Documentation

  • CHANGELOG.md: 1.3.1 checkpoint section (Ctrl+F active-pane search,
    live search, all-match highlighting, pane isolation, pipeline reuse); the
    previous Unreleased documentation notes fold into this section.
  • docs/user/keyboard-shortcuts.md: new Search section documenting
    Ctrl+F, the alias, navigation, close behavior, input isolation, and how
    to reclaim raw Ctrl+F.
  • docs/current-progress.md: repository state for the checkpoint and the
    Pane Search roadmap status.
  • Wiki Development-Changes.md ledger entry follows this PR (needs the
    final SHA and PR number).

Known limitations / deferred Phase 2+ work

Explicitly deferred, by design: scrollbar overview markers, the
WinTerm-specific search UI redesign, performance hardening (debounce,
worker threads, caching), and large-scrollback optimization. The stock
upstream search box visuals and status display remain as-is for this
phase. Out of scope entirely: cross-tab/global/all-pane search, Command
Timeline search, persistent history, fuzzy/semantic/AI search, telemetry.

Validation performed

Environment: Windows 11 x64, MSVC 14.44 + Windows SDK (local toolchain).

  • pwsh scripts\winterm\verify-version.ps1passed (all assertions,
    including the new 1.3.1 literals and checkpoint-tag list).
  • pwsh scripts\winterm\verify-branding.ps1passed.
  • pwsh scripts\winterm\test.ps1 -Suite Smoke -Configuration Release -Platform x64
    passed (same gate CI quick validation runs; includes static,
    identity, asset, license, shell, workspace, pane, release-workflow, and
    privacy checks).
  • Compiled unit tests: new TAEF tests added to UnitTests_SettingsModel
    (KeyBindingsTests::FindDefaultShortcutsAndUserOverride) and
    UnitTests_Control (ControlCoreTests::TestSearchHighlightsAllMatches,
    TestSearchNavigationAndClear, TestSearchStateIsolatedPerCore). The
    x64 Release builds of both test DLLs are running locally now; te.exe
    results will be posted in this PR's conversation before merge. CI's
    no-label quick validation intentionally does not compile native tests.

New test coverage:

  • Defaults resolve both ctrl+f and ctrl+shift+f to
    ShortcutAction::Find; a user layer can unbind ctrl+f while the alias
    stays bound.
  • A foo/error/bar/error buffer yields 2 matches on the live-typing path
    with both spans (rows 1 and 3) present in SearchResultRows();
    case-insensitive by default; case-sensitive mismatch, no-match needle,
    and empty needle each yield 0 matches and an empty highlight collection.
  • Live typing focuses match 0 without stepping; Enter advances and wraps
    0→1→0; Shift+Enter wraps backward; ClearSearch() empties the spans.
  • Two ControlCore instances keep fully independent search state (the
    split-pane isolation acceptance case at the state level).

Manual smoke checklist (PowerShell) for user validation:

  1. Single pane — run
    1..20 | ForEach-Object { Write-Host "line $_ ERROR test" }, press
    Ctrl+F, type ERROR: the search box opens focused inside the pane,
    every occurrence highlights, Enter/Shift+Enter step forward/backward
    with wrap-around, Esc closes and clears the highlights.
  2. Multiple panes — split two panes with different text. Ctrl+F in
    pane A searches and highlights only pane A; focusing pane B and pressing
    Ctrl+F gives pane B its own independent search box and state.
  3. Scrollback — generate more output than one screen, search a string
    scrolled out of view: it is found and scrolled into view.
  4. Input safety — with search open, type a word, close search, press
    Enter in the shell: the typed query never appears on the command line.
  5. CompatibilityCtrl+Shift+F still opens the same search box.

Checklist

  • The change is focused and does not include unrelated formatting.
  • Tests were added or updated where appropriate.
  • All tests claimed above actually ran and passed.
  • User-facing behavior and limitations are documented in this repository.
  • This source/docs commit updates the root CHANGELOG.md.
  • The Wiki Development-Changes.md ledger has been pushed with the final source SHA, link, summary, and checkpoint/release.
  • Documentation screenshots reuse a suitable existing sanitized winterm-site asset, or no screenshot was added.
  • Version or schema changes include compatibility and migration notes.
  • Package identity, winterm.exe, and Microsoft Terminal coexistence remain isolated.
  • No command text, terminal output, clipboard content, credentials, or private paths are logged.
  • New source and script files contain the appropriate MIT license header.
  • I did not include generated build output, secrets, certificates, or local absolute paths.

Bind Ctrl+F to the Find action so search opens in the active pane,
keeping Ctrl+Shift+F as a compatibility alias. The existing Microsoft
Terminal search pipeline (SearchBoxControl, ControlCore::Search,
TextBuffer::SearchText, renderer highlights) carries the feature
unchanged: live search over the pane's scrollback, all-match
highlighting, Enter/Shift+Enter navigation with wrap-around, Esc
cleanup, and per-control state isolation across split panes.

Add ActionMap defaults coverage for both Find chords and user
unbinding, plus ControlCore tests for all-match spans, case behavior,
no-match and empty-needle cleanup, navigation wrap-around, and
per-core search-state isolation.

Advance the engineering version to 1.3.1 following the v1.2.x
checkpoint convention (stable channel, no prerelease suffix, package
1.3.1.0), add v1.3.1 to the release workflow checkpoint-tag list,
update every pinned version literal in the verification scripts, and
document Ctrl+F in CHANGELOG.md and docs/user/keyboard-shortcuts.md.
The layered ActionMap scenario now mirrors the defaults.json shape (one
action definition plus separate keybinding entries) instead of the
legacy command+keys form, whose in-box entries do not resolve through
GetActionByKeyChord. The all-match span assertion now expects the
exclusive end column reported by TextBuffer::SearchText (5, one past
the last character of "error"), matching the observed TAEF run.
@HelloThisWorld

Copy link
Copy Markdown
Owner Author

Local compiled test results (x64 Release, TAEF):

  • SettingsModel.Unit.Tests.dll /name:*KeyBindingsTests* - 20/20 passed, including the new FindDefaultShortcutsAndUserOverride.
  • Control.Unit.Tests.dll /name:*ControlCoreTests* - 20/20 passed, including the new TestSearchHighlightsAllMatches, TestSearchNavigationAndClear, and TestSearchStateIsolatedPerCore.

Two corrections landed in 9f4a918 after the first local run:

  • the layered-ActionMap scenario now mirrors the modern defaults.json shape (an action definition plus separate keybinding entries); in-box legacy command+keys entries do not resolve through GetActionByKeyChord;
  • the all-match span assertion expects the exclusive end column (5) that TextBuffer::SearchText reports for error occupying columns 0-4.

Full-suite regression runs over both test DLLs are in progress; results will follow in this thread.

The test proved a fragment keys field is ignored by asserting Ctrl+F
resolved to nothing, which relied on that chord being unbound in the
inbox defaults. Ctrl+F is now the default Find binding, so the test
asserts the stronger form of the same contract: the chord still
resolves to the in-box Find action rather than the fragment one.
@HelloThisWorld

Copy link
Copy Markdown
Owner Author

Full-suite regression results (x64 Release, TAEF, local):

  • Control.Unit.Tests.dll (all classes: ControlCore, ControlInteractivity, CommandTimeline) - 77/77 passed.
  • SettingsModel.Unit.Tests.dll (all classes) - first run 243/244: the new ctrl+f default legitimately broke DeserializationTests::FragmentActionNoKeys, which proved "a fragment cannot bind keys" by asserting Ctrl+F resolved to nothing - an assumption that only held while the chord was unbound in the inbox defaults. Commit 525497e updates the test to assert the stronger form of the same contract: Ctrl+F still resolves to the in-box Find action rather than the fragment's. After the fix the suite is 244/244.

Combined with the earlier targeted runs, every compiled suite touching this change now passes locally: KeyBindingsTests 20/20, ControlCoreTests 20/20, full Control DLL 77/77, full SettingsModel DLL 244/244.

@HelloThisWorld
HelloThisWorld merged commit 8015963 into main Aug 11, 2026
7 checks passed
@HelloThisWorld
HelloThisWorld deleted the feature/v1.3.1-pane-search-phase1 branch August 11, 2026 13:48
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.

1 participant