From 0852526f6ae4de49d3be932adb6ddc73c23b6e8d Mon Sep 17 00:00:00 2001 From: HelloThisWorld Date: Tue, 11 Aug 2026 21:32:45 +0800 Subject: [PATCH 1/3] feat: add Pane Search Phase 1 checkpoint (v1.3.1) 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. --- .github/workflows/release.yml | 8 +- CHANGELOG.md | 31 +++- README.ja.md | 6 +- README.md | 6 +- docs/current-progress.md | 67 +++++--- docs/user/keyboard-shortcuts.md | 21 +++ scripts/winterm/package-shell-assets.ps1 | 6 +- scripts/winterm/test-release-workflow.ps1 | 4 +- scripts/winterm/test-visual-progress.ps1 | 44 ++--- scripts/winterm/test.ps1 | 4 +- scripts/winterm/verify-branding.ps1 | 2 +- scripts/winterm/verify-version.ps1 | 24 +-- .../winTerm.Shell/winTerm.Shell.psd1 | 4 +- .../winTerm.Shell/winTerm.Shell.psm1 | 2 +- shell/shared/version.json | 6 +- .../Package-winTerm.appxmanifest | 2 +- .../TerminalSettingsModel/defaults.json | 2 + .../UnitTests_Control/ControlCoreTests.cpp | 158 ++++++++++++++++++ .../KeyBindingsTests.cpp | 35 ++++ .../WindowsTerminal/WindowsTerminal.rc | 8 +- src/cascadia/wt/wt.rc | 8 +- .../winterm-shim/winterm-shim.rc | 8 +- src/winterm/Branding/ReleaseMetadata.h | 4 +- src/winterm/Branding/version.json | 12 +- .../Workspaces/Model/WorkspaceDescriptor.h | 2 +- .../Persistence/WorkspaceSerializer.cpp | 2 +- 26 files changed, 367 insertions(+), 109 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66aeef2f2..3901bf071 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ concurrency: jobs: checkpoint-validation: name: Validate engineering checkpoint - if: ${{ contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]'), github.ref_name) }} + if: ${{ contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]'), github.ref_name) }} runs-on: windows-2022 timeout-minutes: 30 steps: @@ -35,7 +35,7 @@ jobs: validate: name: Validate exact release source - if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]'), github.ref_name) }} + if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]'), github.ref_name) }} runs-on: windows-2022 timeout-minutes: 30 steps: @@ -82,7 +82,7 @@ jobs: test-x64: name: Build and test x64 Release - if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]'), github.ref_name) }} + if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]'), github.ref_name) }} needs: validate runs-on: windows-2022 timeout-minutes: 240 @@ -102,7 +102,7 @@ jobs: release: name: Build, test, attest, and publish EXE and Portable ZIP - if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]'), github.ref_name) }} + if: ${{ !contains(fromJSON('["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]'), github.ref_name) }} needs: - validate - test-x64 diff --git a/CHANGELOG.md b/CHANGELOG.md index aee5c1000..6b1a1a191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,34 @@ # Changelog -## Unreleased +## 1.3.1 - 2026-08-11 -Documentation-only work. No application code, resource, or version file -changed, and no release was produced. +Engineering checkpoint for the Pane Search roadmap, Phase 1: active-pane +search. Like the v1.2.1 through v1.2.4 checkpoints, this version marks merged +development work only. It is not a published release and produces no +downloadable artifacts; GitHub Latest and WinGet keep pointing at the stable +v1.2.0, and v1.3.0-beta3 remains the newest published prerelease. + +### Added + +- Added `Ctrl+F` as the default Find shortcut. It targets the active pane + only: the action resolves the focused pane through the tab's existing pane + model and opens the search box as an overlay inside that pane's terminal + control, so sibling panes, other tabs, and the Command Timeline are never + searched and never show foreign search state. `Ctrl+Shift+F` stays bound as + a compatibility alias, and both chords remain ordinary remappable + keybindings — a profile that needs a literal `^F` for a terminal + application can unbind `ctrl+f` and keep reaching Find through + `Ctrl+Shift+F` or the Command Palette. +- The search experience is carried entirely by the existing Microsoft + Terminal search pipeline — search box control, buffer searcher, and + renderer highlights — now validated for winTerm: typing searches the active + pane's full text buffer including scrollback live on every keystroke, all + matches are highlighted at once, `Enter` and `Shift+Enter` step forward and + backward with wrap-around, an empty query shows no highlights, and `Esc` or + the close button clears the search state and returns focus to the terminal. + Text typed into the search box is never sent to the shell, and a closed + search performs no recurring background work. No second search engine, + index, or buffer copy was introduced. ### Documentation diff --git a/README.ja.md b/README.ja.md index 4fb09cbb6..ea18cf8b0 100644 --- a/README.ja.md +++ b/README.ja.md @@ -38,7 +38,7 @@ Microsoft、Windows、Windows Terminalのロゴも使用していません。 - `winTerm--setup-x64.exe` — 現在のユーザー、または全ユーザーへのインストール用; - `winTerm--portable-x64.zip` — 展開してそのまま実行する用。 -現在のソースバージョンは `1.3.0-beta3` で、 +現在のソースバージョンは `1.3.1` で、 最新の安定版リリースは `1.2.0` です。公開されている資産の一覧とチェックサムの全体は、 [最新の公式リリース](https://github.com/HelloThisWorld/winTerm/releases/latest) を参照してください。 @@ -111,8 +111,8 @@ PowerShell 7と、[ビルド手順(英語)](docs/build.md)に記載された .\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -IncludeTests .\scripts\winterm\test.ps1 -Suite Relevant -Configuration Release -Platform x64 .\scripts\winterm\build-unpackaged.ps1 -Configuration Release -Platform x64 -.\scripts\winterm\build-installer.ps1 -Version 1.3.0-beta3 -Platform x64 -.\scripts\winterm\build-portable.ps1 -Version 1.3.0-beta3 -Platform x64 +.\scripts\winterm\build-installer.ps1 -Version 1.3.1 -Platform x64 +.\scripts\winterm\build-portable.ps1 -Version 1.3.1 -Platform x64 ``` アンパッケージ形式の生成処理では、統合されたリソースインデックスを作るための diff --git a/README.md b/README.md index 2d52cc784..6164318b2 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ application downloads are: - `winTerm--setup-x64.exe` — current-user or all-users installation; - `winTerm--portable-x64.zip` — extract and run without installation. -The current source version is `1.3.0-beta3`; +The current source version is `1.3.1`; the latest stable release is `1.2.0`. See the [latest official Release](https://github.com/HelloThisWorld/winTerm/releases/latest) for the complete published asset list and checksums. @@ -106,8 +106,8 @@ Use PowerShell 7 and the Microsoft Terminal toolchain described in .\scripts\winterm\build.ps1 -Configuration Release -Platform x64 -IncludeTests .\scripts\winterm\test.ps1 -Suite Relevant -Configuration Release -Platform x64 .\scripts\winterm\build-unpackaged.ps1 -Configuration Release -Platform x64 -.\scripts\winterm\build-installer.ps1 -Version 1.3.0-beta3 -Platform x64 -.\scripts\winterm\build-portable.ps1 -Version 1.3.0-beta3 -Platform x64 +.\scripts\winterm\build-installer.ps1 -Version 1.3.1 -Platform x64 +.\scripts\winterm\build-portable.ps1 -Version 1.3.1 -Platform x64 ``` The unpackaged generator uses an unsigned MSIX only as an upstream build diff --git a/docs/current-progress.md b/docs/current-progress.md index 9448828f9..b68f1ba9c 100644 --- a/docs/current-progress.md +++ b/docs/current-progress.md @@ -1,33 +1,47 @@ # Current development progress -Last updated: 2026-08-05 +Last updated: 2026-08-11 ## Repository state -- Branch: `release/v1.3.0-beta3` -- Base: `fix/visual-progress-shell-fallback-one-shot` at `baf6f8a87` - (one-shot launch fallback fix, pull request #41), based on `main` at - `39193206c` (1.3.0-beta2 release metadata, pull request #40) +- Branch: `feature/v1.3.1-pane-search-phase1`, based on `main` at + `4753fba68` (Japanese README, pull request #43) - Microsoft Terminal upstream revision: `1cea42d433253d95c4487a3037db48197b5e72f4` -- Application version: `1.3.0-beta3` -- Package/file version: `1.3.0.6` -- PowerShell module version: `1.3.0` with prerelease suffix `beta3` -- Release channel: `beta` -- Release tag: `v1.3.0-beta3` +- Application version: `1.3.1` +- Package/file version: `1.3.1.0` +- PowerShell module version: `1.3.1` with no prerelease suffix +- Release channel: `stable` (engineering checkpoint; nothing is published) +- Checkpoint tag: `v1.3.1`, listed with the v1.2.x checkpoint tags in the + release workflow so a pushed checkpoint tag runs quick validation only and + can never produce release artifacts - Current public Latest: `v1.2.0`, the stable Visual Progress release +- Newest published prerelease: `v1.3.0-beta3` on the beta channel - Supported target: Windows 11 x64 -`v1.3.0-beta3` is the third beta of the Command Timeline release. It fixes -the beta2 field report that any long-running command — k9s, vim, top, -FastAPI/uvicorn, Spring Boot, Node dev servers, `tail -f`, -`kubectl port-forward` — kept the Visual Progress rainbow animation looping -for its whole lifetime. The OSC 133 Shell Integration fallback is now a -bounded one-shot launch indication scoped by a shell command generation. The -release workflow marks any non-stable channel with `--prerelease` and -`--latest=false`, so `/releases/latest` keeps resolving to v1.2.0. Like the -earlier betas, it is listed on the winTerm website next to the stable v1.2.0 -download and is skipped by the WinGet workflow. +`v1.3.1` is an engineering checkpoint, the first of the Pane Search roadmap +toward v1.4. Like the v1.2.1 through v1.2.4 Command Timeline checkpoints, it +is a source/development version only: no GitHub Release, website slot, or +WinGet update is produced, and `/releases/latest` keeps resolving to v1.2.0. + +## Pane Search status (v1.4 roadmap) + +Phase 1 — active-pane search — is complete in this checkpoint. `Ctrl+F` (and +the retained `Ctrl+Shift+F` alias) opens the existing Microsoft Terminal +search box inside the focused pane only. The implementation reuses the +mature upstream pipeline end to end — `SearchBoxControl`, +`ControlCore::Search`, `Search`/`TextBuffer::SearchText`, and renderer +search highlights — with no second search engine, no index, and no buffer +duplication. Live search on every keystroke highlights all matches across +the pane's scrollback, Enter/Shift+Enter navigate with wrap-around, Esc +clears the state, search-box input never reaches the shell, and each +terminal control keeps its own search state so split panes stay isolated. + +Deferred to later phases: the winTerm-specific search UI redesign and +scrollbar overview markers (Phase 2), and performance work such as +debouncing or large-scrollback optimization (Phase 3). Final integration is +planned as `1.4.0-alpha`, with promotion to beta only after manual user +validation. ## Command Timeline status @@ -142,11 +156,14 @@ through pull request #32: ## Next steps -1. Update the winTerm website: dual stable/beta download columns, refreshed - sanitized screenshots, the Tools navigation dropdown, and the logo link. -2. Collect beta feedback. -3. Promote to a stable `v1.3.0` only after beta testing, which is the point at - which Latest, WinGet, and the website stable slot move. +1. Pane Search Phase 2 (`1.3.2`): winTerm-specific search UI and scrollbar + overview markers. +2. Pane Search Phase 3 (`1.3.3`): performance investigation and hardening. +3. Final integration checkpoint `1.4.0-alpha`, then manual user validation + before any beta promotion. +4. Collect Command Timeline beta feedback; promote a stable `v1.3.0` only + after beta testing, which is the point at which Latest, WinGet, and the + website stable slot move. ## Validation state diff --git a/docs/user/keyboard-shortcuts.md b/docs/user/keyboard-shortcuts.md index 18f3259bc..88cdc9d60 100644 --- a/docs/user/keyboard-shortcuts.md +++ b/docs/user/keyboard-shortcuts.md @@ -25,6 +25,27 @@ while it owns pointer capture. Pane movement commands are not available in winTerm 1.1. A legacy custom `movePane` action can still be parsed safely but is disabled. +## Search + +| Shortcut | Behavior | +| --- | --- | +| `Ctrl+F` | Open search in the focused pane | +| `Ctrl+Shift+F` | Open search in the focused pane (compatibility alias) | + +Search always targets the active pane. The search box opens as an overlay +inside that pane without resizing terminal content, and the input field is +focused immediately. Typing searches the pane's buffer and scrollback as you +type and highlights every match at once. `Enter` moves to the next match and +`Shift+Enter` to the previous one, wrapping around at either end. `Esc` or +the close button dismisses the search box, clears the highlights, and +returns focus to the terminal. Text typed into the search box never reaches +the shell. + +Both chords are ordinary configurable keybindings. A terminal application +that needs a literal `Ctrl+F` keystroke can reclaim it by unbinding the +default (`{ "command": "unbound", "keys": "ctrl+f" }` in settings); Find +stays reachable through `Ctrl+Shift+F` or the Command Palette. + ## Command Timeline | Shortcut | Behavior | diff --git a/scripts/winterm/package-shell-assets.ps1 b/scripts/winterm/package-shell-assets.ps1 index 87025cd16..86e527a99 100644 --- a/scripts/winterm/package-shell-assets.ps1 +++ b/scripts/winterm/package-shell-assets.ps1 @@ -32,9 +32,9 @@ foreach ($relativePath in $sourceAssets) } $version = Get-Content -LiteralPath (Join-Path $repositoryRoot 'shell\shared\version.json') -Raw | ConvertFrom-Json -if ($version.moduleVersion -ne '1.3.0' -or - $version.modulePrerelease -ne 'beta3' -or - $version.applicationVersion -ne '1.3.0-beta3' -or +if ($version.moduleVersion -ne '1.3.1' -or + $version.modulePrerelease -ne '' -or + $version.applicationVersion -ne '1.3.1' -or $version.protocolVersion -ne 1) { throw 'The winTerm Shell asset version metadata is invalid.' diff --git a/scripts/winterm/test-release-workflow.ps1 b/scripts/winterm/test-release-workflow.ps1 index e122810a5..951d59a05 100644 --- a/scripts/winterm/test-release-workflow.ps1 +++ b/scripts/winterm/test-release-workflow.ps1 @@ -23,7 +23,7 @@ try "- 'v*'", 'checkpoint-validation:', 'Validate engineering checkpoint', - '["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]', + '["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]', 'verify-version.ps1 -RequireTag', 'test.ps1 -Suite Smoke', 'contents: write', @@ -63,7 +63,7 @@ try } } - $checkpointGuard = 'contains(fromJSON(''["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]''), github.ref_name)' + $checkpointGuard = 'contains(fromJSON(''["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]''), github.ref_name)' if ([regex]::Matches($workflow, [regex]::Escape($checkpointGuard)).Count -ne 4) { throw 'Release workflow must guard the checkpoint validation and all three full release jobs.' diff --git a/scripts/winterm/test-visual-progress.ps1 b/scripts/winterm/test-visual-progress.ps1 index 890af4b60..a2cbdbac8 100644 --- a/scripts/winterm/test-visual-progress.ps1 +++ b/scripts/winterm/test-visual-progress.ps1 @@ -1431,12 +1431,12 @@ try $version = $source.VersionMetadata | ConvertFrom-Json $expectedVersionValues = [ordered]@{ - applicationVersion = '1.3.0-beta3' - packageVersion = '1.3.0.6' - moduleVersion = '1.3.0' - modulePrerelease = 'beta3' - channel = 'beta' - tag = 'v1.3.0-beta3' + applicationVersion = '1.3.1' + packageVersion = '1.3.1.0' + moduleVersion = '1.3.1' + modulePrerelease = '' + channel = 'stable' + tag = 'v1.3.1' workspaceSchemaVersion = 2 dockingModelVersion = 1 shellProtocolVersion = 1 @@ -1451,39 +1451,39 @@ try } } $shellVersion = $source.ShellVersion | ConvertFrom-Json - if ($shellVersion.applicationVersion -ne '1.3.0-beta3' -or $shellVersion.moduleVersion -ne '1.3.0' -or $shellVersion.protocolVersion -ne 1) + if ($shellVersion.applicationVersion -ne '1.3.1' -or $shellVersion.moduleVersion -ne '1.3.1' -or $shellVersion.protocolVersion -ne 1) { - throw 'Shell version metadata does not match winTerm release 1.3.0-beta3 with protocol version 1.' + throw 'Shell version metadata does not match winTerm version 1.3.1 with protocol version 1.' } foreach ($surface in @( - @{ Content = $source.ReleaseMetadata; Value = 'ApplicationVersion{ L"1.3.0-beta3" }'; Description = 'About release metadata' }, - @{ Content = $source.PackageManifest; Value = 'Version="1.3.0.6"'; Description = 'MSIX package manifest' }, - @{ Content = $source.HostResource; Value = 'FILEVERSION 1,3,0,6'; Description = 'Terminal host file version' }, - @{ Content = $source.HostResource; Value = '"ProductVersion", "1.3.0-beta3\0"'; Description = 'Terminal host display version' }, - @{ Content = $source.ShimResource; Value = 'FILEVERSION 1,3,0,6'; Description = 'Shim file version' }, - @{ Content = $source.ShimResource; Value = '"ProductVersion", "1.3.0-beta3\0"'; Description = 'Shim display version' }, + @{ Content = $source.ReleaseMetadata; Value = 'ApplicationVersion{ L"1.3.1" }'; Description = 'About release metadata' }, + @{ Content = $source.PackageManifest; Value = 'Version="1.3.1.0"'; Description = 'MSIX package manifest' }, + @{ Content = $source.HostResource; Value = 'FILEVERSION 1,3,1,0'; Description = 'Terminal host file version' }, + @{ Content = $source.HostResource; Value = '"ProductVersion", "1.3.1\0"'; Description = 'Terminal host display version' }, + @{ Content = $source.ShimResource; Value = 'FILEVERSION 1,3,1,0'; Description = 'Shim file version' }, + @{ Content = $source.ShimResource; Value = '"ProductVersion", "1.3.1\0"'; Description = 'Shim display version' }, @{ Content = $source.CustomProps; Value = '1'; Description = 'Executable major version' }, @{ Content = $source.CustomProps; Value = '3'; Description = 'Executable minor version' }, - @{ Content = $source.ShellModuleManifest; Value = "ModuleVersion = '1.3.0'"; Description = 'PowerShell module manifest' }, - @{ Content = $source.ShellModule; Value = "`$script:WinTermModuleVersion = '1.3.0'"; Description = 'PowerShell module runtime' }, + @{ Content = $source.ShellModuleManifest; Value = "ModuleVersion = '1.3.1'"; Description = 'PowerShell module manifest' }, + @{ Content = $source.ShellModule; Value = "`$script:WinTermModuleVersion = '1.3.1'"; Description = 'PowerShell module runtime' }, @{ Content = $source.PackageShellAssets; Value = "'shell\shared\version.json'"; Description = 'Canonical shell version metadata packaging' }, - @{ Content = $source.WorkspaceSerializer; Value = '"1.3.0-beta3"'; Description = 'Workspace application-version fallback' } + @{ Content = $source.WorkspaceSerializer; Value = '"1.3.1"'; Description = 'Workspace application-version fallback' } )) { Assert-Contains $surface.Content $surface.Value $surface.Description } foreach ($required in @( - "applicationVersion -eq '1.3.0-beta3'", - "packageVersion -eq '1.3.0.6'", - "moduleVersion -eq '1.3.0'", - "tag -eq 'v1.3.0-beta3'", + "applicationVersion -eq '1.3.1'", + "packageVersion -eq '1.3.1.0'", + "moduleVersion -eq '1.3.1'", + "tag -eq 'v1.3.1'", "Workspace Schema version remains 2", "Docking Model version remains 1", "Shell Protocol version remains 1", "Theme Schema remains at version 1" )) { - Assert-Contains $source.VerifyVersion $required 'Authoritative v1.3.0-beta3 version validation surface' + Assert-Contains $source.VerifyVersion $required 'Authoritative v1.3.1 version validation surface' } $testBinary = Join-Path $root "bin\$Platform\$Configuration\UnitTests_SettingsModel\SettingsModel.Unit.Tests.dll" diff --git a/scripts/winterm/test.ps1 b/scripts/winterm/test.ps1 index edf9d94a4..d431df885 100644 --- a/scripts/winterm/test.ps1 +++ b/scripts/winterm/test.ps1 @@ -292,8 +292,8 @@ function Test-ShellExperienceFoundations } $manifest = Import-PowerShellDataFile -LiteralPath $moduleManifest - if ($manifest.ModuleVersion -ne '1.3.0' -or - $manifest.PrivateData.PSData.Prerelease -ne 'beta3' -or + if ($manifest.ModuleVersion -ne '1.3.1' -or + $manifest.PrivateData.PSData.Prerelease -ne '' -or $manifest.PowerShellVersion -ne '5.1') { throw 'The winTerm PowerShell module manifest does not declare the supported version boundary.' diff --git a/scripts/winterm/verify-branding.ps1 b/scripts/winterm/verify-branding.ps1 index 46aaa77da..898c53164 100644 --- a/scripts/winterm/verify-branding.ps1 +++ b/scripts/winterm/verify-branding.ps1 @@ -126,7 +126,7 @@ function Test-Manifest Test-Requirement -Condition ($null -ne $identity -and $identity.Name -eq 'HelloThisWorld.winTerm') -Message "$Path uses package identity HelloThisWorld.winTerm" Test-Requirement -Condition ($null -ne $identity -and $identity.Name -notmatch '^Microsoft\.') -Message "$Path does not use a Microsoft package name" Test-Requirement -Condition ($null -ne $identity -and $identity.Publisher -ceq $ExpectedPublisher) -Message "$Path uses the expected non-Microsoft publisher" - Test-Requirement -Condition ($null -ne $identity -and $identity.Version -eq '1.3.0.6') -Message "$Path uses package version 1.3.0.6" + Test-Requirement -Condition ($null -ne $identity -and $identity.Version -eq '1.3.1.0') -Message "$Path uses package version 1.3.1.0" Test-Requirement -Condition ($null -ne $properties -and $properties.DisplayName -eq 'winTerm') -Message "$Path package display name is winTerm" Test-Requirement -Condition ($null -ne $application -and $application.Id -eq 'winTerm') -Message "$Path application ID is winTerm" Test-Requirement -Condition ($null -ne $visualElements -and $visualElements.DisplayName -eq 'winTerm') -Message "$Path application display name is winTerm" diff --git a/scripts/winterm/verify-version.ps1 b/scripts/winterm/verify-version.ps1 index d11c624ab..17d2c5d28 100644 --- a/scripts/winterm/verify-version.ps1 +++ b/scripts/winterm/verify-version.ps1 @@ -49,10 +49,10 @@ try $versionPath = Join-Path $repositoryRoot 'src\winterm\Branding\version.json' $version = Get-Content -LiteralPath $versionPath -Raw | ConvertFrom-Json - Assert-Condition ($version.applicationVersion -eq '1.3.0-beta3') 'Application version is 1.3.0-beta3' - Assert-Condition ($version.packageVersion -eq '1.3.0.6') 'Package version is 1.3.0.6' - Assert-Condition ($version.moduleVersion -eq '1.3.0') 'PowerShell module version is 1.3.0' - Assert-Condition ($version.modulePrerelease -eq 'beta3') 'PowerShell module prerelease suffix is beta3' + Assert-Condition ($version.applicationVersion -eq '1.3.1') 'Application version is 1.3.1' + Assert-Condition ($version.packageVersion -eq '1.3.1.0') 'Package version is 1.3.1.0' + Assert-Condition ($version.moduleVersion -eq '1.3.1') 'PowerShell module version is 1.3.1' + Assert-Condition ($version.modulePrerelease -eq '') 'PowerShell module has no prerelease suffix' # The release workflow treats any channel other than 'stable' as a # prerelease: it marks the GitHub Release --prerelease and --latest=false. @@ -66,7 +66,7 @@ try Assert-Condition ($version.packageVersion -match '^\d+\.\d+\.\d+\.\d+$') 'Package version stays a four-part numeric version' Assert-Condition ($version.moduleVersion -match '^\d+\.\d+\.\d+$') 'PowerShell module version stays numeric' - Assert-Condition ($version.tag -eq 'v1.3.0-beta3') 'Release tag is v1.3.0-beta3' + Assert-Condition ($version.tag -eq 'v1.3.1') 'Engineering checkpoint tag is v1.3.1' Assert-Condition ($version.workspaceSchemaVersion -eq 2) 'Workspace Schema version remains 2' Assert-Condition ($version.dockingModelVersion -eq 1) 'Docking Model version remains 1' Assert-Condition ($version.shellProtocolVersion -eq 1) 'Shell Protocol version remains 1' @@ -90,7 +90,7 @@ try $moduleManifest = Import-PowerShellDataFile -LiteralPath (Join-Path $repositoryRoot 'shell\powershell\winTerm.Shell\winTerm.Shell.psd1') Assert-Condition ($moduleManifest.ModuleVersion.ToString() -eq $version.moduleVersion) 'PowerShell manifest version matches release metadata' Assert-Condition ($moduleManifest.PrivateData.PSData.Prerelease -eq $version.modulePrerelease) 'PowerShell manifest prerelease matches release metadata' - Assert-Condition ((Get-Text 'shell\powershell\winTerm.Shell\winTerm.Shell.psm1').Contains("`$script:WinTermModuleVersion = '1.3.0'")) 'PowerShell module runtime version matches release metadata' + Assert-Condition ((Get-Text 'shell\powershell\winTerm.Shell\winTerm.Shell.psm1').Contains("`$script:WinTermModuleVersion = '1.3.1'")) 'PowerShell module runtime version matches release metadata' $shellVersion = Get-Text 'shell\shared\version.json' | ConvertFrom-Json Assert-Condition ($shellVersion.applicationVersion -eq $version.applicationVersion) 'Shell asset application version matches release metadata' @@ -98,8 +98,8 @@ try Assert-Condition ($shellVersion.protocolVersion -eq $version.shellProtocolVersion) 'Shell asset protocol version matches release metadata' $releaseHeader = Get-Text 'src\winterm\Branding\ReleaseMetadata.h' - Assert-Condition ($releaseHeader.Contains('ApplicationVersion{ L"1.3.0-beta3" }')) 'About metadata application version is 1.3.0-beta3' - Assert-Condition ($releaseHeader.Contains('ReleaseChannel{ L"Beta" }')) 'About metadata channel is Beta' + Assert-Condition ($releaseHeader.Contains('ApplicationVersion{ L"1.3.1" }')) 'About metadata application version is 1.3.1' + Assert-Condition ($releaseHeader.Contains('ReleaseChannel{ L"Stable" }')) 'About metadata channel is Stable' Assert-Condition ($releaseHeader.Contains($version.microsoftTerminalUpstreamRevision)) 'About metadata contains the Microsoft Terminal upstream revision' Assert-Condition ($releaseHeader.Contains('WorkspaceSchemaVersion{ 2 }')) 'About metadata contains Workspace Schema version 2' Assert-Condition ($releaseHeader.Contains('DockingModelVersion{ 1 }')) 'About metadata contains Docking Model version 1' @@ -129,14 +129,14 @@ try Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('WorkspaceSchemaVersion{ 2 }')) 'Workspace model remains at Schema version 2' Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('DockingModelVersion{ 1 }')) 'Workspace model remains at Docking version 1' - Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('applicationVersion{ "1.3.0-beta3" }')) 'Workspace model application-version fallback is 1.3.0-beta3' + Assert-Condition ((Get-Text 'src\winterm\Workspaces\Model\WorkspaceDescriptor.h').Contains('applicationVersion{ "1.3.1" }')) 'Workspace model application-version fallback is 1.3.1' Assert-Condition ((Get-Text 'src\winterm\Shell\Protocol\ShellIntegrationProtocol.h').Contains('ShellProtocolVersion{ 1 }')) 'Shell protocol remains at version 1' Assert-Condition ((Get-Text 'src\winterm\Appearance\Themes\ThemeDescriptor.h').Contains('CurrentThemeSchemaVersion{ 1 }')) 'Theme Schema remains at version 1' - Assert-Condition ((Get-Text 'src\winterm\Workspaces\Persistence\WorkspaceSerializer.cpp').Contains('"1.3.0-beta3"')) 'Workspace serializer application-version fallback is 1.3.0-beta3' + Assert-Condition ((Get-Text 'src\winterm\Workspaces\Persistence\WorkspaceSerializer.cpp').Contains('"1.3.1"')) 'Workspace serializer application-version fallback is 1.3.1' $releaseWorkflow = Get-Text '.github\workflows\release.yml' Assert-Condition ($releaseWorkflow.Contains("- 'v*'")) 'Release workflow accepts version tags through a generic guarded trigger' - Assert-Condition ($releaseWorkflow.Contains('["v1.2.1","v1.2.2","v1.2.3","v1.2.4"]')) 'Release workflow identifies engineering checkpoint tags' + Assert-Condition ($releaseWorkflow.Contains('["v1.2.1","v1.2.2","v1.2.3","v1.2.4","v1.3.1"]')) 'Release workflow identifies engineering checkpoint tags' Assert-Condition ($releaseWorkflow.Contains('checkpoint-validation:')) 'Release workflow retains quick validation for checkpoint tags' Assert-Condition ($releaseWorkflow.Contains("`$expectedTag = `"v`$(`$metadata.applicationVersion)`"")) 'Release workflow derives the expected tag from version.json' Assert-Condition ($releaseWorkflow.Contains("`$metadata.tag -cne `$expectedTag")) 'Release workflow rejects a version metadata tag mismatch' @@ -187,7 +187,7 @@ try if ($RequireTag) { $tag = (& git describe --tags --exact-match 2>$null).Trim() - Assert-Condition ($LASTEXITCODE -eq 0 -and $tag -eq $version.tag) 'Checked-out commit is exactly tagged v1.3.0-beta3' + Assert-Condition ($LASTEXITCODE -eq 0 -and $tag -eq $version.tag) 'Checked-out commit is exactly tagged v1.3.1' } Write-Host 'winTerm version consistency verification passed.' -ForegroundColor Green diff --git a/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 b/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 index 5428bd543..707764b87 100644 --- a/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 +++ b/shell/powershell/winTerm.Shell/winTerm.Shell.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'winTerm.Shell.psm1' - ModuleVersion = '1.3.0' + ModuleVersion = '1.3.1' GUID = 'f65cd8f4-5d25-4a2a-a0d4-58df1ab3dc5a' Author = 'winTerm contributors' CompanyName = 'winTerm' @@ -12,7 +12,7 @@ AliasesToExport = @() PrivateData = @{ PSData = @{ - Prerelease = 'beta3' + Prerelease = '' Tags = @('winTerm', 'terminal', 'shell-integration') ProjectUri = 'https://github.com/HelloThisWorld/winTerm' LicenseUri = 'https://github.com/HelloThisWorld/winTerm/blob/main/LICENSE' diff --git a/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 b/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 index 3bce5fffa..c6bc71434 100644 --- a/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 +++ b/shell/powershell/winTerm.Shell/winTerm.Shell.psm1 @@ -3,7 +3,7 @@ Set-StrictMode -Version Latest -$script:WinTermModuleVersion = '1.3.0' +$script:WinTermModuleVersion = '1.3.1' $script:WinTermProtocolVersion = 1 $script:WinTermIntegrationEnabled = $false $script:WinTermPromptWrapped = $false diff --git a/shell/shared/version.json b/shell/shared/version.json index fe072a0ba..beff553c0 100644 --- a/shell/shared/version.json +++ b/shell/shared/version.json @@ -1,6 +1,6 @@ { - "applicationVersion": "1.3.0-beta3", - "moduleVersion": "1.3.0", - "modulePrerelease": "beta3", + "applicationVersion": "1.3.1", + "moduleVersion": "1.3.1", + "modulePrerelease": "", "protocolVersion": 1 } diff --git a/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest b/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest index 35c602499..c2972cd7a 100644 --- a/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-winTerm.appxmanifest @@ -18,7 +18,7 @@ + Version="1.3.1.0" /> winTerm diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index fc62f990c..f3f7958e6 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -1031,6 +1031,8 @@ { "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, { "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, { "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, + // Find is Ctrl+F; Ctrl+Shift+F is kept as a compatibility alias. + { "keys": "ctrl+f", "id": "Terminal.FindText" }, { "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, { "keys": "ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, { "keys": "win+sc(41)", "id": "Terminal.QuakeMode" }, diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 5b242b823..9d38050c5 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -37,6 +37,10 @@ namespace ControlUnitTests TEST_METHOD(TestClearScreen); TEST_METHOD(TestClearAll); TEST_METHOD(TestReadEntireBuffer); + + TEST_METHOD(TestSearchHighlightsAllMatches); + TEST_METHOD(TestSearchNavigationAndClear); + TEST_METHOD(TestSearchStateIsolatedPerCore); TEST_METHOD(TestSelectCommandSimple); TEST_METHOD(TestSelectOutputSimple); @@ -367,6 +371,160 @@ namespace ControlUnitTests Log::Comment(L"Check the buffer contents"); VERIFY_ARE_EQUAL(L"This is some text\r\nwith varying amounts\r\nof whitespace\r\n", core->ReadEntireBuffer()); + } + + void ControlCoreTests::TestSearchHighlightsAllMatches() + { + auto [settings, conn] = _createSettingsAndConnection(); + auto core = createCore(*settings, *conn); + VERIFY_IS_NOT_NULL(core); + _standardInit(core); + + const auto search = [&core](const winrt::hstring& text, const bool caseSensitive = false) { + return core->Search(Control::SearchRequest{ + .Text = text, + .GoForward = true, + .CaseSensitive = caseSensitive, + .RegularExpression = false, + .ExecuteSearch = false, + .ScrollIntoView = false, + .ScrollOffset = 0, + }); + }; + + Log::Comment(L"Print a buffer with two matches"); + conn->WriteInput(winrt_wstring_to_array_view(L"foo\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"error\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"bar\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"error\r\n")); + + Log::Comment(L"A reset-only search, the live-typing path, collects every match"); + auto results = search(L"error"); + VERIFY_ARE_EQUAL(2, results.TotalMatches); + VERIFY_ARE_EQUAL(0, results.CurrentMatch); + VERIFY_IS_FALSE(results.SearchRegexInvalid); + + Log::Comment(L"Every match span reaches the highlight state, not only the current one"); + const auto& rows = core->SearchResultRows(); + VERIFY_ARE_EQUAL(2u, rows.size()); + VERIFY_ARE_EQUAL(0, rows[0].start.x); + VERIFY_ARE_EQUAL(1, rows[0].start.y); + VERIFY_ARE_EQUAL(4, rows[0].end.x); + VERIFY_ARE_EQUAL(1, rows[0].end.y); + VERIFY_ARE_EQUAL(0, rows[1].start.x); + VERIFY_ARE_EQUAL(3, rows[1].start.y); + + Log::Comment(L"The default search is case-insensitive"); + results = search(L"ERROR"); + VERIFY_ARE_EQUAL(2, results.TotalMatches); + + Log::Comment(L"A case-sensitive search of the same needle matches nothing"); + results = search(L"ERROR", true); + VERIFY_ARE_EQUAL(0, results.TotalMatches); + VERIFY_IS_TRUE(core->SearchResultRows().empty()); + + Log::Comment(L"A needle with no matches leaves no highlight state behind"); + results = search(L"does-not-exist"); + VERIFY_ARE_EQUAL(0, results.TotalMatches); + VERIFY_IS_TRUE(core->SearchResultRows().empty()); + + Log::Comment(L"An empty needle produces no matches and no highlights"); + results = search(L""); + VERIFY_ARE_EQUAL(0, results.TotalMatches); + VERIFY_IS_TRUE(core->SearchResultRows().empty()); + } + + void ControlCoreTests::TestSearchNavigationAndClear() + { + auto [settings, conn] = _createSettingsAndConnection(); + auto core = createCore(*settings, *conn); + VERIFY_IS_NOT_NULL(core); + _standardInit(core); + + const auto search = [&core](const winrt::hstring& text, const bool goForward, const bool executeSearch) { + return core->Search(Control::SearchRequest{ + .Text = text, + .GoForward = goForward, + .CaseSensitive = false, + .RegularExpression = false, + .ExecuteSearch = executeSearch, + .ScrollIntoView = false, + .ScrollOffset = 0, + }); + }; + + Log::Comment(L"Print a buffer with two matches"); + conn->WriteInput(winrt_wstring_to_array_view(L"foo\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"error\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"bar\r\n")); + conn->WriteInput(winrt_wstring_to_array_view(L"error\r\n")); + + Log::Comment(L"Live typing focuses the first match without stepping"); + auto results = search(L"error", true, false); + VERIFY_ARE_EQUAL(2, results.TotalMatches); + VERIFY_ARE_EQUAL(0, results.CurrentMatch); + + Log::Comment(L"Enter moves to the next match"); + results = search(L"error", true, true); + VERIFY_ARE_EQUAL(1, results.CurrentMatch); + + Log::Comment(L"Enter on the last match wraps around to the first"); + results = search(L"error", true, true); + VERIFY_ARE_EQUAL(0, results.CurrentMatch); + + Log::Comment(L"Shift+Enter moves backward and wraps to the last match"); + results = search(L"error", false, true); + VERIFY_ARE_EQUAL(1, results.CurrentMatch); + + Log::Comment(L"Closing the search clears every highlight span"); + core->ClearSearch(); + VERIFY_IS_TRUE(core->SearchResultRows().empty()); + } + + void ControlCoreTests::TestSearchStateIsolatedPerCore() + { + auto [settingsA, connA] = _createSettingsAndConnection(); + auto coreA = createCore(*settingsA, *connA); + VERIFY_IS_NOT_NULL(coreA); + _standardInit(coreA); + + auto [settingsB, connB] = _createSettingsAndConnection(); + auto coreB = createCore(*settingsB, *connB); + VERIFY_IS_NOT_NULL(coreB); + _standardInit(coreB); + + const auto search = [](auto& core, const winrt::hstring& text) { + return core->Search(Control::SearchRequest{ + .Text = text, + .GoForward = true, + .CaseSensitive = false, + .RegularExpression = false, + .ExecuteSearch = false, + .ScrollIntoView = false, + .ScrollOffset = 0, + }); + }; + + Log::Comment(L"Two panes hold different content"); + connA->WriteInput(winrt_wstring_to_array_view(L"ERROR A1\r\nERROR A2\r\n")); + connB->WriteInput(winrt_wstring_to_array_view(L"ERROR B1\r\n")); + + Log::Comment(L"Searching the active pane never creates state in the sibling"); + const auto resultsB = search(coreB, L"ERROR"); + VERIFY_ARE_EQUAL(1, resultsB.TotalMatches); + VERIFY_ARE_EQUAL(1u, coreB->SearchResultRows().size()); + VERIFY_IS_TRUE(coreA->SearchResultRows().empty()); + + Log::Comment(L"Each core keeps its own independent result set"); + const auto resultsA = search(coreA, L"ERROR"); + VERIFY_ARE_EQUAL(2, resultsA.TotalMatches); + VERIFY_ARE_EQUAL(2u, coreA->SearchResultRows().size()); + VERIFY_ARE_EQUAL(1u, coreB->SearchResultRows().size()); + + Log::Comment(L"Clearing one core's search leaves the sibling untouched"); + coreB->ClearSearch(); + VERIFY_IS_TRUE(coreB->SearchResultRows().empty()); + VERIFY_ARE_EQUAL(2u, coreA->SearchResultRows().size()); } static void _writePrompt(const winrt::com_ptr& conn, const std::wstring_view& path) diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index fb8873303..a0886c830 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -42,6 +42,7 @@ namespace SettingsModelUnitTests TEST_METHOD(KeybindingsWithoutVkey); TEST_METHOD(ControlCIsReservedForTerminalInput); TEST_METHOD(CommandTimelineDefaultShortcutsAndUserOverride); + TEST_METHOD(FindDefaultShortcutsAndUserOverride); }; void KeyBindingsTests::KeyChords() @@ -185,6 +186,40 @@ namespace SettingsModelUnitTests static_cast(overridden.ActionAndArgs().Action())); } + void KeyBindingsTests::FindDefaultShortcutsAndUserOverride() + { + const auto settings = CascadiaSettings::LoadDefaults(); + const auto actionMap = settings.ActionMap(); + const auto verifyAction = [&](const KeyChord& chord, const ShortcutAction expected) { + const auto command = actionMap.GetActionByKeyChord(chord); + VERIFY_IS_NOT_NULL(command); + VERIFY_ARE_EQUAL(static_cast(expected), static_cast(command.ActionAndArgs().Action())); + }; + + // Ctrl+F is winTerm's primary Find chord; Ctrl+Shift+F stays bound as + // the upstream-compatible alias. Both resolve to the same action. + verifyAction(KeyChord{ true, false, false, false, static_cast('F'), 0 }, ShortcutAction::Find); + verifyAction(KeyChord{ true, false, true, false, static_cast('F'), 0 }, ShortcutAction::Find); + + // A user layer can reclaim raw Ctrl+F for a terminal application by + // unbinding it, and the alias keeps Find reachable. + auto layered = winrt::make_self(); + layered->LayerJson(VerifyParseSucceeded(R"([ + { "command": "find", "keys": "ctrl+f" }, + { "command": "find", "keys": "ctrl+shift+f" } + ])"), + OriginTag::InBox); + layered->LayerJson(VerifyParseSucceeded(R"([ + { "command": "unbound", "keys": "ctrl+f" } + ])"), + OriginTag::User); + VERIFY_IS_NULL(layered->GetActionByKeyChord(KeyChord{ true, false, false, false, static_cast('F'), 0 })); + const auto aliasCommand = layered->GetActionByKeyChord(KeyChord{ true, false, true, false, static_cast('F'), 0 }); + VERIFY_IS_NOT_NULL(aliasCommand); + VERIFY_ARE_EQUAL(static_cast(ShortcutAction::Find), + static_cast(aliasCommand.ActionAndArgs().Action())); + } + void KeyBindingsTests::LayerKeybindings() { const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.rc b/src/cascadia/WindowsTerminal/WindowsTerminal.rc index fd81c8855..11d038538 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.rc +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.rc @@ -83,8 +83,8 @@ IDI_APPICON_HC_WHITE ICON "..\\..\\..\\res\\terminal\\imag #if defined(WT_BRANDING_WINTERM) 1 VERSIONINFO - FILEVERSION 1,3,0,6 - PRODUCTVERSION 1,3,0,6 + FILEVERSION 1,3,1,0 + PRODUCTVERSION 1,3,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -101,12 +101,12 @@ BEGIN BEGIN VALUE "CompanyName", "helloThisWorld\0" VALUE "FileDescription", "winTerm Terminal Host\0" - VALUE "FileVersion", "1.3.0.6\0" + VALUE "FileVersion", "1.3.1.0\0" VALUE "InternalName", "WindowsTerminal\0" VALUE "LegalCopyright", "Copyright (c) winTerm contributors. Portions copyright Microsoft Corporation.\0" VALUE "OriginalFilename", "WindowsTerminal.exe\0" VALUE "ProductName", "winTerm\0" - VALUE "ProductVersion", "1.3.0-beta3\0" + VALUE "ProductVersion", "1.3.1\0" END END BLOCK "VarFileInfo" diff --git a/src/cascadia/wt/wt.rc b/src/cascadia/wt/wt.rc index 9ca4e210d..c19aae744 100644 --- a/src/cascadia/wt/wt.rc +++ b/src/cascadia/wt/wt.rc @@ -58,8 +58,8 @@ IDI_APPICON ICON "..\\..\\..\\res\\terminal.ico" #if defined(WT_BRANDING_WINTERM) 1 VERSIONINFO - FILEVERSION 1,3,0,6 - PRODUCTVERSION 1,3,0,6 + FILEVERSION 1,3,1,0 + PRODUCTVERSION 1,3,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -76,12 +76,12 @@ BEGIN BEGIN VALUE "CompanyName", "helloThisWorld\0" VALUE "FileDescription", "winTerm Launcher\0" - VALUE "FileVersion", "1.3.0.6\0" + VALUE "FileVersion", "1.3.1.0\0" VALUE "InternalName", "winTerm\0" VALUE "LegalCopyright", "Copyright (c) winTerm contributors. Portions copyright Microsoft Corporation.\0" VALUE "OriginalFilename", "winTerm.exe\0" VALUE "ProductName", "winTerm\0" - VALUE "ProductVersion", "1.3.0-beta3\0" + VALUE "ProductVersion", "1.3.1\0" END END BLOCK "VarFileInfo" diff --git a/src/winterm-tools/winterm-shim/winterm-shim.rc b/src/winterm-tools/winterm-shim/winterm-shim.rc index d221c720a..c241af3d9 100644 --- a/src/winterm-tools/winterm-shim/winterm-shim.rc +++ b/src/winterm-tools/winterm-shim/winterm-shim.rc @@ -4,8 +4,8 @@ #include 1 VERSIONINFO - FILEVERSION 1,3,0,6 - PRODUCTVERSION 1,3,0,6 + FILEVERSION 1,3,1,0 + PRODUCTVERSION 1,3,1,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS VS_FF_DEBUG @@ -22,12 +22,12 @@ BEGIN BEGIN VALUE "CompanyName", "helloThisWorld\0" VALUE "FileDescription", "winTerm Shell Integration Helper\0" - VALUE "FileVersion", "1.3.0.6\0" + VALUE "FileVersion", "1.3.1.0\0" VALUE "InternalName", "winterm-shim\0" VALUE "LegalCopyright", "Copyright (c) winTerm contributors.\0" VALUE "OriginalFilename", "winterm-shim.exe\0" VALUE "ProductName", "winTerm\0" - VALUE "ProductVersion", "1.3.0-beta3\0" + VALUE "ProductVersion", "1.3.1\0" END END BLOCK "VarFileInfo" diff --git a/src/winterm/Branding/ReleaseMetadata.h b/src/winterm/Branding/ReleaseMetadata.h index d4681f444..9434e97b0 100644 --- a/src/winterm/Branding/ReleaseMetadata.h +++ b/src/winterm/Branding/ReleaseMetadata.h @@ -25,8 +25,8 @@ namespace winTerm::Branding { inline constexpr std::wstring_view Publisher{ L"helloThisWorld" }; - inline constexpr std::wstring_view ApplicationVersion{ L"1.3.0-beta3" }; - inline constexpr std::wstring_view ReleaseChannel{ L"Beta" }; + inline constexpr std::wstring_view ApplicationVersion{ L"1.3.1" }; + inline constexpr std::wstring_view ReleaseChannel{ L"Stable" }; inline constexpr std::wstring_view CommitSha{ WINTERM_BUILD_COMMIT_SHA }; inline constexpr std::wstring_view BuildTimestamp{ WINTERM_BUILD_TIMESTAMP }; inline constexpr std::wstring_view WorkflowRunId{ WINTERM_BUILD_WORKFLOW_RUN_ID }; diff --git a/src/winterm/Branding/version.json b/src/winterm/Branding/version.json index 99082cd9f..cf06818a0 100644 --- a/src/winterm/Branding/version.json +++ b/src/winterm/Branding/version.json @@ -1,10 +1,10 @@ { - "applicationVersion": "1.3.0-beta3", - "packageVersion": "1.3.0.6", - "moduleVersion": "1.3.0", - "modulePrerelease": "beta3", - "channel": "beta", - "tag": "v1.3.0-beta3", + "applicationVersion": "1.3.1", + "packageVersion": "1.3.1.0", + "moduleVersion": "1.3.1", + "modulePrerelease": "", + "channel": "stable", + "tag": "v1.3.1", "workspaceSchemaVersion": 2, "dockingModelVersion": 1, "shellProtocolVersion": 1, diff --git a/src/winterm/Workspaces/Model/WorkspaceDescriptor.h b/src/winterm/Workspaces/Model/WorkspaceDescriptor.h index 68686acbf..6eaf8caff 100644 --- a/src/winterm/Workspaces/Model/WorkspaceDescriptor.h +++ b/src/winterm/Workspaces/Model/WorkspaceDescriptor.h @@ -219,7 +219,7 @@ namespace winTerm::Workspaces std::string createdAt; std::string updatedAt; WorkspaceSource source{ WorkspaceSource::User }; - std::string applicationVersion{ "1.3.0-beta3" }; + std::string applicationVersion{ "1.3.1" }; uint32_t protocolVersion{ 1 }; uint32_t dockingModelVersion{ DockingModelVersion }; WorkspaceStartupBehavior startupBehavior; diff --git a/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp b/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp index 99ab22803..023376ec3 100644 --- a/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp +++ b/src/winterm/Workspaces/Persistence/WorkspaceSerializer.cpp @@ -618,7 +618,7 @@ WorkspaceDescriptor WorkspaceSerializer::FromJson(const Json::Value& json, const throw std::runtime_error("The workspace source is not supported."); } workspace.source = *source; - workspace.applicationVersion = StringOrDefault(json, "applicationVersion", "1.3.0-beta3"); + workspace.applicationVersion = StringOrDefault(json, "applicationVersion", "1.3.1"); workspace.protocolVersion = UIntOrDefault(json, "protocolVersion", 1); workspace.dockingModelVersion = UIntOrDefault(json, "dockingModelVersion", DockingModelVersion); if (const auto& startup = json["startupBehavior"]; !startup.isNull()) From 9f4a9183ceae554a3a5baad0d9d2faf425ac50df Mon Sep 17 00:00:00 2001 From: HelloThisWorld Date: Tue, 11 Aug 2026 21:40:58 +0800 Subject: [PATCH 2/3] test: fix Find keybinding layer format and search span end assertion 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. --- src/cascadia/UnitTests_Control/ControlCoreTests.cpp | 2 +- src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp index 9d38050c5..d2aa20df6 100644 --- a/src/cascadia/UnitTests_Control/ControlCoreTests.cpp +++ b/src/cascadia/UnitTests_Control/ControlCoreTests.cpp @@ -409,7 +409,7 @@ namespace ControlUnitTests VERIFY_ARE_EQUAL(2u, rows.size()); VERIFY_ARE_EQUAL(0, rows[0].start.x); VERIFY_ARE_EQUAL(1, rows[0].start.y); - VERIFY_ARE_EQUAL(4, rows[0].end.x); + VERIFY_ARE_EQUAL(5, rows[0].end.x); // the end column is exclusive VERIFY_ARE_EQUAL(1, rows[0].end.y); VERIFY_ARE_EQUAL(0, rows[1].start.x); VERIFY_ARE_EQUAL(3, rows[1].start.y); diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index a0886c830..3e295aeab 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -202,11 +202,13 @@ namespace SettingsModelUnitTests verifyAction(KeyChord{ true, false, true, false, static_cast('F'), 0 }, ShortcutAction::Find); // A user layer can reclaim raw Ctrl+F for a terminal application by - // unbinding it, and the alias keeps Find reachable. + // unbinding it, and the alias keeps Find reachable. The in-box layer + // mirrors the defaults.json shape: one action, two keybindings. auto layered = winrt::make_self(); layered->LayerJson(VerifyParseSucceeded(R"([ - { "command": "find", "keys": "ctrl+f" }, - { "command": "find", "keys": "ctrl+shift+f" } + { "command": "find", "id": "Terminal.FindText" }, + { "keys": "ctrl+f", "id": "Terminal.FindText" }, + { "keys": "ctrl+shift+f", "id": "Terminal.FindText" } ])"), OriginTag::InBox); layered->LayerJson(VerifyParseSucceeded(R"([ From 525497eba87c9b499f4248f9a5ce08204353fd77 Mon Sep 17 00:00:00 2001 From: HelloThisWorld Date: Tue, 11 Aug 2026 21:45:19 +0800 Subject: [PATCH 3/3] test: keep FragmentActionNoKeys aligned with the Ctrl+F default 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. --- .../UnitTests_SettingsModel/DeserializationTests.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp index c253a1749..e0e842f47 100644 --- a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp @@ -2156,7 +2156,12 @@ namespace SettingsModelUnitTests const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); const auto actionsByName = actionMap->NameMap(); VERIFY_IS_NOT_NULL(actionsByName.TryLookup(L"Test Action")); - VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('F'), 0 })); + + // The fragment's "keys" field must be ignored: Ctrl+F still resolves + // to the in-box Find binding, not to the fragment's action. + const auto ctrlF = actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('F'), 0 }); + VERIFY_IS_NOT_NULL(ctrlF); + VERIFY_ARE_EQUAL(static_cast(ShortcutAction::Find), static_cast(ctrlF.ActionAndArgs().Action())); } void DeserializationTests::FragmentActionNested()