diff --git a/.github/actions/spelling/allow/allow.txt b/.github/actions/spelling/allow/allow.txt
index bf9f1af1fdd..ef4f666f47d 100644
--- a/.github/actions/spelling/allow/allow.txt
+++ b/.github/actions/spelling/allow/allow.txt
@@ -1,77 +1,40 @@
aci
-admins
allcolors
-Apc
-apc
-backpressure
breadcrumb
breadcrumbs
-bsd
-calt
ccmp
ccon
-changelog
clickable
-clig
CMMI
+colorbrewer
consvc
copyable
-Counterintuitively
-CtrlDToClose
-CVS
-CUI
-cybersecurity
dalet
-Dcs
dcs
deselection
dialytika
diffing
dje
-downside
downsides
dze
dzhe
-DTo
-EDDB
-EDDC
Emacspeak
-Enum'd
Fitt
-formattings
FTCS
-ftp
-fvar
gantt
-gcc
-geeksforgeeks
ghe
-github
gje
godbolt
-hostname
-hostnames
-https
-hyperlink
hyperlinking
hyperlinks
-iconify
-ID
-img
-inlined
-issuetitle
-It'd
kje
libfuzzer
-libuv
liga
lje
Llast
-llvm
Lmid
locl
lol
-lorem
Lorigin
maxed
megathread
@@ -80,28 +43,24 @@ mkmk
mnt
mru
nje
-noreply
notwrapped
ogonek
-ok'd
overlined
perlw
-pipeline
postmodern
Powerline
-powerline
ptys
+pwn
pwshw
-quickfix
qof
qps
-Remappings
-Retargets
+quickfix
rclt
reimplementation
+Remappings
reserialization
-reserialize
reserializes
+Retargets
rlig
rubyw
runtimes
@@ -109,34 +68,20 @@ servicebus
shcha
similaritytolerance
slnt
-Sos
-ssh
-sustainability
stakeholders
+sustainability
sxn
-timeline
-timelines
-timestamped
TLDR
-tokenizes
tonos
toolset
-truthiness
tshe
-ubuntu
UEFI
uiatextrange
-UIs
und
-unregister
-versioned
vsdevcmd
-walkthrough
-walkthroughs
-We'd
westus
-wildcards
workarounds
+wtconfig
XBox
YBox
yeru
diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index a2f9f7be00e..3a6a2a6b7c1 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -515,6 +515,7 @@ dsound
DSSCL
DSwap
DTest
+DTo
DTTERM
dup'ed
dvi
@@ -737,6 +738,7 @@ HABCDEF
Hackathon
HALTCOND
HANGEUL
+hardlinks
hashalg
HASSTRINGS
hbitmap
@@ -1079,6 +1081,7 @@ MOUSEFIRST
MOUSEHWHEEL
MOVESTART
msb
+msbuildcache
msctf
msctls
msdata
@@ -1488,6 +1491,7 @@ reparented
reparenting
replatformed
Replymessage
+reportfileaccesses
repositorypath
Requiresx
rerasterize
@@ -2004,6 +2008,7 @@ wincontypes
WINCORE
windbg
WINDEF
+windir
windll
WINDOWALPHA
windowdpiapi
diff --git a/.gitignore b/.gitignore
index 3db8546ef4e..9e8281ee5dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -283,3 +283,6 @@ MSG*.bin
profiles.json
*.metaproj
*.swp
+
+# MSBuildCache
+/MSBuildCacheLogs/
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 00000000000..312ae503ed4
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+ Microsoft.MSBuildCache.AzurePipelines
+ Microsoft.MSBuildCache.Local
+
+
+
+
+ 202310210737
+
+
+
+ $(MSBuildCacheAllowFileAccessAfterProjectFinishFilePatterns);
+ \**\ApplicationInsights.config;
+ $(LocalAppData)\Microsoft\VSApplicationInsights\**;
+ $(LocalAppData)\Microsoft\Windows\INetCache\**;
+ A:\;
+ E:\;
+ $(windir)\**;
+
+
+
+ $(MSBuildCacheIdenticalDuplicateOutputPatterns);bin\**
+
+
+ $(MSBuildThisFileDirectory)\dep\nuget\packages.config
+ $(MSBuildCacheIgnoredInputPatterns);$(PackagesConfigFile)
+
+
+
+ $([System.IO.File]::ReadAllText("$(PackagesConfigFile)"))
+ $([System.Text.RegularExpressions.Regex]::Match($(PackagesConfigContents), 'Microsoft.MSBuildCache.*?version="(.*?)"').Groups[1].Value)
+ $(MSBuildThisFileDirectory)packages\$(MSBuildCachePackageName).$(MSBuildCachePackageVersion)
+ $(MSBuildThisFileDirectory)packages\Microsoft.MSBuildCache.SharedCompilation.$(MSBuildCachePackageVersion)
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 00000000000..df6e6b7ebb1
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NOTICE.md b/NOTICE.md
index fb48315d254..091060db2cd 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -325,6 +325,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
+## ColorBrewer
+**Source**: [https://colorbrewer2.org/](https://colorbrewer2.org/)
+
+### License
+
+```
+Apache-Style Software License for ColorBrewer software and ColorBrewer Color Schemes
+
+Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State University.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+```
+
# Microsoft Open Source
This product also incorporates source code from other Microsoft open source projects, all licensed under the MIT license.
diff --git a/build/pipelines/ci-caching.yml b/build/pipelines/ci-caching.yml
index 96b79c46999..e8379b556e9 100644
--- a/build/pipelines/ci-caching.yml
+++ b/build/pipelines/ci-caching.yml
@@ -1,30 +1,32 @@
trigger:
batch: true
-# branches:
-# include:
-# - main
-# - feature/*
-# - gh-readonly-queue/*
-# paths:
-# exclude:
-# - doc/*
-# - samples/*
-# - tools/*
+ branches:
+ include:
+ - main
+ - feature/*
+ - gh-readonly-queue/*
+ paths:
+ exclude:
+ - doc/*
+ - samples/*
+ - tools/*
-#pr:
-# branches:
-# include:
-# - main
-# - feature/*
-# paths:
-# exclude:
-# - doc/*
-# - samples/*
-# - tools/*
+pr:
+ branches:
+ include:
+ - main
+ - feature/*
+ paths:
+ exclude:
+ - doc/*
+ - samples/*
+ - tools/*
variables:
- name: runCodesignValidationInjectionBG
value: false
+ - name: EnablePipelineCache
+ value: true
# 0.0.yyMM.dd##
# 0.0.1904.0900
@@ -81,6 +83,8 @@ stages:
buildConfigurations: [Release]
buildEverything: true
keepAllExpensiveBuildOutputs: false
+ ${{ if eq(variables['System.PullRequest.IsFork'], 'False') }}:
+ enableCaching: true
- ${{ if eq(parameters.runTests, true) }}:
- stage: Test_${{ platform }}
diff --git a/build/pipelines/templates-v2/job-build-project.yml b/build/pipelines/templates-v2/job-build-project.yml
index ae5d3a16ac6..ac350da72fd 100644
--- a/build/pipelines/templates-v2/job-build-project.yml
+++ b/build/pipelines/templates-v2/job-build-project.yml
@@ -68,6 +68,9 @@ parameters:
- name: signingIdentity
type: object
default: {}
+ - name: enableCaching
+ type: boolean
+ default: false
jobs:
- job: ${{ parameters.jobName }}
@@ -95,6 +98,7 @@ jobs:
# Yup.
BuildTargetParameter: ' '
SelectedSigningFragments: ' '
+ MSBuildCacheParameters: ' '
# When building the unpackaged distribution, build it in portable mode if it's Canary-branded
${{ if eq(parameters.branding, 'Canary') }}:
UnpackagedBuildArguments: -PortableMode
@@ -111,6 +115,7 @@ jobs:
clean: true
submodules: true
persistCredentials: True
+
# This generates either nothing for BuildTargetParameter, or /t:X;Y;Z, to control targets later.
- pwsh: |-
If (-Not [bool]::Parse("${{ parameters.buildEverything }}")) {
@@ -139,6 +144,17 @@ jobs:
}
displayName: Prepare Build and Sign Targets
+ - ${{ if eq(parameters.enableCaching, true) }}:
+ - pwsh: |-
+ $MSBuildCacheParameters = ""
+ $MSBuildCacheParameters += " -graph"
+ $MSBuildCacheParameters += " -reportfileaccesses"
+ $MSBuildCacheParameters += " -p:MSBuildCacheEnabled=true"
+ $MSBuildCacheParameters += " -p:MSBuildCacheLogDirectory=$(Build.SourcesDirectory)\MSBuildCacheLogs"
+ Write-Host "MSBuildCacheParameters: $MSBuildCacheParameters"
+ Write-Host "##vso[task.setvariable variable=MSBuildCacheParameters]$MSBuildCacheParameters"
+ displayName: Prepare MSBuildCache variables
+
- pwsh: |-
.\build\scripts\Generate-ThirdPartyNotices.ps1 -MarkdownNoticePath .\NOTICE.md -OutputPath .\src\cascadia\CascadiaPackage\NOTICE.html
displayName: Generate NOTICE.html from NOTICE.md
@@ -160,21 +176,37 @@ jobs:
${{ parameters.additionalBuildOptions }}
/bl:$(Build.SourcesDirectory)\msbuild.binlog
$(BuildTargetParameter)
+ $(MSBuildCacheParameters)
platform: $(BuildPlatform)
configuration: $(BuildConfiguration)
+ msbuildArchitecture: x64
maximumCpuCount: true
+ ${{ if eq(parameters.enableCaching, true) }}:
+ env:
+ SYSTEM_ACCESSTOKEN: $(System.AccessToken)
- ${{ if eq(parameters.publishArtifacts, true) }}:
- publish: $(Build.SourcesDirectory)/msbuild.binlog
artifact: logs-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
condition: always()
displayName: Publish Build Log
+ - ${{ if eq(parameters.enableCaching, true) }}:
+ - publish: $(Build.SourcesDirectory)\MSBuildCacheLogs
+ artifact: logs-msbuildcache-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
+ condition: always()
+ displayName: Publish MSBuildCache Logs
- ${{ else }}:
- task: CopyFiles@2
displayName: Copy Build Log
inputs:
contents: $(Build.SourcesDirectory)/msbuild.binlog
TargetFolder: $(Terminal.BinDir)
+ - ${{ if eq(parameters.enableCaching, true) }}:
+ - task: CopyFiles@2
+ displayName: Copy MSBuildCache Logs
+ inputs:
+ contents: $(Build.SourcesDirectory)/MSBuildCacheLogs/**
+ TargetFolder: $(Terminal.BinDir)/MSBuildCacheLogs
# This saves ~2GiB per architecture. We won't need these later.
# Removes:
diff --git a/dep/nuget/packages.config b/dep/nuget/packages.config
index e6fd7ec3a68..a69adb22942 100644
--- a/dep/nuget/packages.config
+++ b/dep/nuget/packages.config
@@ -17,4 +17,9 @@
+
+
+
+
+
diff --git a/doc/specs/#1595 - Suggestions UI/Snippets.md b/doc/specs/#1595 - Suggestions UI/Snippets.md
new file mode 100644
index 00000000000..4fd12702ff2
--- /dev/null
+++ b/doc/specs/#1595 - Suggestions UI/Snippets.md
@@ -0,0 +1,648 @@
+---
+author: Mike Griese
+created on: 2022-08-22
+last updated: 2024-06-13
+issue id: 1595
+---
+
+# Windows Terminal - Snippets
+
+## Abstract
+
+The command line is a highly powerful tool. However, its power is dependent on
+the user's knowledge of the specific commands, flags and parameters needed to
+perform tasks from the command-line. For simple everyday commands, this might
+not be so hard. For longer commands, or ones used less frequently, there's quite
+a bit of mental overhead trying to recall the exact syntax. For teams, it might
+be helpful to share these tasks with everyone on the project. The Terminal can
+be an avenue by which complicated tasks can be remembered, shared, discovered,
+and recalled by the user simply thinking **"what do I want to do"**, rather than
+"how do I do it".
+
+## Background
+
+> **Note**:
+>
+> This largely builds off of work in the [Suggestions UI], for displaying these
+> tasks to the user. Make sure to read that spec first.
+
+### Inspiration
+
+The primordial version of this idea was probably [#keep] - a command-line tool I
+wrote for stashing long command-lines and directories, and recalling them with
+just a number. We've had many variations on this idea over the years - [#1595]
+was probably the first such request on the Terminal repo. ITerm2 also had [a
+similar feature](https://iterm2.com/images/CommandHistory.png). Theirs was more
+directly tied to shell integration (that menu is populated from commands that
+they know were run in the shell). In the absence of shell integration though, it
+should be able to save these commands to a menu manually.
+
+Consider [VsCode Tasks]. These are files which can be placed in the root of
+a workspace, and share common tasks between users of that workspace. They've got
+support for starting processes, with a set of args. These args can also be
+picked at runtime, and custom sets of arguments can be specified for individual
+arguments.
+
+It is hard to say that the ultimate vision here isn't partially inspired by the
+"[workflows]" of [Warp], or by [Fig]. These are modern tools that seek to
+augment the command-line experience, by making the command-line more
+approachable. Warp quite clearly has the same concept in "workflows" - scripts
+which the user can build and Warp (a Terminal emulator) can insert quickly. Fig,
+on the other hand, is more focused on just simplifying the command-line
+experience. Fig is more about providing additional metadata to the user as
+they're typing. They are [also working on workflows], so there's clearly quite a
+bit of ecosystem-wide demand for more discoverable command-line tooling.
+
+We've had verbatim feedback that developers already attempt to record useful
+commandlines in various different ways - in OneNotes, in shell scripts, in
+aliases. Furthermore, developers often share these commands with the rest of
+their teams. Providing a unified way to easily store, browse, and use these
+command lines should be valuable to developers already doing this. A static
+file in their project containing commands for the whole team seems like a simple
+solution to this problem.
+
+### User Stories
+
+Story | Size | Description
+--|-----------|--
+A | โ
Done | Users can bring up a menu of command line tasks and quickly execute them
+B | โ
Done | Fragment apps can provide tasks to a users settings
+C | ๐ถ Walk | The user can save commands straight to their settings with a `wt` command
+D | ๐ถ Walk | Users can have different tasks enabled for different profiles(/shells?)
+E | ๐ถ Walk | The Terminal displays a Snippets Pane for easy browsing of relevant snippets
+F | ๐โโ๏ธ Run | The terminal can automatically look for command fragments in the tree of the CWD
+G | ๐โโ๏ธ Run | Snippets with multiple lines can be sent only conditionally on the success of the previous command (with shell integration)
+H | โ
Done | Snippets can be filtered by text the user has already typed
+I | ๐ Sprint | Snippets can have prompt-able sections of input
+J | ๐ Sprint | Community tasks are hosted in a public GH repo
+K | ๐ Sprint | A simple UX (either web or in Terminal) is exposed for interacting with public GH repo of tasks
+
+### Elevator Pitch
+
+The Terminal can remember long command-lines and display them with user-friendly
+descriptions of _what they actually do_. These tasks can be searched by intent,
+rather than the particular combination of flags. They can be shared with members
+of your team, so everyone has easy access to common tasks for a project.
+
+### Why not just aliases / native script files?
+
+Why not just take these tasks and put them into a shell alias? For longer tasks,
+why not just stick them in a `.ps1`/`.bat`/`.sh`/etc file? This is an option
+that's existed since the time immemorial. However, this still requires the user
+to remember that they've created these aliases/scripts, remember where they're
+stored, and remember how they work.
+
+By providing a dedicated UI for these command-lines, they can always be at your
+fingertips. No need to remember what the alias for a particular command-line is -
+just look up what you want to do. Aliases and scripts are no longer scattered
+across `.bashrc`, `.bash_profile`, `.profile`, etc, they can all be stashed in
+the Terminal config, or in the project they're relevant to. By stashing them
+alongside the code, then anyone else coming to work on the code can have
+immediate access to useful sets of tasks.
+
+Aliases have a tendency towards more experienced shell users. This proposal
+instead brings the power of these aliases and scripts right to the foreground,
+with a cross-shell mechanism of exposing them to even beginners. With fragment
+extensions, tools can bundle common workflows together with their application so
+the Terminal can automatically load them for the user.
+
+## Business Justification
+
+It will delight developers.
+
+## Scenario Details
+
+### Implementation Details
+
+For the most part, this is already implemented as the `sendInput` action. These
+actions send text to the terminal already, and work quite well as snippets.
+
+#### Basics
+
+We'll want to also augment `sendInput` to add support for `input` as an array of
+strings, not only a single string value. When the input is a list of strings,
+then the terminal can send each string, separated by the enter key.
+We can also add a `waitForSuccess` parameter to `sendInput` (with a default
+value of `false`). If that's set to `true`, **and shell integration is enabled**,
+then the Terminal will wait to send each command until the previous command
+exits.
+
+As another minor improvement, we'll add a `description` property to Commands.
+This will allow users to add additional information to snippets which we can
+surface. Additionally, extension authors could provide more details as well.
+
+As a matter of renaming, we'll also update `"source": "tasks"` for the
+`SuggestionsSource` enum to instead be `snippets` (and gracefully update that
+where we find it). "tasks" was an older name for this feature, and "snippets"
+will better align with our partners in VsCode.
+
+##### Multi-line snippets example
+
+Consider the [following script](https://gist.github.com/zadjii-msft/b598eebd6c5601328498e3e7acc581a7):
+
+```pwsh
+$s=Invoke-GitHubGraphQlApi "query{organization(login:`"Microsoft`"){projectV2(number: 159) { id } } }"
+
+$tasks = get-GitHubIssue -Labels "Issue-Task" -state open
+$bugs = get-GitHubIssue -Labels "Issue-Bug" -state open
+$issues = $tasks + $bugs
+
+$issues | ? {$_.labels.Name -NotContains "Needs-Triage" } | ? { $_.milestone.title -Ne "Icebox โ" } | ? type -Ne "PullRequest" | select -expand node_id | % {
+ $resp = Add-GitHubBetaProjectItem -ProjectNodeId $s.organization.projectV2.id -ContentNodeId $_ ;
+}
+```
+
+As just a raw sendInput action with a single `input`, this would look like the following:
+
+```jsonc
+{
+ "command":
+ {
+ "action": "sendInput",
+ "input": "$s=Invoke-GitHubGraphQlApi \"query{organization(login:`\"Microsoft`\"){projectV2(number: 159) { id } } }\"\r\n$tasks = get-GitHubIssue -Labels \"Issue-Task\" -state open\r\n$bugs = get-GitHubIssue -Labels \"Issue-Bug\" -state open\r\n$issues = $tasks + $bugs\r\n$issues | ? {$_.labels.Name -NotContains \"Needs-Triage\" } | ? { $_.milestone.title -Ne \"Icebox โ\" } | ? type -Ne \"PullRequest\" | select -expand node_id | % {\r\n $resp = Add-GitHubBetaProjectItem -ProjectNodeId $s.organization.projectV2.id -ContentNodeId $_ ;\r\n}"
+ },
+ "name": "Upload to project board",
+ "description": "Sync all our issues and bugs that have been triaged and are actually on the backlog to the big-ol project",
+},
+```
+
+This JSON is basically entirely unusable. Since JSON doesn't support multiline
+strings, then every line has to be joined to a single line, separated by `\r\n`.
+
+Instead, the following version of this command uses an array for the `input`
+parameter. This then implies that each string should be sent in sequence, with
+enter between them.
+
+```jsonc
+{
+ "command":
+ {
+ "action": "sendInput",
+ "input":
+ [
+ "$s=Invoke-GitHubGraphQlApi \"query{organization(login:`\"Microsoft`\"){projectV2(number: 159) { id } } }\"",
+ "$tasks = get-GitHubIssue -Labels \"Issue-Task\" -state open",
+ "$bugs = get-GitHubIssue -Labels \"Issue-Bug\" -state open",
+ "$issues = $tasks + $bugs",
+ "$issues | ? {$_.labels.Name -NotContains \"Needs-Triage\" } | ? { $_.milestone.title -Ne \"Icebox โ\" } | ? type -Ne \"PullRequest\" | select -expand node_id | % {",
+ " $resp = Add-GitHubBetaProjectItem -ProjectNodeId $s.organization.projectV2.id -ContentNodeId $_ ;",
+ "}",
+ ""
+ ]
+ },
+ "name": "Upload to project board",
+ "description": "Sync all our issues and bugs that have been triaged and are actually on the backlog to the big-ol project",
+},
+```
+
+This is slightly more maintainable. Assuming the user also has shell integration
+enabled, they could also set `"waitForSuccess": true`, and if any part of the
+script fails, then the rest of it won't be sent to the shell[[1](#footnote-1)].
+
+#### Fragment actions
+
+This was already added in [#16185]. These will allow third-party developers to
+create apps which add additional snippets to the Terminal. These will require
+app developers to add `id`s to each action they add in this way. Users can then
+bind that action `id` to a keybinding, if they so choose.
+
+Case in point:
+https://github.com/abduvik/just-enough-series/tree/master/courses/docker+docker-compose.
+Something like that should be able to be easily added directly to the Terminal.
+
+### Snippets pane
+
+With non-terminal content landing in 1.21 Preview, it's now simple to add
+additional types of panes to add to the Terminal. We'll support a new pane
+`"type": "snippets"`, to support opening the Snippets pane.
+
+This will be a pane with a `TreeView` in it and a text box to filter results
+(ala the Command Palette).
+
+Each item in the TreeView will be a kind of `FilteredCommand`, with a play
+button to support quickly running the command.
+
+This pane could also support all the different suggestion sources that the
+Suggestions UI supports - `recentCommands` could be plumbed into it from the
+currently active This pane could also support checkboxes to filter different
+suggestion sources.
+
+### Per-Project Snippets (`.wt.json`)
+
+Users may also want to leave snippets in the root of their repo, for others to
+use as well. To support this, the Terminal will automatically look for a
+`.wt.json` file in any directories that are parents of the CWD of the shell, and
+load actions from that file as well. The syntax for this file will be a modified
+version of the standard settings schema. As an example:
+
+```json
+{
+ "$version": "1.0.0",
+ "snippets":
+ [
+ {
+ "input": "bx",
+ "name": "Build project",
+ "description": "Build the project in the CWD"
+ },
+ {
+ "input": "bcz",
+ "name": "Clean & build solution",
+ "icon": "\uE8e6",
+ "description": "Start over. Go get your coffee. "
+ },
+ {
+ "input": "nuget push -ApiKey az -source TerminalDependencies %userprofile%\\Downloads" ,
+ "name": "Upload package to nuget feed",
+ "icon": "\uE898",
+ "description": "Go download a .nupkg, put it in ~/Downloads, and use this to push to our private feed."
+ },
+ ]
+}
+```
+
+Instead of `actions`, the top-level list is `snippets`. These snippet objects
+are a simplification of the `Command` object. They have a `name`, `description`,
+and `icon` properties, just like a `Command`. However, instead of an arbitrary
+`action`, we will just have the `SendInput` action's args as properties directly
+in the object.
+
+Additionally, we'll also support a `$version` field, in case we ever want to
+make breaking changes to the schema. When this is missing, we'll just assume the
+version to be `1.0.0`, which is this originally proposed schema.
+
+By default, a `TermControl` is always initialized with the CWD set to the
+`startingDirectory` of a profile. So, even for users that don't have shell
+integration enabled, the Terminal will still be able to load snippets from the
+`.wt.json` in the profile's `startingDirectory`. If the user has shell
+integration configured to tell the Terminal about the CWD, then we'll refresh
+that list as the user changes directories.
+
+* In `Terminal.Settings.Model`, we will store a cached map of path->actions.
+ * that if multiple panes are all in the same CWD, they don't need to
+ individually re-read the file from disk and regenerate that part of the map.
+* I believe it should be impossible for a keybinding to be bound to a local
+ action. Even if it has an ID, the file won't be loaded when we build the
+ keymap, and we don't really want the keymap changing based on CWD. Also, with
+ the actions living in an entirely separate map of CWD->actions, the
+ keybindings in the main map won't be able to easily get to them. See also
+ [Security considerations](#security) for more.
+* If the Snippets pane or Suggestions UI is opened with `snippets` as a source,
+ then we'll just append the appropriate list of suggestions for the active
+ control's CWD.
+ * We don't need to have the control raise an event when the CWD changes - we
+ can lazy-load these actions when a UI element that requires it is first
+ invoked.
+* These _local_ snippets will not be included in the Command Palette.
+ (`sendInput` actions in the settings file and in fragments still will be,
+ however)
+ * The Command Palette is quite tightly bound to our own ActionMap settings
+ model, accumulated through complicated layering of defaults, fragments, and
+ user settings. It's not trivially mutable at runtime in the way that
+ context-sensitive snippets would require.
+ * The Suggestions UI and Snippets pane are both surfaces that are better
+ equipped to handle context-relevant actions, especially where the context is
+ a `TermControl`.
+ * The Suggestions UI and snippets pane will give us more flexibility in
+ customizing the experience specifically for snippets. Case in point - we'll
+ want to filter the suggestions UI based on both the `Name` _and_ the `Input`
+ of the send input command. Contrast that with the Command Palette which is
+ currently only capable of filtering based on names.
+* If we find multiple `.wt.json` files in the ancestors of the CWD (e.g. for
+ `c:\a\b\c\d\`, there's a `c:\a\.wt.json` and a `c:\a\b\c\.wt.json`), then
+ we'll add each one separately to the map of paths->directories. When
+ requesting the actual actions for `c:\a\b\c\d\`, we'll layer the ones from
+ `c:\a\` before the ones from `c:\a\b\c`, so that deeper descendants take
+ precedence.
+ * For example, `c:\a\.wt.json` has a snippet with `input: "foo", name:
+ "Build"`, and `c:\a\b\c\.wt.json` has a snippet with `input: "bar", name:
+ "Build"`. When the user is under `c:\a\b\c`, the Terminal will show `bar`
+ when the user selects the `Build` suggestion. Otherwise, if the user is
+ under `c:\a`, then the Terminal will show `foo`.
+* If we fail to parse the `.wt.json` file, then we'll ignore it. For parsing
+ errors, we'll want to display warnings to the user:
+ * If the user had opened the suggestions UI, we can display a Toast the first
+ time we fail to load it to show the error.
+ * In the snippets pane, we can have static text along the lines of "failed to
+ parse snippets found in `path/to/file`" at the top of the pane.
+
+### Saving snippets from the commandline
+
+_This has already been prototyped in [#16513]_
+
+Users should be able to save commands as snippets directly from the commandline.
+Consider: you've just run the command that worked the way you need it to. You
+shouldn't have to open the settings to then separately copy-paste the command in
+to save it. It should be as easy as Up, Home, `wt x-save `,
+Enter.
+
+This will be powered by a `saveSnippet` action behind the scenes. However, we
+won't actually parse these actions from a user's settings file. They don't
+really make sense to have the action to save a snippet to the settings file, in
+the settings file already.
+
+The exact syntax of `x-save` is as follows:
+
+#### `x-save` subcommand
+
+`x-save [--name,-n name][--description,-d description][-- commandline]`
+
+Saves a given commandline as a sendInput action to the Terminal settings file.
+This will immediately write the Terminal settings file.
+
+**Parameters**:
+* `--name,-n name`: The name to assign to the `name` parameter of the saved
+ command. If omitted, then the parameter will be left blank, and the command
+ will use the auto-generated "Send input:..." name in menus.
+* `--description,-d`: The description to optionally assign to the command.
+* `commandline`: The commandline to save as the `input` of the `sendInput` action.
+
+If the `save` subcommand is ran without any other subcommands, the Terminal will
+imply the `-w 0` arguments, to attempt to send this action to the current
+Terminal window (unless of course, `-w` was manually provided on the
+commandline). This is done to avoid a new terminal window popping up, just to
+inform the user a command was saved. When run with other subcommands, then the
+action will just be ran in the same window as all the other subcommands.
+
+> [!NOTE]
+> In team discussions, we've decided to accept this for now as
+> experimental. We have some concerns about how effective we'll be at dealing
+> with all the weird edge cases of string escaping. For now, we'll keep this
+> subcommand as `x-save`, where `x-` implies "experimental".
+>
+> Perhaps once we add a dialog for saving these snippets, we can promote it out of experimental.
+
+### UI/UX Design
+
+For the most part, we'll be using the [Suggestions UI] to display tasks to the
+user. This is a text cursor-relative UI surface that can quickly display actions
+to the user, in the context of what they're working on.
+
+The following are some examples from VsCode and Warp. These are meant to be
+illustrative of what these menus already look like in the wild:
+
+![VS Code demo of tasks](img/vscode-tasks-000.gif)
+
+![Warp demo of workflows](img/warp-workflows-000.gif)
+
+A prototype of saving a command directly to the user's settings, then invoking
+it via the suggestions UI
+
+![Example of using `wt save foo bar --baz` to save the command, then invoke the saved command via the suggestions UI](img/save-command.gif)
+
+A prototype of reading tasks from the CWD
+
+![navigate into a folder with a .wt.json file, view that it's populated, then invoke a snippet from that file using the suggestions UI](img/tasks-from-cwd.gif)
+
+The initial version of the snippets pane:
+
+![snippets-pane](https://github.com/microsoft/terminal/assets/18356694/f4aee6f8-dbaa-4a70-a2ac-98244eb0e4f1)
+
+## Tenets
+
+
+
+Compatibility |
+
+I considered supporting YAML for local snippets (`.wt.json`), instead of JSON.
+JSON is not super friendly to command-lines - since everything's gotta be
+encapsulated as a string. Embedding tabs `\t`, newlines `\r`, escape characters,
+is fairly straightforward. However, quotes can get complicated fast in JSON,
+since they've got to be escaped too, and with many CLI utilities also having
+separate quote-parsing rules, JSON can get unwieldy quickly.
+
+However, supporting YAML directly would require us to spec out a YAML syntax for
+these files, and also find an OSS YAML parser and implement support for it. That
+would be quite a bit more expensive than JSON.
+
+ |
+
+Accessibility |
+
+Nothing particular to call out here. The Snippets pane will need to be a11y
+tested, like most of our other UI surfaces.
+
+ |
+
+Sustainability |
+
+No substantial climate impacts expected here. We're not using expensive compute
+resources for this feature, so the impact should be comparable to any other
+Terminal feature.
+
+ |
+
+Localization |
+
+I'm mildly worried here about the potential for community-driven tasks to have
+non-localized descriptions. We may need to accept a `description:{ en-us:"",
+pt-br:"", ...}`-style map of language->string descriptions. That may just need
+to be a future consideration for now.
+
+ |
+
+Security |
+
+Another reason we shouldn't support keys being able to be lazy-bound to local
+snippets: It's entirely too easy for `malicious.exe` to create a file in
+`%HomePath%` that creates a snippet for `\u003pwn-your-machine.exe\r` (or
+similar). Any app can read your settings file, and it is again too easy for that
+malicious app to set it's own action `id` to the same as some other well-meaning
+local snippet's ID which you DO have bound to a key.
+
+When we first load the snippets from the `.wt.json` file, we'll want to also ask
+them if they trust that folder. This is similar to the way that VsCode et. al.
+If they accept, then we'll add that folder to a list of trusted folders (and
+store permanently in `state.json`). If they don't, then we'll just ignore that
+file. To make things easier for the user, we can also add a checkbox to "trust
+the parent folder" in the dialog (again, similar to VsCode).
+
+We'll also want to engage our security partners to see if there's anything extra
+we'll need to do to ensure we're securely parsing random JSON files that we
+don't own.
+
+ |
+
+
+
+### Other potential issues
+
+Something like `wt save ping 8.8.8.8 > foo.txt` isn't going to work the way
+users want. The shell is gonna get the first crack at parsing that commandline,
+and is going to try and redirect the _output of `wt`_ to `foo.txt`.
+
+Predictably, local snippets won't work over ssh or other remote connections.
+Terminal is only able to read files off the local filesystem. We'll at best be
+able to read snippets from the directory they `ssh`'d _from_ locally.
+
+## Implementation Plan
+
+### ๐ฃ Crawl
+* [ ] The Command Palette and Suggestions UI need to be able to display both the
+ command name and a tooltip for the comment
+ - This will need to be reconciled with [#7039], which tracks displaying
+ non-localized names in the command palette
+ - It won't be a TeachingTip, since those are an unmitigated disaster. But we
+ can just fake it with another text box.
+ - A prototype can be found in [#17376]
+* [X] [#1595] Add the Suggestions UI, with support for `tasks`
+* [x] Fragments can add **actions** to a user's settings
+
+### ๐ถ Walk
+* [ ] The terminal can look for a settings file of tasks in a profile's
+ `startingDirectory` (regardless of shell integration being enabled)
+* [ ] [#5790] - profile specific actions
+* [ ] [#12857] Ability to save selected text as a `sendInput` action
+* [x] [#12861] Re-evaluate showing some sort of "ghost text" or other preview for snippets
+
+### ๐โโ๏ธ Run
+* [ ] When the user `cd`s to a directory (with shell integration enabled), the
+ terminal can load the tasks from that directory tree
+* [ ] [#10436] Users can manage all their fragments extensions directly in the Settings UI
+* [ ] The suggestions UI & snippets pane can filter not only on name of a
+ command, but for snippets, the input as well.
+* [ ] [#12927] Enlighten the suggestions UI to support (_a yet undeclared syntax
+ for_) snippets with prompt-able sections in them
+
+
+
+## Conclusion
+
+Snippets are something that developers immediately understand the value of.
+After talking with users, everyone we talked with immediately understood the
+concept, and you could see the gears turning on ways to integrate this into
+their own workflows.
+
+### Future Considerations
+
+* We may want to add additional params to the `save` subcommand in the future,
+ to configure where the snippet is saved:
+ * `--local`: Save to the `.wt.json` in the CWD, if there is one (or create
+ one)
+ * `--parent`: Save to the `.wt.json` in the first ancestor of the CWD, if
+ there is one. Otherwise create one here.
+ * `--settings`: Manually save to the settings file?
+ * `--profile`: save to this profile???? Not sure if this is actually possible.
+ Maybe with the `WT_SESSION_ID` env var to figure out which profile is in use
+ for the pane with that ID
+ * This would probably require per-profile actions, which are woefully under specified
+ * `--local`/`--parent`/`--settings` was well received in team discussion -
+ maybe we should just do them now.
+* Longer workflows might be better exposed as notebooks. We've already got a
+ mind to support [markdown in a notebook-like
+ experience](https://github.com/microsoft/terminal/issues/16495) in the
+ Terminal. For longer scripts that may need rich markup between commands, that
+ will likely be a better UX.
+* For what it is worth, [Warp] uses .yaml files for their "workflows". As an
+ example, see
+ [`clone_all_repos_in_org.yaml`](https://github.com/warpdotdev/workflows/blob/main/specs/git/clone_all_repos_in_org.yaml).
+ We may want to straight up just seamlessly support that syntax as well.
+ * Converting them to WT-compatible json is fairly trivial [[2](#footnote-2)].
+ * We may want to consider supporting YAML like this for `wt import`, ala [#10083]
+ * Similarly, we could import the YAML in the settings UI in a fashion similar
+ to how we import color schemes:
+ * Furthermore, the commands are all licensed under Apache 2.0, which means they
+ can be easily consumed by other OSS projects and shared with other developers.
+ * This leads us to the next future consideration:
+* Discoverability will be important. Perhaps the actions page could have a
+ toggle to immediately filter to "snippets"? Which then also displays some text
+ like "Tip: save snippets directly from the commandline with
+ `wt save `".
+* We should easily be able to put "Save command as snippet" into the quick fix
+ menu next to an individual prompt, when shell integration is enabled.
+* We should most definitely add a dialog for saving snippets directly in the Terminal.
+ * We'd have inputs for the commandline, name, description.
+ * Obviously, it'd be easy to have a "Add new" button (to open that dialog) on
+ the snippets pane.
+ * We could have `wt save` open that dialog pre-populated, rather than just
+ saving the command directly.
+ * We could even also find a way to pre-populate that dialog with the recent
+ commands (from shell integration)!
+* As a potential v2.0 of the snippets file schema, we may look to the
+ `.vscode/tasks.json` schema for inspiration. That file supports much more
+ complex task definitions. Notably, with the ability to prompt the user for
+ different inputs, for different parameter values. This is something that would
+ play well off of [#12927]
+* We may want to consider a future property of snippets like `shell`, which
+ specifies which shells a snippet can be used with. We could then only filter
+ to the snippets that will work with the current shell. This is left for the
+ future because we don't have a reliable way of knowing what shell application
+ the user is currently running.
+* We may want to consider promoting `sendInput` actions to a top-level
+ `snippets` array in `settings.json` in the future. That might make sharing
+ them from one user's settings, to a `.wt.json`, and back, a little easier.
+
+#### Community Snippets
+
+_The big stretch version of this feature._
+
+It would be supremely cool to have a community curated list of Snippets, for
+various tools. Stored publicly on a GitHub repo (a la the winget-pkgs repo).
+Users can submit Snippets with descriptions of what the Snippet does. The
+Terminal can plug into that repo automatically and fetch the latest community
+commands, immediately giving the user access to a wide berth of common
+Snippets. That could easily be done as another suggestion source.
+
+#### Profiles in `.wt.json`
+
+If we've got a `.wt.json` in a given directory, should we be dynamically
+adding/removing other settings too? Wouldn't profiles also make sense? Take for
+example, the Terminal repo. We've got a PowerShell build environment and a CMD
+one. What if we could drop two profiles in the `.wt.json` file, with the
+`commandline`'s set up to call those scripts as needed?
+
+However, what does that even mean? We wouldn't know that file exists till we see
+it the first time. Maybe there's room to integrate that with Dev Home ala
+[microsoft/DevHome/3005]. Though, that probably makes the most sense as a winget
+DSC to create a fragment profile instead.
+
+## Resources
+
+### Footnotes
+
+[1]: Shell integration would be a strict requirement
+for that parameter to work as intended. Without also enabling shell integration,
+then the Terminal would only send the first line of the script, then wait
+forever for a `FTCS_COMMAND_FINISHED`.
+
+[2]: For your consideration, I made a python script
+that will take the Warp workflow YAML and convert it into json that the Terminal
+can load. Go checkout [`dump_workflows.py`](./dump-workflows.py) to see it. It's
+super straightforward.
+
+
+[Fig]: https://github.com/withfig/autocomplete
+[Warp]: https://www.warp.dev/
+[workflows]: https://docs.warp.dev/features/workflows
+[also working on workflows]: https://fig.io/user-manual/workflows
+[winget script]: https://github.com/microsoft/PowerToys/blob/main/.github/workflows/package-submissions.yml
+[#1595]: https://github.com/microsoft/terminal/issues/1595
+[#7039]: https://github.com/microsoft/terminal/issues/7039
+[#3121]: https://github.com/microsoft/terminal/issues/3121
+[#10436]: https://github.com/microsoft/terminal/issues/10436
+[#12927]: https://github.com/microsoft/terminal/issues/12927
+[#12857]: https://github.com/microsoft/terminal/issues/12857
+[#5790]: https://github.com/microsoft/terminal/issues/5790
+[Notebooks]: ./Markdown%20Notebooks.md
+[Suggestions UI]: ./Suggestions-UI.md
+[#keep]: https://github.com/zadjii/keep
+[VsCode Tasks]: https://github.com/microsoft/terminal/blob/main/.vscode/tasks.json
+
+[#16185]: https://github.com/microsoft/terminal/pull/16185
+[#16513]: https://github.com/microsoft/terminal/pull/16513
+[#12861]: https://github.com/microsoft/terminal/issues/12861
+[#16495]: https://github.com/microsoft/terminal/issues/16495
+[#17376]: https://github.com/microsoft/terminal/pull/17376
+[#10083]: https://github.com/microsoft/terminal/issues/10083
+[#8639]: https://github.com/microsoft/terminal/issues/8639
+
+[microsoft/DevHome/3005]: https://github.com/microsoft/DevHome/issues/3005
diff --git a/doc/specs/#1595 - Suggestions UI/dump-workflows.py b/doc/specs/#1595 - Suggestions UI/dump-workflows.py
new file mode 100644
index 00000000000..789a2aaa416
--- /dev/null
+++ b/doc/specs/#1595 - Suggestions UI/dump-workflows.py
@@ -0,0 +1,45 @@
+import yaml
+import json
+import sys
+import os
+
+def parse_yaml_files(tool, directory):
+ json_data = {}
+ json_data["name"] = f"{tool}..."
+ json_data["commands"] = []
+
+ for filename in os.listdir(directory):
+ if filename.endswith(".yaml") or filename.endswith(".yml"):
+ file_path = os.path.join(directory, filename)
+ with open(file_path, 'r', encoding="utf-8") as file:
+ try:
+ yaml_data = yaml.safe_load(file)
+ new_obj = {}
+ command = {}
+ command["input"] = yaml_data["command"]
+ command["action"] ="sendInput"
+
+ new_obj["command"]=command
+ new_obj["name"] = yaml_data["name"]
+
+ new_obj["description"] = yaml_data["description"] if "description" in yaml_data else ""
+ json_data["commands"].append(new_obj)
+ except yaml.YAMLError as e:
+ print(f"Error parsing {filename}: {e}")
+ sys.exit(-1)
+ return json_data
+
+def main(directory) -> int:
+ json_data = {}
+ json_data["actions"] = []
+
+ for tool_dir in os.listdir(directory):
+ # print(tool_dir)
+ json_data["actions"].append(parse_yaml_files(tool_dir, os.path.join(directory, tool_dir)))
+ print(json.dumps(json_data, indent=4))
+ return 0
+
+if __name__ == '__main__':
+ # Write this output to something like
+ # "%localappdata%\Microsoft\Windows Terminal\Fragments\warp-workflows\actions.json"
+ sys.exit(main("d:\\dev\\public\\workflows\\specs"))
\ No newline at end of file
diff --git a/doc/specs/#1595 - Suggestions UI/img/3121-sxn-menu-2023-000.gif b/doc/specs/#1595 - Suggestions UI/img/3121-sxn-menu-2023-000.gif
new file mode 100644
index 00000000000..da5f0c4f663
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/3121-sxn-menu-2023-000.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/Copilot-in-cmdpal.png b/doc/specs/#1595 - Suggestions UI/img/Copilot-in-cmdpal.png
new file mode 100644
index 00000000000..2fdef06059f
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/Copilot-in-cmdpal.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/GitHub-open-with.png b/doc/specs/#1595 - Suggestions UI/img/GitHub-open-with.png
new file mode 100644
index 00000000000..cc6d484c8c4
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/GitHub-open-with.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/command-history-suggestions.gif b/doc/specs/#1595 - Suggestions UI/img/command-history-suggestions.gif
new file mode 100644
index 00000000000..94e7be1a54e
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/command-history-suggestions.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/developers-already-do-this-000.png b/doc/specs/#1595 - Suggestions UI/img/developers-already-do-this-000.png
new file mode 100644
index 00000000000..728d2ee9661
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/developers-already-do-this-000.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/inline-blocks-000.png b/doc/specs/#1595 - Suggestions UI/img/inline-blocks-000.png
new file mode 100644
index 00000000000..2120143995b
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/inline-blocks-000.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/iterm2-CommandHistory.png b/doc/specs/#1595 - Suggestions UI/img/iterm2-CommandHistory.png
new file mode 100644
index 00000000000..a5308b963b7
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/iterm2-CommandHistory.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/jupyter-notebooks-example.png b/doc/specs/#1595 - Suggestions UI/img/jupyter-notebooks-example.png
new file mode 100644
index 00000000000..9b762bd45ef
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/jupyter-notebooks-example.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/mockup-000.png b/doc/specs/#1595 - Suggestions UI/img/mockup-000.png
new file mode 100644
index 00000000000..3f160371756
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/mockup-000.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/save-command.gif b/doc/specs/#1595 - Suggestions UI/img/save-command.gif
new file mode 100644
index 00000000000..b92e377cc7d
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/save-command.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/shell-autocomplete-jul-2022-000.gif b/doc/specs/#1595 - Suggestions UI/img/shell-autocomplete-jul-2022-000.gif
new file mode 100644
index 00000000000..0a832691b02
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/shell-autocomplete-jul-2022-000.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/shell-completion-menu-2023-02-21.gif b/doc/specs/#1595 - Suggestions UI/img/shell-completion-menu-2023-02-21.gif
new file mode 100644
index 00000000000..a8c32922e07
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/shell-completion-menu-2023-02-21.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/shell-completion-tooltip-000.png b/doc/specs/#1595 - Suggestions UI/img/shell-completion-tooltip-000.png
new file mode 100644
index 00000000000..790adb58cbd
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/shell-completion-tooltip-000.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/tasks-from-cwd.gif b/doc/specs/#1595 - Suggestions UI/img/tasks-from-cwd.gif
new file mode 100644
index 00000000000..2f9f7495bd7
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/tasks-from-cwd.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/tasks-suggestions.gif b/doc/specs/#1595 - Suggestions UI/img/tasks-suggestions.gif
new file mode 100644
index 00000000000..3629dd5a7b7
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/tasks-suggestions.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/vscode-shell-autocomplete-000.gif b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-autocomplete-000.gif
new file mode 100644
index 00000000000..93f76dc0d2e
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-autocomplete-000.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/vscode-shell-integration-gutter-mark.png b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-integration-gutter-mark.png
new file mode 100644
index 00000000000..d73a5c7b14a
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-integration-gutter-mark.png differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/vscode-shell-suggestions.gif b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-suggestions.gif
new file mode 100644
index 00000000000..93f76dc0d2e
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/vscode-shell-suggestions.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/vscode-tasks-000.gif b/doc/specs/#1595 - Suggestions UI/img/vscode-tasks-000.gif
new file mode 100644
index 00000000000..b7ad4fe76c8
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/vscode-tasks-000.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/warp-workflows-000.gif b/doc/specs/#1595 - Suggestions UI/img/warp-workflows-000.gif
new file mode 100644
index 00000000000..44763f08f65
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/warp-workflows-000.gif differ
diff --git a/doc/specs/#1595 - Suggestions UI/img/warp-workflows-001.gif b/doc/specs/#1595 - Suggestions UI/img/warp-workflows-001.gif
new file mode 100644
index 00000000000..a770263df0d
Binary files /dev/null and b/doc/specs/#1595 - Suggestions UI/img/warp-workflows-001.gif differ
diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp
index 03570cf526d..3aa6e8b3128 100644
--- a/src/buffer/out/textBuffer.cpp
+++ b/src/buffer/out/textBuffer.cpp
@@ -48,7 +48,7 @@ TextBuffer::TextBuffer(til::size screenBufferSize,
const TextAttribute defaultAttributes,
const UINT cursorSize,
const bool isActiveBuffer,
- Microsoft::Console::Render::Renderer& renderer) :
+ Microsoft::Console::Render::Renderer* renderer) :
_renderer{ renderer },
_currentAttributes{ defaultAttributes },
// This way every TextBuffer will start with a ""unique"" _lastMutationId
@@ -373,38 +373,6 @@ TextBufferCellIterator TextBuffer::GetCellDataAt(const til::point at, const View
return TextBufferCellIterator(*this, at, limit);
}
-//Routine Description:
-// - Call before inserting a character into the buffer.
-// - This will ensure a consistent double byte state (KAttrs line) within the text buffer
-// - It will attempt to correct the buffer if we're inserting an unexpected double byte character type
-// and it will pad out the buffer if we're going to split a double byte sequence across two rows.
-//Arguments:
-// - dbcsAttribute - Double byte information associated with the character about to be inserted into the buffer
-//Return Value:
-// - true if we successfully prepared the buffer and moved the cursor
-// - false otherwise (out of memory)
-void TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute)
-{
- // Now compensate if we don't have enough space for the upcoming double byte sequence
- // We only need to compensate for leading bytes
- if (dbcsAttribute == DbcsAttribute::Leading)
- {
- const auto cursorPosition = GetCursor().GetPosition();
- const auto lineWidth = GetLineWidth(cursorPosition.y);
-
- // If we're about to lead on the last column in the row, we need to add a padding space
- if (cursorPosition.x == lineWidth - 1)
- {
- // set that we're wrapping for double byte reasons
- auto& row = GetMutableRowByOffset(cursorPosition.y);
- row.SetDoubleBytePadded(true);
-
- // then move the cursor forward and onto the next row
- IncrementCursor();
- }
- }
-}
-
// Given the character offset `position` in the `chars` string, this function returns the starting position of the next grapheme.
// For instance, given a `chars` of L"x\uD83D\uDE42y" and a `position` of 1 it'll return 3.
// GraphemePrev would do the exact inverse of this operation.
@@ -740,144 +708,6 @@ OutputCellIterator TextBuffer::WriteLine(const OutputCellIterator givenIt,
return newIt;
}
-//Routine Description:
-// - Inserts one codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
-//Arguments:
-// - chars - The codepoint to insert
-// - dbcsAttribute - Double byte information associated with the codepoint
-// - bAttr - Color data associated with the character
-//Return Value:
-// - true if we successfully inserted the character
-// - false otherwise (out of memory)
-void TextBuffer::InsertCharacter(const std::wstring_view chars,
- const DbcsAttribute dbcsAttribute,
- const TextAttribute attr)
-{
- // Ensure consistent buffer state for double byte characters based on the character type we're about to insert
- _PrepareForDoubleByteSequence(dbcsAttribute);
-
- // Get the current cursor position
- const auto iRow = GetCursor().GetPosition().y; // row stored as logical position, not array position
- const auto iCol = GetCursor().GetPosition().x; // column logical and array positions are equal.
-
- // Get the row associated with the given logical position
- auto& Row = GetMutableRowByOffset(iRow);
-
- // Store character and double byte data
- switch (dbcsAttribute)
- {
- case DbcsAttribute::Leading:
- Row.ReplaceCharacters(iCol, 2, chars);
- break;
- case DbcsAttribute::Trailing:
- Row.ReplaceCharacters(iCol - 1, 2, chars);
- break;
- default:
- Row.ReplaceCharacters(iCol, 1, chars);
- break;
- }
-
- // Store color data
- Row.SetAttrToEnd(iCol, attr);
- IncrementCursor();
-}
-
-//Routine Description:
-// - Inserts one ucs2 codepoint into the buffer at the current cursor position and advances the cursor as appropriate.
-//Arguments:
-// - wch - The codepoint to insert
-// - dbcsAttribute - Double byte information associated with the codepoint
-// - bAttr - Color data associated with the character
-//Return Value:
-// - true if we successfully inserted the character
-// - false otherwise (out of memory)
-void TextBuffer::InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr)
-{
- InsertCharacter({ &wch, 1 }, dbcsAttribute, attr);
-}
-
-//Routine Description:
-// - Finds the current row in the buffer (as indicated by the cursor position)
-// and specifies that we have forced a line wrap on that row
-//Arguments:
-// - - Always sets to wrap
-//Return Value:
-// -
-void TextBuffer::_SetWrapOnCurrentRow()
-{
- _AdjustWrapOnCurrentRow(true);
-}
-
-//Routine Description:
-// - Finds the current row in the buffer (as indicated by the cursor position)
-// and specifies whether or not it should have a line wrap flag.
-//Arguments:
-// - fSet - True if this row has a wrap. False otherwise.
-//Return Value:
-// -
-void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet)
-{
- // The vertical position of the cursor represents the current row we're manipulating.
- const auto uiCurrentRowOffset = GetCursor().GetPosition().y;
-
- // Set the wrap status as appropriate
- GetMutableRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
-}
-
-//Routine Description:
-// - Increments the cursor one position in the buffer as if text is being typed into the buffer.
-// - NOTE: Will introduce a wrap marker if we run off the end of the current row
-//Arguments:
-// -
-//Return Value:
-// - true if we successfully moved the cursor.
-// - false otherwise (out of memory)
-void TextBuffer::IncrementCursor()
-{
- // Cursor position is stored as logical array indices (starts at 0) for the window
- // Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
- // So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
- const auto iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().y) - 1;
-
- // Move the cursor one position to the right
- GetCursor().IncrementXPosition(1);
-
- // If we've passed the final valid column...
- if (GetCursor().GetPosition().x > iFinalColumnIndex)
- {
- // Then mark that we've been forced to wrap
- _SetWrapOnCurrentRow();
-
- // Then move the cursor to a new line
- NewlineCursor();
- }
-}
-
-//Routine Description:
-// - Increments the cursor one line down in the buffer and to the beginning of the line
-//Arguments:
-// -
-//Return Value:
-// - true if we successfully moved the cursor.
-void TextBuffer::NewlineCursor()
-{
- const auto iFinalRowIndex = GetSize().BottomInclusive();
-
- // Reset the cursor position to 0 and move down one line
- GetCursor().SetXPosition(0);
- GetCursor().IncrementYPosition(1);
-
- // If we've passed the final valid row...
- if (GetCursor().GetPosition().y > iFinalRowIndex)
- {
- // Stay on the final logical/offset row of the buffer.
- GetCursor().SetYPosition(iFinalRowIndex);
-
- // Instead increment the circular buffer to move us into the "oldest" row of the backing buffer
- IncrementCircularBuffer();
- }
-}
-
//Routine Description:
// - Increments the circular buffer by one. Circular buffer is represented by FirstRow variable.
//Arguments:
@@ -888,9 +718,9 @@ void TextBuffer::IncrementCircularBuffer(const TextAttribute& fillAttributes)
{
// FirstRow is at any given point in time the array index in the circular buffer that corresponds
// to the logical position 0 in the window (cursor coordinates and all other coordinates).
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerFlush(true);
+ _renderer->TriggerFlush(true);
}
// Prune hyperlinks to delete obsolete references
@@ -954,38 +784,6 @@ til::point TextBuffer::GetLastNonSpaceCharacter(const Viewport* viewOptional) co
return coordEndOfText;
}
-// Routine Description:
-// - Retrieves the position of the previous character relative to the current cursor position
-// Arguments:
-// -
-// Return Value:
-// - Coordinate position in screen coordinates of the character just before the cursor.
-// - NOTE: Will return 0,0 if already in the top left corner
-til::point TextBuffer::_GetPreviousFromCursor() const
-{
- auto coordPosition = GetCursor().GetPosition();
-
- // If we're not at the left edge, simply move the cursor to the left by one
- if (coordPosition.x > 0)
- {
- coordPosition.x--;
- }
- else
- {
- // Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
- if (coordPosition.y > 0)
- {
- // move the cursor up one line
- coordPosition.y--;
-
- // and to the right edge
- coordPosition.x = GetLineWidth(coordPosition.y) - 1;
- }
- }
-
- return coordPosition;
-}
-
const til::CoordType TextBuffer::GetFirstRowIndex() const noexcept
{
return _firstRow;
@@ -1265,56 +1063,56 @@ bool TextBuffer::IsActiveBuffer() const noexcept
return _isActiveBuffer;
}
-Microsoft::Console::Render::Renderer& TextBuffer::GetRenderer() noexcept
+Microsoft::Console::Render::Renderer* TextBuffer::GetRenderer() noexcept
{
return _renderer;
}
void TextBuffer::NotifyPaintFrame() noexcept
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.NotifyPaintFrame();
+ _renderer->NotifyPaintFrame();
}
}
void TextBuffer::TriggerRedraw(const Viewport& viewport)
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerRedraw(viewport);
+ _renderer->TriggerRedraw(viewport);
}
}
void TextBuffer::TriggerRedrawAll()
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerRedrawAll();
+ _renderer->TriggerRedrawAll();
}
}
void TextBuffer::TriggerScroll()
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerScroll();
+ _renderer->TriggerScroll();
}
}
void TextBuffer::TriggerScroll(const til::point delta)
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerScroll(&delta);
+ _renderer->TriggerScroll(&delta);
}
}
void TextBuffer::TriggerNewTextNotification(const std::wstring_view newText)
{
- if (_isActiveBuffer)
+ if (_isActiveBuffer && _renderer)
{
- _renderer.TriggerNewTextNotification(newText);
+ _renderer->TriggerNewTextNotification(newText);
}
}
diff --git a/src/buffer/out/textBuffer.hpp b/src/buffer/out/textBuffer.hpp
index 6f21ad41cb4..7d3a0074297 100644
--- a/src/buffer/out/textBuffer.hpp
+++ b/src/buffer/out/textBuffer.hpp
@@ -72,7 +72,7 @@ class TextBuffer final
const TextAttribute defaultAttributes,
const UINT cursorSize,
const bool isActiveBuffer,
- Microsoft::Console::Render::Renderer& renderer);
+ Microsoft::Console::Render::Renderer* renderer);
TextBuffer(const TextBuffer&) = delete;
TextBuffer(TextBuffer&&) = delete;
@@ -121,11 +121,6 @@ class TextBuffer final
const std::optional setWrap = std::nullopt,
const std::optional limitRight = std::nullopt);
- void InsertCharacter(const wchar_t wch, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
- void InsertCharacter(const std::wstring_view chars, const DbcsAttribute dbcsAttribute, const TextAttribute attr);
- void IncrementCursor();
- void NewlineCursor();
-
// Scroll needs access to this to quickly rotate around the buffer.
void IncrementCircularBuffer(const TextAttribute& fillAttributes = {});
@@ -166,7 +161,7 @@ class TextBuffer final
void SetAsActiveBuffer(const bool isActiveBuffer) noexcept;
bool IsActiveBuffer() const noexcept;
- Microsoft::Console::Render::Renderer& GetRenderer() noexcept;
+ Microsoft::Console::Render::Renderer* GetRenderer() noexcept;
void NotifyPaintFrame() noexcept;
void TriggerRedraw(const Microsoft::Console::Types::Viewport& viewport);
@@ -322,11 +317,6 @@ class TextBuffer final
til::CoordType _estimateOffsetOfLastCommittedRow() const noexcept;
void _SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept;
- til::point _GetPreviousFromCursor() const;
- void _SetWrapOnCurrentRow();
- void _AdjustWrapOnCurrentRow(const bool fSet);
- // Assist with maintaining proper buffer state for Double Byte character sequences
- void _PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute);
void _ExpandTextRow(til::inclusive_rect& selectionRow) const;
DelimiterClass _GetDelimiterClassAt(const til::point pos, const std::wstring_view wordDelimiters) const;
til::point _GetWordStartForAccessibility(const til::point target, const std::wstring_view wordDelimiters) const;
@@ -343,7 +333,7 @@ class TextBuffer final
static void _AppendRTFText(std::string& contentBuilder, const std::wstring_view& text);
- Microsoft::Console::Render::Renderer& _renderer;
+ Microsoft::Console::Render::Renderer* _renderer = nullptr;
std::unordered_map _hyperlinkMap;
std::unordered_map _hyperlinkCustomIdMap;
diff --git a/src/buffer/out/ut_textbuffer/ReflowTests.cpp b/src/buffer/out/ut_textbuffer/ReflowTests.cpp
index d1fad4c407f..191d7329b99 100644
--- a/src/buffer/out/ut_textbuffer/ReflowTests.cpp
+++ b/src/buffer/out/ut_textbuffer/ReflowTests.cpp
@@ -699,7 +699,7 @@ class ReflowTests
static DummyRenderer renderer;
static std::unique_ptr _textBufferFromTestBuffer(const TestBuffer& testBuffer)
{
- auto buffer = std::make_unique(testBuffer.size, TextAttribute{ 0x7 }, 0, false, renderer);
+ auto buffer = std::make_unique(testBuffer.size, TextAttribute{ 0x7 }, 0, false, &renderer);
til::CoordType y = 0;
for (const auto& testRow : testBuffer.rows)
@@ -725,7 +725,7 @@ class ReflowTests
static std::unique_ptr _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const til::size newSize)
{
- auto buffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, renderer);
+ auto buffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, &renderer);
TextBuffer::Reflow(originalBuffer, *buffer);
return buffer;
}
diff --git a/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp b/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
index 24d04a56ff8..be9e941c757 100644
--- a/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
+++ b/src/buffer/out/ut_textbuffer/UTextAdapterTests.cpp
@@ -37,7 +37,7 @@ class UTextAdapterTests
TEST_METHOD(Unicode)
{
DummyRenderer renderer;
- TextBuffer buffer{ til::size{ 24, 1 }, TextAttribute{}, 0, false, renderer };
+ TextBuffer buffer{ til::size{ 24, 1 }, TextAttribute{}, 0, false, &renderer };
RowWriteState state{
.text = L"abc ๐ถ๐ท๐ธ abc ใใณใกใใ",
diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp
index 92d0c153c8e..eb153ce1ed8 100644
--- a/src/cascadia/TerminalApp/Pane.cpp
+++ b/src/cascadia/TerminalApp/Pane.cpp
@@ -1711,7 +1711,9 @@ void Pane::_SetupChildCloseHandlers()
IPaneContent Pane::_takePaneContent()
{
_closeRequestedRevoker.revoke();
- return std::move(_content);
+ // we cannot return std::move(_content) because we don't want _content to be null,
+ // since _content gets accessed even after Close is called
+ return _content;
}
// This method safely sets the content of the Pane. It'll ensure to revoke and
@@ -1721,15 +1723,14 @@ void Pane::_setPaneContent(IPaneContent content)
{
// The IPaneContent::Close() implementation may be buggy and raise the CloseRequested event again.
// _takePaneContent() avoids this as it revokes the event handler.
- if (const auto c = _takePaneContent())
+ if (_takePaneContent())
{
- c.Close();
+ _content.Close();
}
- _content = std::move(content);
-
- if (_content)
+ if (content)
{
+ _content = std::move(content);
_closeRequestedRevoker = _content.CloseRequested(winrt::auto_revoke, [this](auto&&, auto&&) { Close(); });
}
}
diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp
index ceec02ed52a..146fb99b681 100644
--- a/src/cascadia/TerminalControl/ControlCore.cpp
+++ b/src/cascadia/TerminalControl/ControlCore.cpp
@@ -1894,7 +1894,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto lock = _terminal->LockForWriting();
auto& renderSettings = _terminal->GetRenderSettings();
- renderSettings.ToggleBlinkRendition(*_renderer);
+ renderSettings.ToggleBlinkRendition(_renderer.get());
}
void ControlCore::BlinkCursor()
diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
index 54c6a4643b5..d618171e58b 100644
--- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw
@@ -207,7 +207,8 @@ Please either install the missing font or choose another one.
{0} is a file name
- Unable to compile the specified pixel shader.
+ Pixel shader failed to compile: {0}
+ {0} is the error message generated by the compiler
Renderer encountered an unexpected error: {0}
@@ -223,7 +224,7 @@ Please either install the missing font or choose another one.
Renderer encountered an unexpected error: {0:#010x} {1}
- {Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.
+ {Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message. {2} is the filename.
Read-only mode is enabled.
@@ -320,4 +321,4 @@ Please either install the missing font or choose another one.
Suggested input: {0}
{Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input
-
+
\ No newline at end of file
diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp
index 1fffe66dc50..bc045f36381 100644
--- a/src/cascadia/TerminalControl/TermControl.cpp
+++ b/src/cascadia/TerminalControl/TermControl.cpp
@@ -1162,7 +1162,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") }, parameter) };
break;
case D2DERR_SHADER_COMPILE_FAILED:
- message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) };
+ message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }, parameter) };
break;
case DWRITE_E_NOFONT:
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorFontNotFound") }, parameter) };
@@ -1175,7 +1175,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
wchar_t buf[512];
const auto len = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &buf[0], ARRAYSIZE(buf), nullptr);
const std::wstring_view msg{ &buf[0], len };
- message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorOther") }, hr, msg) };
+ std::wstring resourceString = RS_(L"RendererErrorOther").c_str();
+ //conditional message construction
+ std::wstring partialMessage = fmt::format(std::wstring_view{ resourceString }, hr, msg);
+ if (!parameter.empty())
+ {
+ fmt::format_to(std::back_inserter(partialMessage), LR"( "{0}")", parameter);
+ }
+ message = winrt::hstring{ partialMessage };
break;
}
}
diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp
index 05f9a681d84..c1b0436797d 100644
--- a/src/cascadia/TerminalCore/Terminal.cpp
+++ b/src/cascadia/TerminalCore/Terminal.cpp
@@ -47,9 +47,9 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re
Utils::ClampToShortMax(viewportSize.height + scrollbackLines, 1) };
const TextAttribute attr{};
const UINT cursorSize = 12;
- _mainBuffer = std::make_unique(bufferSize, attr, cursorSize, true, renderer);
+ _mainBuffer = std::make_unique(bufferSize, attr, cursorSize, true, &renderer);
- auto dispatch = std::make_unique(*this, renderer, _renderSettings, _terminalInput);
+ auto dispatch = std::make_unique(*this, &renderer, _renderSettings, _terminalInput);
auto engine = std::make_unique(std::move(dispatch));
_stateMachine = std::make_unique(std::move(engine));
diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
index 2ac6770cab4..a18db5349a4 100644
--- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw
@@ -931,7 +931,7 @@
The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.
- When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font. This feature is unavailable when using Direct2D as the Graphics API.
+ When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font.
A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.
diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp
index daaedf87d7a..d77a916ab80 100644
--- a/src/host/screenInfo.cpp
+++ b/src/host/screenInfo.cpp
@@ -117,7 +117,7 @@ SCREEN_INFORMATION::~SCREEN_INFORMATION()
defaultAttributes,
uiCursorSize,
pScreen->IsActiveScreenBuffer(),
- *ServiceLocator::LocateGlobals().pRender);
+ ServiceLocator::LocateGlobals().pRender);
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
pScreen->_textBuffer->GetCursor().SetType(gci.GetCursorType());
@@ -253,10 +253,9 @@ void SCREEN_INFORMATION::s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pSc
{
auto& g = ServiceLocator::LocateGlobals();
auto& gci = g.getConsoleInformation();
- auto& renderer = *g.pRender;
auto& renderSettings = gci.GetRenderSettings();
auto& terminalInput = gci.GetActiveInputBuffer()->GetTerminalInput();
- auto adapter = std::make_unique(_api, renderer, renderSettings, terminalInput);
+ auto adapter = std::make_unique(_api, g.pRender, renderSettings, terminalInput);
auto engine = std::make_unique(std::move(adapter));
// Note that at this point in the setup, we haven't determined if we're
// in VtIo mode or not yet. We'll set the OutputStateMachine's
diff --git a/src/host/ut_host/TextBufferTests.cpp b/src/host/ut_host/TextBufferTests.cpp
index f70f5c6b8ec..d39be844de6 100644
--- a/src/host/ut_host/TextBufferTests.cpp
+++ b/src/host/ut_host/TextBufferTests.cpp
@@ -94,18 +94,10 @@ class TextBufferTests
TEST_METHOD(TestCopyProperties);
- TEST_METHOD(TestInsertCharacter);
-
- TEST_METHOD(TestIncrementCursor);
-
- TEST_METHOD(TestNewlineCursor);
-
void TestLastNonSpace(const til::CoordType cursorPosY);
TEST_METHOD(TestGetLastNonSpaceCharacter);
- TEST_METHOD(TestSetWrapOnCurrentRow);
-
TEST_METHOD(TestIncrementCircularBuffer);
TEST_METHOD(TestMixedRgbAndLegacyForeground);
@@ -145,7 +137,6 @@ class TextBufferTests
TEST_METHOD(ResizeTraditionalHighUnicodeRowRemoval);
TEST_METHOD(ResizeTraditionalHighUnicodeColumnRemoval);
- TEST_METHOD(TestBurrito);
TEST_METHOD(TestOverwriteChars);
TEST_METHOD(TestReplace);
TEST_METHOD(TestInsert);
@@ -400,129 +391,6 @@ void TextBufferTests::TestCopyProperties()
VERIFY_IS_TRUE(testTextBuffer->GetCursor().GetDelay());
}
-void TextBufferTests::TestInsertCharacter()
-{
- auto& textBuffer = GetTbi();
-
- // get starting cursor position
- const auto coordCursorBefore = textBuffer.GetCursor().GetPosition();
-
- // Get current row from the buffer
- auto& Row = textBuffer.GetRowByOffset(coordCursorBefore.y);
-
- // create some sample test data
- const auto wch = L'Z';
- const std::wstring_view wchTest(&wch, 1);
- const auto dbcsAttribute = DbcsAttribute::Leading;
- const auto wAttrTest = BACKGROUND_INTENSITY | FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE;
- auto TestAttributes = TextAttribute(wAttrTest);
-
- // ensure that the buffer didn't start with these fields
- VERIFY_ARE_NOT_EQUAL(Row.GlyphAt(coordCursorBefore.x), wchTest);
- VERIFY_ARE_NOT_EQUAL(Row.DbcsAttrAt(coordCursorBefore.x), dbcsAttribute);
-
- auto attr = Row.GetAttrByColumn(coordCursorBefore.x);
-
- VERIFY_ARE_NOT_EQUAL(attr, TestAttributes);
-
- // now apply the new data to the buffer
- textBuffer.InsertCharacter(wchTest, dbcsAttribute, TestAttributes);
-
- // ensure that the buffer position where the cursor WAS contains the test items
- VERIFY_ARE_EQUAL(Row.GlyphAt(coordCursorBefore.x), wchTest);
- VERIFY_ARE_EQUAL(Row.DbcsAttrAt(coordCursorBefore.x), dbcsAttribute);
-
- attr = Row.GetAttrByColumn(coordCursorBefore.x);
- VERIFY_ARE_EQUAL(attr, TestAttributes);
-
- // ensure that the cursor moved to a new position (X or Y or both have changed)
- VERIFY_IS_TRUE((coordCursorBefore.x != textBuffer.GetCursor().GetPosition().x) ||
- (coordCursorBefore.y != textBuffer.GetCursor().GetPosition().y));
- // the proper advancement of the cursor (e.g. which position it goes to) is validated in other tests
-}
-
-void TextBufferTests::TestIncrementCursor()
-{
- auto& textBuffer = GetTbi();
-
- // only checking X increments here
- // Y increments are covered in the NewlineCursor test
-
- const auto sBufferWidth = textBuffer.GetSize().Width();
-
- const auto sBufferHeight = textBuffer.GetSize().Height();
- VERIFY_IS_TRUE(sBufferWidth > 1 && sBufferHeight > 1);
-
- Log::Comment(L"Test normal case of moving once to the right within a single line");
- textBuffer.GetCursor().SetXPosition(0);
- textBuffer.GetCursor().SetYPosition(0);
-
- auto coordCursorBefore = textBuffer.GetCursor().GetPosition();
-
- textBuffer.IncrementCursor();
-
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().x, 1); // X should advance by 1
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().y, coordCursorBefore.y); // Y shouldn't have moved
-
- Log::Comment(L"Test line wrap case where cursor is on the right edge of the line");
- textBuffer.GetCursor().SetXPosition(sBufferWidth - 1);
- textBuffer.GetCursor().SetYPosition(0);
-
- coordCursorBefore = textBuffer.GetCursor().GetPosition();
-
- textBuffer.IncrementCursor();
-
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().x, 0); // position should be reset to the left edge when passing right edge
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().y - 1, coordCursorBefore.y); // the cursor should be moved one row down from where it used to be
-}
-
-void TextBufferTests::TestNewlineCursor()
-{
- auto& textBuffer = GetTbi();
-
- const auto sBufferHeight = textBuffer.GetSize().Height();
-
- const auto sBufferWidth = textBuffer.GetSize().Width();
- // width and height are sufficiently large for upcoming math
- VERIFY_IS_TRUE(sBufferWidth > 4 && sBufferHeight > 4);
-
- Log::Comment(L"Verify standard row increment from somewhere in the buffer");
-
- // set cursor X position to non zero, any position in buffer
- textBuffer.GetCursor().SetXPosition(3);
-
- // set cursor Y position to not-the-final row in the buffer
- textBuffer.GetCursor().SetYPosition(3);
-
- auto coordCursorBefore = textBuffer.GetCursor().GetPosition();
-
- // perform operation
- textBuffer.NewlineCursor();
-
- // verify
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().x, 0); // move to left edge of buffer
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().y, coordCursorBefore.y + 1); // move down one row
-
- Log::Comment(L"Verify increment when already on last row of buffer");
-
- // X position still doesn't matter
- textBuffer.GetCursor().SetXPosition(3);
-
- // Y position needs to be on the last row of the buffer
- textBuffer.GetCursor().SetYPosition(sBufferHeight - 1);
-
- coordCursorBefore = textBuffer.GetCursor().GetPosition();
-
- // perform operation
- textBuffer.NewlineCursor();
-
- // verify
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().x, 0); // move to left edge
- VERIFY_ARE_EQUAL(textBuffer.GetCursor().GetPosition().y, coordCursorBefore.y); // cursor Y position should not have moved. stays on same logical final line of buffer
-
- // This is okay because the backing circular buffer changes, not the logical screen position (final visible line of the buffer)
-}
-
void TextBufferTests::TestLastNonSpace(const til::CoordType cursorPosY)
{
auto& textBuffer = GetTbi();
@@ -568,37 +436,6 @@ void TextBufferTests::TestGetLastNonSpaceCharacter()
TestLastNonSpace(14);
}
-void TextBufferTests::TestSetWrapOnCurrentRow()
-{
- auto& textBuffer = GetTbi();
-
- auto sCurrentRow = textBuffer.GetCursor().GetPosition().y;
-
- auto& Row = textBuffer.GetMutableRowByOffset(sCurrentRow);
-
- Log::Comment(L"Testing off to on");
-
- // turn wrap status off first
- Row.SetWrapForced(false);
-
- // trigger wrap
- textBuffer._SetWrapOnCurrentRow();
-
- // ensure this row was flipped
- VERIFY_IS_TRUE(Row.WasWrapForced());
-
- Log::Comment(L"Testing on stays on");
-
- // make sure wrap status is on
- Row.SetWrapForced(true);
-
- // trigger wrap
- textBuffer._SetWrapOnCurrentRow();
-
- // ensure row is still on
- VERIFY_IS_TRUE(Row.WasWrapForced());
-}
-
void TextBufferTests::TestIncrementCircularBuffer()
{
auto& textBuffer = GetTbi();
@@ -1705,7 +1542,7 @@ void TextBufferTests::ResizeTraditional()
const til::size smallSize = { 5, 5 };
const TextAttribute defaultAttr(0);
- TextBuffer buffer(smallSize, defaultAttr, 12, false, _renderer);
+ TextBuffer buffer(smallSize, defaultAttr, 12, false, &_renderer);
Log::Comment(L"Fill buffer with some data and do assorted resize operations.");
@@ -1801,7 +1638,7 @@ void TextBufferTests::ResizeTraditionalRotationPreservesHighUnicode()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Get a position inside the buffer
const til::point pos{ 2, 1 };
@@ -1842,7 +1679,7 @@ void TextBufferTests::ScrollBufferRotationPreservesHighUnicode()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Get a position inside the buffer
const til::point pos{ 2, 1 };
@@ -1877,7 +1714,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeRowRemoval()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Get a position inside the buffer in the bottom row
const til::point pos{ 0, bufferSize.height - 1 };
@@ -1907,7 +1744,7 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Get a position inside the buffer in the last column (-2 as the inserted character is 2 columns wide).
const til::point pos{ bufferSize.width - 2, 0 };
@@ -1929,33 +1766,12 @@ void TextBufferTests::ResizeTraditionalHighUnicodeColumnRemoval()
_buffer->ResizeTraditional(trimmedBufferSize);
}
-void TextBufferTests::TestBurrito()
-{
- til::size bufferSize{ 80, 9001 };
- UINT cursorSize = 12;
- TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
-
- // This is the burrito emoji: ๐ฏ
- // It's encoded in UTF-16, as needed by the buffer.
- const auto burrito = L"\xD83C\xDF2F";
- OutputCellIterator burriter{ burrito };
-
- auto afterFIter = _buffer->Write({ L"F" });
- _buffer->IncrementCursor();
-
- auto afterBurritoIter = _buffer->Write(burriter);
- _buffer->IncrementCursor();
- _buffer->IncrementCursor();
- VERIFY_IS_FALSE(afterBurritoIter);
-}
-
void TextBufferTests::TestOverwriteChars()
{
til::size bufferSize{ 10, 3 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer };
+ TextBuffer buffer{ bufferSize, attr, cursorSize, false, &_renderer };
auto& row = buffer.GetMutableRowByOffset(0);
// scientist emoji U+1F9D1 U+200D U+1F52C
@@ -2011,7 +1827,7 @@ void TextBufferTests::TestReplace()
static constexpr til::size bufferSize{ 10, 3 };
static constexpr UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- TextBuffer buffer{ bufferSize, attr, cursorSize, false, _renderer };
+ TextBuffer buffer{ bufferSize, attr, cursorSize, false, &_renderer };
#define complex L"\U0001F41B"
@@ -2093,7 +1909,7 @@ void TextBufferTests::TestInsert()
static constexpr TextAttribute attr1{ 0x11111111, 0x00000000 };
static constexpr TextAttribute attr2{ 0x22222222, 0x00000000 };
static constexpr TextAttribute attr3{ 0x33333333, 0x00000000 };
- TextBuffer buffer{ bufferSize, attr1, cursorSize, false, _renderer };
+ TextBuffer buffer{ bufferSize, attr1, cursorSize, false, &_renderer };
struct Test
{
@@ -2232,7 +2048,7 @@ void TextBufferTests::GetWordBoundaries()
til::size bufferSize{ 80, 9001 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Setup: Write lines of text to the buffer
const std::vector text = { L"word other",
@@ -2448,7 +2264,7 @@ void TextBufferTests::MoveByWord()
til::size bufferSize{ 80, 9001 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Setup: Write lines of text to the buffer
const std::vector text = { L"word other",
@@ -2555,7 +2371,7 @@ void TextBufferTests::GetGlyphBoundaries()
til::size bufferSize{ 10, 10 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// This is the burrito emoji: ๐ฏ
// It's encoded in UTF-16, as needed by the buffer.
@@ -2591,7 +2407,7 @@ void TextBufferTests::GetTextRects()
til::size bufferSize{ 20, 50 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Setup: Write lines of text to the buffer
const std::vector text = { L"0123456789",
@@ -2671,7 +2487,7 @@ void TextBufferTests::GetPlainText()
til::size bufferSize{ 10, 20 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Setup: Write lines of text to the buffer
const std::vector bufferText = { L"12345",
@@ -2759,7 +2575,7 @@ void TextBufferTests::GetPlainText()
til::size bufferSize{ 5, 20 };
UINT cursorSize = 12;
TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
// Setup: Write lines of text to the buffer
const std::vector bufferText = { L"1234567",
@@ -2888,7 +2704,7 @@ void TextBufferTests::HyperlinkTrim()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
static constexpr std::wstring_view url{ L"test.url" };
static constexpr std::wstring_view otherUrl{ L"other.url" };
@@ -2934,7 +2750,7 @@ void TextBufferTests::NoHyperlinkTrim()
const til::size bufferSize{ 80, 10 };
const UINT cursorSize = 12;
const TextAttribute attr{ 0x7f };
- auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, _renderer);
+ auto _buffer = std::make_unique(bufferSize, attr, cursorSize, false, &_renderer);
static constexpr std::wstring_view url{ L"test.url" };
static constexpr std::wstring_view customId{ L"CustomId" };
@@ -3086,7 +2902,7 @@ void TextBufferTests::ReflowPromptRegions()
// After we resize, make sure to get the new textBuffers
til::size newSize{ oldSize.Width() + dx, oldSize.Height() };
- auto newBuffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, _renderer);
+ auto newBuffer = std::make_unique(newSize, TextAttribute{ 0x7 }, 0, false, &_renderer);
TextBuffer::Reflow(*tbi, *newBuffer);
Log::Comment(L"========== Checking the host buffer state (after) ==========");
diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp
index b6e8ad153be..4c002700469 100644
--- a/src/inc/test/CommonState.hpp
+++ b/src/inc/test/CommonState.hpp
@@ -188,7 +188,7 @@ class CommonState
initialAttributes,
uiCursorSize,
true,
- *g.pRender);
+ g.pRender);
if (textBuffer.get() == nullptr)
{
m_hrTextBufferInfo = E_OUTOFMEMORY;
diff --git a/src/renderer/atlas/colorbrewer.h b/src/inc/til/colorbrewer.h
similarity index 79%
rename from src/renderer/atlas/colorbrewer.h
rename to src/inc/til/colorbrewer.h
index be5cef6b8a6..5f22f972f86 100644
--- a/src/renderer/atlas/colorbrewer.h
+++ b/src/inc/til/colorbrewer.h
@@ -3,7 +3,7 @@
#pragma once
-namespace Microsoft::Console::Render::Atlas::colorbrewer
+namespace til::colorbrewer
{
// The following list of colors is only used as a debug aid and not part of the final product.
// They're licensed under:
@@ -22,7 +22,7 @@ namespace Microsoft::Console::Render::Atlas::colorbrewer
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
- inline constexpr u32 pastel1[]{
+ inline constexpr uint32_t pastel1[]{
0xfbb4ae,
0xb3cde3,
0xccebc5,
@@ -33,4 +33,15 @@ namespace Microsoft::Console::Render::Atlas::colorbrewer
0xfddaec,
0xf2f2f2,
};
+
+ inline constexpr uint32_t dark2[]{
+ 0x1b9e77,
+ 0xd95f02,
+ 0x7570b3,
+ 0xe7298a,
+ 0x66a61e,
+ 0xe6ab02,
+ 0xa6761d,
+ 0x666666,
+ };
}
diff --git a/src/renderer/atlas/AtlasEngine.r.cpp b/src/renderer/atlas/AtlasEngine.r.cpp
index 3591abe7905..ca665228345 100644
--- a/src/renderer/atlas/AtlasEngine.r.cpp
+++ b/src/renderer/atlas/AtlasEngine.r.cpp
@@ -465,6 +465,7 @@ void AtlasEngine::_present()
return;
}
+#pragma warning(suppress : 4127) // conditional expression is constant
if (!ATLAS_DEBUG_SHOW_DIRTY && !_p.s->target->disablePresent1 && memcmp(&dirtyRect, &fullRect, sizeof(RECT)) != 0)
{
params.DirtyRectsCount = 1;
diff --git a/src/renderer/atlas/BackendD2D.cpp b/src/renderer/atlas/BackendD2D.cpp
index cf7bd8a2bec..c8455cfdd92 100644
--- a/src/renderer/atlas/BackendD2D.cpp
+++ b/src/renderer/atlas/BackendD2D.cpp
@@ -7,7 +7,7 @@
#include
#if ATLAS_DEBUG_SHOW_DIRTY
-#include "colorbrewer.h"
+#include
#endif
#if ATLAS_DEBUG_DUMP_RENDER_TARGET
@@ -394,8 +394,14 @@ void BackendD2D::_prepareBuiltinGlyphRenderTarget(const RenderingPayload& p)
THROW_IF_FAILED(target->GetBitmap(_builtinGlyphsBitmap.put()));
_builtinGlyphsRenderTarget = target.query();
_builtinGlyphsBitmapCellCountU = cellCountU;
- _builtinGlyphsRenderTargetActive = false;
memset(&_builtinGlyphsReady[0], 0, sizeof(_builtinGlyphsReady));
+
+ _builtinGlyphsRenderTarget->BeginDraw();
+ _builtinGlyphsRenderTargetActive = true;
+
+ // The initial contents of the bitmap are undefined.
+ // -> We need to define them. :)
+ _builtinGlyphsRenderTarget->Clear();
}
D2D1_RECT_U BackendD2D::_prepareBuiltinGlyph(const RenderingPayload& p, char32_t ch, u32 off)
@@ -911,7 +917,7 @@ void BackendD2D::_debugShowDirty(const RenderingPayload& p)
static_cast(rect.right),
static_cast(rect.bottom),
};
- const auto color = colorbrewer::pastel1[i] | 0x1f000000;
+ const auto color = til::colorbrewer::pastel1[i] | 0x1f000000;
_fillRectangle(rectF, color);
}
}
diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp
index b266d3ba092..9a83b75d233 100644
--- a/src/renderer/atlas/BackendD3D.cpp
+++ b/src/renderer/atlas/BackendD3D.cpp
@@ -15,9 +15,10 @@
#include "dwrite.h"
#include "wic.h"
#include "../../types/inc/ColorFix.hpp"
+#include "../../types/inc/convert.hpp"
#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_COLORIZE_GLYPH_ATLAS
-#include "colorbrewer.h"
+#include
#endif
TIL_FAST_MATH_BEGIN
@@ -451,15 +452,21 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
{
if (error)
{
- LOG_HR_MSG(hr, "%.*hs", static_cast(error->GetBufferSize()), static_cast(error->GetBufferPointer()));
+ if (p.warningCallback)
+ {
+ //to handle compile time errors
+ const std::string_view errMsgStrView{ static_cast(error->GetBufferPointer()), error->GetBufferSize() };
+ const auto errMsgWstring = ConvertToW(CP_ACP, errMsgStrView);
+ p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, errMsgWstring);
+ }
}
else
{
- LOG_HR(hr);
- }
- if (p.warningCallback)
- {
- p.warningCallback(D2DERR_SHADER_COMPILE_FAILED, p.s->misc->customPixelShaderPath);
+ if (p.warningCallback)
+ {
+ //to handle errors such as file not found, path not found, access denied
+ p.warningCallback(hr, p.s->misc->customPixelShaderPath);
+ }
}
}
@@ -2222,7 +2229,7 @@ void BackendD3D::_debugShowDirty(const RenderingPayload& p)
if (rect.non_empty())
{
_appendQuad() = {
- .shadingType = ShadingType::Selection,
+ .shadingType = static_cast(ShadingType::Selection),
.position = {
static_cast(rect.left),
static_cast(rect.top),
@@ -2231,7 +2238,7 @@ void BackendD3D::_debugShowDirty(const RenderingPayload& p)
static_cast(rect.right - rect.left),
static_cast(rect.bottom - rect.top),
},
- .color = colorbrewer::pastel1[i] | 0x1f000000,
+ .color = til::colorbrewer::pastel1[i] | 0x1f000000,
};
}
}
diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj
index 694b850224d..37dac662d81 100644
--- a/src/renderer/atlas/atlas.vcxproj
+++ b/src/renderer/atlas/atlas.vcxproj
@@ -32,7 +32,6 @@
-
@@ -102,4 +101,4 @@
$(SolutionDir)\oss\stb;$(OutDir)$(ProjectName);%(AdditionalIncludeDirectories)
-
+
\ No newline at end of file
diff --git a/src/renderer/base/RenderSettings.cpp b/src/renderer/base/RenderSettings.cpp
index f885957796a..db9afca8dc4 100644
--- a/src/renderer/base/RenderSettings.cpp
+++ b/src/renderer/base/RenderSettings.cpp
@@ -259,7 +259,7 @@ COLORREF RenderSettings::GetAttributeUnderlineColor(const TextAttribute& attr) c
// renderer if there are blinking cells currently in view.
// Arguments:
// - renderer: the renderer that will be redrawn.
-void RenderSettings::ToggleBlinkRendition(Renderer& renderer) noexcept
+void RenderSettings::ToggleBlinkRendition(Renderer* renderer) noexcept
try
{
if (GetRenderMode(Mode::BlinkAllowed))
@@ -277,7 +277,10 @@ try
// We reset the _blinkIsInUse flag before redrawing, so we can
// get a fresh assessment of the current blink attribute usage.
_blinkIsInUse = false;
- renderer.TriggerRedrawAll();
+ if (renderer)
+ {
+ renderer->TriggerRedrawAll();
+ }
}
}
}
diff --git a/src/renderer/inc/RenderSettings.hpp b/src/renderer/inc/RenderSettings.hpp
index c836bdde848..704f97d4f09 100644
--- a/src/renderer/inc/RenderSettings.hpp
+++ b/src/renderer/inc/RenderSettings.hpp
@@ -42,7 +42,7 @@ namespace Microsoft::Console::Render
std::pair GetAttributeColors(const TextAttribute& attr) const noexcept;
std::pair GetAttributeColorsWithAlpha(const TextAttribute& attr) const noexcept;
COLORREF GetAttributeUnderlineColor(const TextAttribute& attr) const noexcept;
- void ToggleBlinkRendition(class Renderer& renderer) noexcept;
+ void ToggleBlinkRendition(class Renderer* renderer) noexcept;
private:
til::enumset _renderMode{ Mode::BlinkAllowed, Mode::IntenseIsBright };
diff --git a/src/terminal/adapter/PageManager.cpp b/src/terminal/adapter/PageManager.cpp
index 76b03faf779..9a0ae4de711 100644
--- a/src/terminal/adapter/PageManager.cpp
+++ b/src/terminal/adapter/PageManager.cpp
@@ -99,7 +99,7 @@ void Page::MoveViewportDown() noexcept
_viewport.bottom++;
}
-PageManager::PageManager(ITerminalApi& api, Renderer& renderer) noexcept :
+PageManager::PageManager(ITerminalApi& api, Renderer* renderer) noexcept :
_api{ api },
_renderer{ renderer }
{
@@ -220,9 +220,9 @@ void PageManager::MoveTo(const til::CoordType pageNumber, const bool makeVisible
}
_activePageNumber = newPageNumber;
- if (redrawRequired)
+ if (redrawRequired && _renderer)
{
- _renderer.TriggerRedrawAll();
+ _renderer->TriggerRedrawAll();
}
}
diff --git a/src/terminal/adapter/PageManager.hpp b/src/terminal/adapter/PageManager.hpp
index e625d24d142..652c98b83ca 100644
--- a/src/terminal/adapter/PageManager.hpp
+++ b/src/terminal/adapter/PageManager.hpp
@@ -46,7 +46,7 @@ namespace Microsoft::Console::VirtualTerminal
using Renderer = Microsoft::Console::Render::Renderer;
public:
- PageManager(ITerminalApi& api, Renderer& renderer) noexcept;
+ PageManager(ITerminalApi& api, Renderer* renderer) noexcept;
void Reset();
Page Get(const til::CoordType pageNumber) const;
Page ActivePage() const;
@@ -59,7 +59,7 @@ namespace Microsoft::Console::VirtualTerminal
TextBuffer& _getBuffer(const til::CoordType pageNumber, const til::size pageSize) const;
ITerminalApi& _api;
- Renderer& _renderer;
+ Renderer* _renderer;
til::CoordType _activePageNumber = 1;
til::CoordType _visiblePageNumber = 1;
static constexpr til::CoordType MAX_PAGES = 6;
diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp
index 74ede69b53f..1a451941e09 100644
--- a/src/terminal/adapter/adaptDispatch.cpp
+++ b/src/terminal/adapter/adaptDispatch.cpp
@@ -16,7 +16,7 @@ using namespace Microsoft::Console::VirtualTerminal;
static constexpr std::wstring_view whitespace{ L" " };
-AdaptDispatch::AdaptDispatch(ITerminalApi& api, Renderer& renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept :
+AdaptDispatch::AdaptDispatch(ITerminalApi& api, Renderer* renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept :
_api{ api },
_renderer{ renderer },
_renderSettings{ renderSettings },
@@ -1937,7 +1937,10 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
{
return false;
}
- _renderer.TriggerRedrawAll();
+ if (_renderer)
+ {
+ _renderer->TriggerRedrawAll();
+ }
return true;
case DispatchTypes::ModeParams::DECOM_OriginMode:
_modes.set(Mode::Origin, enable);
@@ -3221,7 +3224,10 @@ bool AdaptDispatch::HardReset()
TabSet(DispatchTypes::TabSetType::SetEvery8Columns);
// Clear the soft font in the renderer and delete the font buffer.
- _renderer.UpdateSoftFont({}, {}, false);
+ if (_renderer)
+ {
+ _renderer->UpdateSoftFont({}, {}, false);
+ }
_fontBuffer = nullptr;
// Reset internal modes to their initial state
@@ -3501,18 +3507,20 @@ bool AdaptDispatch::SetColorTableEntry(const size_t tableIndex, const DWORD dwCo
return false;
}
- // If we're updating the background color, we need to let the renderer
- // know, since it may want to repaint the window background to match.
- const auto backgroundIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground);
- const auto backgroundChanged = (tableIndex == backgroundIndex);
+ if (_renderer)
+ {
+ // If we're updating the background color, we need to let the renderer
+ // know, since it may want to repaint the window background to match.
+ const auto backgroundIndex = _renderSettings.GetColorAliasIndex(ColorAlias::DefaultBackground);
+ const auto backgroundChanged = (tableIndex == backgroundIndex);
- // Similarly for the frame color, the tab may need to be repainted.
- const auto frameIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground);
- const auto frameChanged = (tableIndex == frameIndex);
+ // Similarly for the frame color, the tab may need to be repainted.
+ const auto frameIndex = _renderSettings.GetColorAliasIndex(ColorAlias::FrameBackground);
+ const auto frameChanged = (tableIndex == frameIndex);
+
+ _renderer->TriggerRedrawAll(backgroundChanged, frameChanged);
+ }
- // Update the screen colors if we're not a pty
- // No need to force a redraw in pty mode.
- _renderer.TriggerRedrawAll(backgroundChanged, frameChanged);
return true;
}
@@ -3566,14 +3574,19 @@ bool AdaptDispatch::AssignColor(const DispatchTypes::ColorItem item, const VTInt
}
// No need to force a redraw in pty mode.
- const auto inPtyMode = _api.IsConsolePty();
- if (!inPtyMode)
+ if (_api.IsConsolePty())
+ {
+ return false;
+ }
+
+ if (_renderer)
{
const auto backgroundChanged = item == DispatchTypes::ColorItem::NormalText;
const auto frameChanged = item == DispatchTypes::ColorItem::WindowFrame;
- _renderer.TriggerRedrawAll(backgroundChanged, frameChanged);
+ _renderer->TriggerRedrawAll(backgroundChanged, frameChanged);
}
- return !inPtyMode;
+
+ return true;
}
//Routine Description:
@@ -3769,11 +3782,11 @@ bool AdaptDispatch::DoConEmuAction(const std::wstring_view string)
bool AdaptDispatch::DoITerm2Action(const std::wstring_view string)
{
const auto isConPty = _api.IsConsolePty();
- if (isConPty)
+ if (isConPty && _renderer)
{
// Flush the frame manually, to make sure marks end up on the right
// line, like the alt buffer sequence.
- _renderer.TriggerFlush(false);
+ _renderer->TriggerFlush(false);
}
if constexpr (!Feature_ScrollbarMarks::IsEnabled())
@@ -3813,11 +3826,11 @@ bool AdaptDispatch::DoITerm2Action(const std::wstring_view string)
bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string)
{
const auto isConPty = _api.IsConsolePty();
- if (isConPty)
+ if (isConPty && _renderer)
{
// Flush the frame manually, to make sure marks end up on the right
// line, like the alt buffer sequence.
- _renderer.TriggerFlush(false);
+ _renderer->TriggerFlush(false);
}
if constexpr (!Feature_ScrollbarMarks::IsEnabled())
@@ -3904,10 +3917,10 @@ bool AdaptDispatch::DoFinalTermAction(const std::wstring_view string)
bool AdaptDispatch::DoVsCodeAction(const std::wstring_view string)
{
// This is not implemented in conhost.
- if (_api.IsConsolePty())
+ if (_api.IsConsolePty() && _renderer)
{
// Flush the frame manually to make sure this action happens at the right time.
- _renderer.TriggerFlush(false);
+ _renderer->TriggerFlush(false);
return false;
}
@@ -4048,10 +4061,13 @@ ITermDispatch::StringHandler AdaptDispatch::DownloadDRCS(const VTInt fontNumber,
{
_termOutput.SetDrcs94Designation(_fontBuffer->GetDesignation());
}
- const auto bitPattern = _fontBuffer->GetBitPattern();
- const auto cellSize = _fontBuffer->GetCellSize();
- const auto centeringHint = _fontBuffer->GetTextCenteringHint();
- _renderer.UpdateSoftFont(bitPattern, cellSize, centeringHint);
+ if (_renderer)
+ {
+ const auto bitPattern = _fontBuffer->GetBitPattern();
+ const auto cellSize = _fontBuffer->GetCellSize();
+ const auto centeringHint = _fontBuffer->GetTextCenteringHint();
+ _renderer->UpdateSoftFont(bitPattern, cellSize, centeringHint);
+ }
}
return true;
};
@@ -4912,9 +4928,9 @@ bool AdaptDispatch::PlaySounds(const VTParameters parameters)
// If we're a conpty, we return false so the command will be passed on
// to the connected terminal. But we need to flush the current frame
// first, otherwise the visual output will lag behind the sound.
- if (_api.IsConsolePty())
+ if (_api.IsConsolePty() && _renderer)
{
- _renderer.TriggerFlush(false);
+ _renderer->TriggerFlush(false);
return false;
}
@@ -4948,7 +4964,11 @@ ITermDispatch::StringHandler AdaptDispatch::_CreatePassthroughHandler()
{
// Before we pass through any more data, we need to flush the current frame
// first, otherwise it can end up arriving out of sync.
- _renderer.TriggerFlush(false);
+ if (_renderer)
+ {
+ _renderer->TriggerFlush(false);
+ }
+
// Then we need to flush the sequence introducer and parameters that have
// already been parsed by the state machine.
auto& stateMachine = _api.GetStateMachine();
diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp
index 76e32d0fdd5..7087932a497 100644
--- a/src/terminal/adapter/adaptDispatch.hpp
+++ b/src/terminal/adapter/adaptDispatch.hpp
@@ -36,7 +36,7 @@ namespace Microsoft::Console::VirtualTerminal
using RenderSettings = Microsoft::Console::Render::RenderSettings;
public:
- AdaptDispatch(ITerminalApi& api, Renderer& renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept;
+ AdaptDispatch(ITerminalApi& api, Renderer* renderer, RenderSettings& renderSettings, TerminalInput& terminalInput) noexcept;
void Print(const wchar_t wchPrintable) override;
void PrintString(const std::wstring_view string) override;
@@ -286,7 +286,7 @@ namespace Microsoft::Console::VirtualTerminal
bool _initDefaultTabStops = true;
ITerminalApi& _api;
- Renderer& _renderer;
+ Renderer* _renderer;
RenderSettings& _renderSettings;
TerminalInput& _terminalInput;
TerminalOutput _termOutput;
diff --git a/src/terminal/adapter/ut_adapter/adapterTest.cpp b/src/terminal/adapter/ut_adapter/adapterTest.cpp
index c0ff50e5748..415d22fe86c 100644
--- a/src/terminal/adapter/ut_adapter/adapterTest.cpp
+++ b/src/terminal/adapter/ut_adapter/adapterTest.cpp
@@ -250,7 +250,7 @@ class TestGetSet final : public ITerminalApi
_setTextAttributesResult = TRUE;
_returnResponseResult = TRUE;
- _textBuffer = std::make_unique(til::size{ 100, 600 }, TextAttribute{}, 0, false, _renderer);
+ _textBuffer = std::make_unique(til::size{ 100, 600 }, TextAttribute{}, 0, false, &_renderer);
// Viewport sitting in the "middle" of the buffer somewhere (so all sides have excess buffer around them)
_viewport.top = 20;
@@ -406,7 +406,7 @@ class AdapterTest
_terminalInput = TerminalInput{};
auto& renderer = _testGetSet->_renderer;
auto& renderSettings = renderer._renderSettings;
- auto adapter = std::make_unique(*_testGetSet, renderer, renderSettings, _terminalInput);
+ auto adapter = std::make_unique(*_testGetSet, &renderer, renderSettings, _terminalInput);
fSuccess = adapter.get() != nullptr;
if (fSuccess)
@@ -2448,7 +2448,7 @@ class AdapterTest
Log::Comment(L"Starting test...");
til::inclusive_rect srTestMargins;
- _testGetSet->_textBuffer = std::make_unique(til::size{ 100, 600 }, TextAttribute{}, 0, false, _testGetSet->_renderer);
+ _testGetSet->_textBuffer = std::make_unique(til::size{ 100, 600 }, TextAttribute{}, 0, false, &_testGetSet->_renderer);
_testGetSet->_viewport.right = 8;
_testGetSet->_viewport.bottom = 8;
auto sScreenHeight = _testGetSet->_viewport.bottom - _testGetSet->_viewport.top;
diff --git a/src/tools/ConsoleBench/ConsoleBench.exe.manifest b/src/tools/ConsoleBench/ConsoleBench.exe.manifest
new file mode 100644
index 00000000000..380e2e03a37
--- /dev/null
+++ b/src/tools/ConsoleBench/ConsoleBench.exe.manifest
@@ -0,0 +1,26 @@
+
+
+
+
+ true
+ UTF-8
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj b/src/tools/ConsoleBench/ConsoleBench.vcxproj
index 1e66412cc6b..5ea84a7c56f 100644
--- a/src/tools/ConsoleBench/ConsoleBench.vcxproj
+++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj
@@ -25,6 +25,9 @@
+
+
+
false
diff --git a/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
index 25a667f9907..ca5d01a8e55 100644
--- a/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
+++ b/src/tools/ConsoleBench/ConsoleBench.vcxproj.filters
@@ -49,4 +49,9 @@
Source Files
+
+
+ Source Files
+
+
\ No newline at end of file
diff --git a/src/tools/ConsoleBench/arena.cpp b/src/tools/ConsoleBench/arena.cpp
index d91986c7276..8c96c341b4c 100644
--- a/src/tools/ConsoleBench/arena.cpp
+++ b/src/tools/ConsoleBench/arena.cpp
@@ -5,7 +5,7 @@ using namespace mem;
Arena::Arena(size_t bytes)
{
- m_alloc = static_cast(THROW_IF_NULL_ALLOC(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)));
+ m_alloc = static_cast(THROW_IF_NULL_ALLOC(VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_READWRITE)));
}
Arena::~Arena()
@@ -41,8 +41,18 @@ void* Arena::_push_raw(size_t bytes, size_t alignment)
{
const auto mask = alignment - 1;
const auto pos = (m_pos + mask) & ~mask;
+ const auto pos_new = pos + bytes;
const auto ptr = m_alloc + pos;
- m_pos = pos + bytes;
+
+ if (pos_new > m_commit)
+ {
+ // Commit in 1MB chunks and pre-commit 1MiB in advance.
+ const auto commit_new = (pos_new + 0x1FFFFF) & ~0xFFFFF;
+ THROW_IF_NULL_ALLOC(VirtualAlloc(m_alloc + m_commit, commit_new - m_commit, MEM_COMMIT, PAGE_READWRITE));
+ m_commit = commit_new;
+ }
+
+ m_pos = pos_new;
return ptr;
}
@@ -76,8 +86,8 @@ ScopedArena::~ScopedArena()
static [[msvc::noinline]] std::array thread_arenas_init()
{
return {
- Arena{ 64 * 1024 * 1024 },
- Arena{ 64 * 1024 * 1024 },
+ Arena{ 1024 * 1024 * 1024 },
+ Arena{ 1024 * 1024 * 1024 },
};
}
@@ -166,7 +176,9 @@ std::wstring_view mem::format(Arena& arena, const wchar_t* fmt, va_list args)
return {};
}
+ // Make space for a terminating \0 character.
len++;
+
const auto buffer = arena.push_uninitialized(len);
len = _vsnwprintf(buffer, len, fmt, args);
diff --git a/src/tools/ConsoleBench/arena.h b/src/tools/ConsoleBench/arena.h
index 1519f03377b..92409c32921 100644
--- a/src/tools/ConsoleBench/arena.h
+++ b/src/tools/ConsoleBench/arena.h
@@ -60,6 +60,7 @@ namespace mem
void* _push_uninitialized(size_t bytes, size_t alignment = __STDCPP_DEFAULT_NEW_ALIGNMENT__);
uint8_t* m_alloc = nullptr;
+ size_t m_commit = 0;
size_t m_pos = 0;
};
@@ -96,16 +97,32 @@ namespace mem
}
template
- std::basic_string_view repeat_string(Arena& arena, std::basic_string_view in, size_t count)
+ auto repeat(Arena& arena, const T& in, size_t count) -> decltype(auto)
{
- const auto len = count * in.size();
- const auto buf = arena.push_uninitialized(len);
-
- for (size_t i = 0; i < count; ++i)
+ if constexpr (is_std_view::value)
{
- mem::copy(buf + i * in.size(), in.data(), in.size());
+ const auto data = in.data();
+ const auto size = in.size();
+ const auto len = count * size;
+ const auto buf = arena.push_uninitialized(len);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ mem::copy(buf + i * size, data, size);
+ }
+
+ return T{ buf, len };
}
+ else
+ {
+ const auto buf = arena.push_uninitialized(count);
+
+ for (size_t i = 0; i < count; ++i)
+ {
+ memcpy(buf + i, &in, sizeof(T));
+ }
- return { buf, len };
+ return std::span{ buf, count };
+ }
}
}
diff --git a/src/tools/ConsoleBench/conhost.cpp b/src/tools/ConsoleBench/conhost.cpp
index 95a0a3ea8a9..4db5352f99b 100644
--- a/src/tools/ConsoleBench/conhost.cpp
+++ b/src/tools/ConsoleBench/conhost.cpp
@@ -3,6 +3,7 @@
#include
#include
+#include
#include "arena.h"
@@ -46,12 +47,27 @@ static void conhostCopyToStringBuffer(USHORT& length, auto& buffer, const wchar_
ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path)
{
+ const auto pathLen = wcslen(path);
+ const auto isDLL = pathLen > 4 && wcscmp(&path[pathLen - 4], L".dll") == 0;
+
const auto scratch = mem::get_scratch_arena(arena);
- const auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false);
+ auto server = conhostCreateHandle(nullptr, L"\\Device\\ConDrv\\Server", true, false);
auto reference = conhostCreateHandle(server.get(), L"\\Reference", false, true);
{
- const auto cmd = format(scratch.arena, LR"("%s" --server 0x%zx)", path, server.get());
+ const auto selfPath = scratch.arena.push_uninitialized(64 * 1024);
+ GetModuleFileNameW(nullptr, selfPath, 64 * 1024);
+
+ std::wstring_view cmd;
+
+ if (isDLL)
+ {
+ cmd = format(scratch.arena, LR"("%s" host %zx "%s")", selfPath, server.get(), path);
+ }
+ else
+ {
+ cmd = format(scratch.arena, LR"("%s" --server 0x%zx)", path, server.get());
+ }
uint8_t attrListBuffer[64];
@@ -154,6 +170,22 @@ ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path)
};
}
+// A continuation of spawn_conhost().
+void check_spawn_conhost_dll(int argc, const wchar_t* argv[])
+{
+ if (argc == 4 && wcscmp(argv[1], L"host") == 0)
+ {
+ const auto serverHandle = reinterpret_cast(wcstoull(argv[2], nullptr, 16));
+ const auto path = argv[3];
+
+ using Entrypoint = NTSTATUS(NTAPI*)(HANDLE);
+ const auto h = THROW_LAST_ERROR_IF_NULL(LoadLibraryExW(path, nullptr, 0));
+ const auto f = THROW_LAST_ERROR_IF_NULL(reinterpret_cast(GetProcAddress(h, "ConsoleCreateIoThread")));
+ THROW_IF_NTSTATUS_FAILED(f(serverHandle));
+ ExitThread(S_OK);
+ }
+}
+
HANDLE get_active_connection()
{
// (Not actually) FUN FACT! The handles don't mean anything and the cake is a lie!
diff --git a/src/tools/ConsoleBench/conhost.h b/src/tools/ConsoleBench/conhost.h
index e794eca555e..719a5bb87ad 100644
--- a/src/tools/ConsoleBench/conhost.h
+++ b/src/tools/ConsoleBench/conhost.h
@@ -16,5 +16,6 @@ struct ConhostHandle
};
ConhostHandle spawn_conhost(mem::Arena& arena, const wchar_t* path);
+void check_spawn_conhost_dll(int argc, const wchar_t* argv[]);
HANDLE get_active_connection();
void set_active_connection(HANDLE connection);
diff --git a/src/tools/ConsoleBench/main.cpp b/src/tools/ConsoleBench/main.cpp
index 4e380474ebc..31b43e6b93d 100644
--- a/src/tools/ConsoleBench/main.cpp
+++ b/src/tools/ConsoleBench/main.cpp
@@ -7,26 +7,47 @@
#include "conhost.h"
#include "utils.h"
+#define ENABLE_TEST_OUTPUT_WRITE 1
+#define ENABLE_TEST_OUTPUT_SCROLL 1
+#define ENABLE_TEST_OUTPUT_FILL 1
+#define ENABLE_TEST_OUTPUT_READ 1
+#define ENABLE_TEST_INPUT 1
+#define ENABLE_TEST_CLIPBOARD 1
+
using Measurements = std::span;
using MeasurementsPerBenchmark = std::span;
struct BenchmarkContext
{
- HWND hwnd;
- HANDLE input;
- HANDLE output;
- int64_t time_limit;
+ bool wants_more() const;
+ void mark_beg();
+ void mark_end();
+ size_t rand();
+
+ HWND hwnd = nullptr;
+ HANDLE input = nullptr;
+ HANDLE output = nullptr;
+
mem::Arena& arena;
std::string_view utf8_4Ki;
std::string_view utf8_128Ki;
std::wstring_view utf16_4Ki;
std::wstring_view utf16_128Ki;
+ std::span attr_4Ki;
+ std::span char_4Ki;
+ std::span input_4Ki;
+
+ Measurements m_measurements;
+ size_t m_measurements_off = 0;
+ int64_t m_time = 0;
+ int64_t m_time_limit = 0;
+ size_t m_rng_state = 0;
};
struct Benchmark
{
const char* title;
- void (*exec)(const BenchmarkContext& ctx, Measurements measurements);
+ void (*exec)(BenchmarkContext& ctx);
};
struct AccumulatedResults
@@ -37,158 +58,415 @@ struct AccumulatedResults
MeasurementsPerBenchmark* measurments;
};
-constexpr int32_t perf_delta(int64_t beg, int64_t end)
-{
- return static_cast(end - beg);
-}
+static constexpr COORD s_buffer_size{ 120, 9001 };
+static constexpr COORD s_viewport_size{ 120, 30 };
-static constexpr Benchmark s_benchmarks[]{
+static constexpr Benchmark s_benchmarks[] = {
+#if ENABLE_TEST_OUTPUT_WRITE
Benchmark{
.title = "WriteConsoleA 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleA(ctx.output, ctx.utf8_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ ctx.mark_beg();
+ const auto res = WriteConsoleA(ctx.output, ctx.utf8_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
.title = "WriteConsoleW 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ ctx.mark_beg();
+ const auto res = WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
.title = "WriteConsoleA 128Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- WriteConsoleA(ctx.output, ctx.utf8_128Ki.data(), static_cast(ctx.utf8_128Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ ctx.mark_beg();
+ const auto res = WriteConsoleA(ctx.output, ctx.utf8_128Ki.data(), static_cast(ctx.utf8_128Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleW 128Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputAttribute(ctx.output, ctx.attr_4Ki.data(), static_cast(ctx.attr_4Ki.size()), pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputCharacterW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf16_4Ki.size()), pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+ Benchmark{
+ .title = "WriteConsoleOutputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ static constexpr COORD size{ 64, 64 };
+ static constexpr SMALL_RECT rect{ 0, 0, 63, 63 };
+
+ while (ctx.wants_more())
+ {
+ auto written = rect;
+
+ ctx.mark_beg();
+ const auto res = WriteConsoleOutputW(ctx.output, ctx.char_4Ki.data(), size, pos, &written);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_OUTPUT_SCROLL
+ Benchmark{
+ .title = "ScrollConsoleScreenBufferW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ for (int i = 0; i < 10; i++)
+ {
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+ }
- if (end >= ctx.time_limit)
+ static constexpr CHAR_INFO fill{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
+ static constexpr size_t w = 64;
+ static constexpr size_t h = 64;
+
+ while (ctx.wants_more())
+ {
+ auto r = ctx.rand();
+ const auto srcLeft = (r >> 0) % (s_buffer_size.X - w);
+ const auto srcTop = (r >> 16) % (s_buffer_size.Y - h);
+
+ size_t dstLeft;
+ size_t dstTop;
+ do
{
- break;
- }
+ r = ctx.rand();
+ dstLeft = (r >> 0) % (s_buffer_size.X - w);
+ dstTop = (r >> 16) % (s_buffer_size.Y - h);
+ } while (srcLeft == dstLeft && srcTop == dstTop);
+
+ const SMALL_RECT scrollRect{
+ .Left = static_cast(srcLeft),
+ .Top = static_cast(srcTop),
+ .Right = static_cast(srcLeft + w - 1),
+ .Bottom = static_cast(srcTop + h - 1),
+ };
+ const COORD destOrigin{
+ .X = static_cast(dstLeft),
+ .Y = static_cast(dstTop),
+ };
+
+ ctx.mark_beg();
+ const auto res = ScrollConsoleScreenBufferW(ctx.output, &scrollRect, nullptr, destOrigin, &fill);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
Benchmark{
- .title = "WriteConsoleW 128Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- for (auto& d : measurements)
+ .title = "ScrollConsoleScreenBufferW vertical",
+ .exec = [](BenchmarkContext& ctx) {
+ for (int i = 0; i < 10; i++)
{
- const auto beg = query_perf_counter();
WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ }
- if (end >= ctx.time_limit)
+ static constexpr CHAR_INFO fill{ L' ', FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED };
+ static constexpr size_t h = (4096 + s_buffer_size.X / 2) / s_buffer_size.X;
+
+ while (ctx.wants_more())
+ {
+ auto r = ctx.rand();
+ const auto srcTop = r % (s_buffer_size.Y - h);
+
+ size_t dstTop;
+ do
{
- break;
- }
+ r = ctx.rand();
+ dstTop = r % (s_buffer_size.Y - h);
+ } while (srcTop == dstTop);
+
+ const SMALL_RECT scrollRect{
+ .Left = 0,
+ .Top = static_cast(srcTop),
+ .Right = s_buffer_size.X - 1,
+ .Bottom = static_cast(srcTop + h - 1),
+ };
+ const COORD destOrigin{
+ .X = 0,
+ .Y = static_cast(dstTop),
+ };
+
+ ctx.mark_beg();
+ const auto res = ScrollConsoleScreenBufferW(ctx.output, &scrollRect, nullptr, destOrigin, &fill);
+ ctx.mark_end();
+ debugAssert(res == TRUE);
}
},
},
+#endif
+#if ENABLE_TEST_OUTPUT_FILL
Benchmark{
- .title = "Copy to clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+ .title = "FillConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF5 /* ID_CONSOLE_SELECTALL */, 0);
+ ctx.mark_beg();
+ FillConsoleOutputAttribute(ctx.output, FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, 4096, pos, &written);
+ ctx.mark_end();
+ debugAssert(written == 4096);
+ }
+ },
+ },
+ Benchmark{
+ .title = "FillConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ DWORD written;
- const auto beg = query_perf_counter();
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF0 /* ID_CONSOLE_COPY */, 0);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ FillConsoleOutputCharacterW(ctx.output, L'A', 4096, pos, &written);
+ ctx.mark_end();
+ debugAssert(written == 4096);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_OUTPUT_READ
+ Benchmark{
+ .title = "ReadConsoleOutputAttribute 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(4096);
+ DWORD read;
- if (end >= ctx.time_limit)
- {
- break;
- }
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ ReadConsoleOutputAttribute(ctx.output, buf, 4096, pos, &read);
+ ctx.mark_end();
+ debugAssert(read == 4096);
}
},
},
Benchmark{
- .title = "Paste from clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- set_clipboard(ctx.hwnd, ctx.utf16_4Ki);
+ .title = "ReadConsoleOutputCharacterW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(4096);
+ DWORD read;
+
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ ctx.mark_beg();
+ ReadConsoleOutputCharacterW(ctx.output, buf, 4096, pos, &read);
+ ctx.mark_end();
+ debugAssert(read == 4096);
+ }
+ },
+ },
+ Benchmark{
+ .title = "ReadConsoleOutputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ static constexpr COORD pos{ 0, 0 };
+ static constexpr COORD size{ 64, 64 };
+ static constexpr SMALL_RECT rect{ 0, 0, 63, 63 };
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(size.X * size.Y);
+
+ WriteConsoleW(ctx.output, ctx.utf16_128Ki.data(), static_cast(ctx.utf16_128Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ auto read = rect;
+
+ ctx.mark_beg();
+ ReadConsoleOutputW(ctx.output, buf, size, pos, &read);
+ ctx.mark_end();
+ debugAssert(read.Right == 63 && read.Bottom == 63);
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_INPUT
+ Benchmark{
+ .title = "WriteConsoleInputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ DWORD written;
+
FlushConsoleInputBuffer(ctx.input);
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
- const auto beg = query_perf_counter();
- SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
+ ctx.mark_beg();
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ ctx.mark_end();
+ debugAssert(written == ctx.input_4Ki.size());
FlushConsoleInputBuffer(ctx.input);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
}
},
},
Benchmark{
- .title = "ReadConsoleInputW clipboard 4Ki",
- .exec = [](const BenchmarkContext& ctx, Measurements measurements) {
- static constexpr DWORD cap = 16 * 1024;
+ .title = "ReadConsoleInputW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ const auto scratch = mem::get_scratch_arena(ctx.arena);
+ const auto buf = scratch.arena.push_uninitialized(ctx.input_4Ki.size());
+ DWORD written, read;
+ FlushConsoleInputBuffer(ctx.input);
+
+ while (ctx.wants_more())
+ {
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ debugAssert(written == ctx.input_4Ki.size());
+
+ ctx.mark_beg();
+ ReadConsoleInputW(ctx.input, buf, static_cast(ctx.input_4Ki.size()), &read);
+ ctx.mark_end();
+ debugAssert(read == ctx.input_4Ki.size());
+ }
+ },
+ },
+ Benchmark{
+ .title = "ReadConsoleW 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
const auto scratch = mem::get_scratch_arena(ctx.arena);
- const auto buf = scratch.arena.push_uninitialized(cap);
- DWORD read;
+ const auto cap = static_cast(ctx.input_4Ki.size()) * 4;
+ const auto buf = scratch.arena.push_uninitialized(cap);
+ DWORD written, read;
+
+ FlushConsoleInputBuffer(ctx.input);
+
+ while (ctx.wants_more())
+ {
+ WriteConsoleInputW(ctx.input, ctx.input_4Ki.data(), static_cast(ctx.input_4Ki.size()), &written);
+ debugAssert(written == ctx.input_4Ki.size());
+ ctx.mark_beg();
+ ReadConsoleW(ctx.input, buf, cap, &read, nullptr);
+ debugAssert(read == ctx.input_4Ki.size());
+ ctx.mark_end();
+ }
+ },
+ },
+#endif
+#if ENABLE_TEST_CLIPBOARD
+ Benchmark{
+ .title = "Clipboard copy 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
+ WriteConsoleW(ctx.output, ctx.utf16_4Ki.data(), static_cast(ctx.utf8_4Ki.size()), nullptr, nullptr);
+
+ while (ctx.wants_more())
+ {
+ SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF5 /* ID_CONSOLE_SELECTALL */, 0);
+
+ ctx.mark_beg();
+ SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF0 /* ID_CONSOLE_COPY */, 0);
+ ctx.mark_end();
+ }
+ },
+ },
+ Benchmark{
+ .title = "Clipboard paste 4Ki",
+ .exec = [](BenchmarkContext& ctx) {
set_clipboard(ctx.hwnd, ctx.utf16_4Ki);
FlushConsoleInputBuffer(ctx.input);
- for (auto& d : measurements)
+ while (ctx.wants_more())
{
+ ctx.mark_beg();
SendMessageW(ctx.hwnd, WM_SYSCOMMAND, 0xFFF1 /* ID_CONSOLE_PASTE */, 0);
+ ctx.mark_end();
- const auto beg = query_perf_counter();
- ReadConsoleInputW(ctx.input, buf, cap, &read);
- debugAssert(read >= 1024 && read < cap);
- const auto end = query_perf_counter();
- d = perf_delta(beg, end);
-
- if (end >= ctx.time_limit)
- {
- break;
- }
+ FlushConsoleInputBuffer(ctx.input);
}
},
},
+#endif
};
-static constexpr size_t s_benchmarks_count = _countof(s_benchmarks);
-// Each of these strings is 128 columns.
-static constexpr std::string_view payload_utf8{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor็ ใๅญ็ซใฏใใฐใญ็ฉใใฎๅคขใ่ฆใ" };
-static constexpr std::wstring_view payload_utf16{ L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor็ ใๅญ็ซใฏใใฐใญ็ฉใใฎๅคขใ่ฆใ" };
+static constexpr size_t s_benchmarks_count = _countof(s_benchmarks);
+static constexpr size_t s_samples_min = 20;
+static constexpr size_t s_samples_max = 1000;
+
+// 128 characters and 124 columns.
+static constexpr std::string_view s_payload_utf8{ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna alฮฮฮฮฮ" };
+// 128 characters and 128 columns.
+static constexpr std::wstring_view s_payload_utf16{ L"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.ฮฮฮฮฮ" };
+
+static constexpr WORD s_payload_attr = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED;
+static constexpr CHAR_INFO s_payload_char{
+ .Char = { .UnicodeChar = L'A' },
+ .Attributes = s_payload_attr,
+};
+static constexpr INPUT_RECORD s_payload_record{
+ .EventType = KEY_EVENT,
+ .Event = {
+ .KeyEvent = {
+ .bKeyDown = TRUE,
+ .wRepeatCount = 1,
+ .wVirtualKeyCode = 'A',
+ .wVirtualScanCode = 0,
+ .uChar = 'A',
+ .dwControlKeyState = 0,
+ },
+ },
+};
static bool print_warning();
static AccumulatedResults* prepare_results(mem::Arena& arena, std::span paths);
@@ -196,6 +474,7 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
static void generate_html(mem::Arena& arena, const AccumulatedResults* results);
int wmain(int argc, const wchar_t* argv[])
+try
{
if (argc < 2)
{
@@ -203,6 +482,8 @@ int wmain(int argc, const wchar_t* argv[])
return 1;
}
+ check_spawn_conhost_dll(argc, argv);
+
const auto cp = GetConsoleCP();
const auto output_cp = GetConsoleOutputCP();
const auto restore_cp = wil::scope_exit([&]() {
@@ -230,12 +511,29 @@ int wmain(int argc, const wchar_t* argv[])
{
const auto title = results->trace_names[trace_idx];
print_format(scratch.arena, "\r\n# %.*s\r\n", title.size(), title.data());
+
+ // I found that waiting between tests fixes weird bugs when launching very old conhost versions.
+ if (trace_idx != 0)
+ {
+ Sleep(5000);
+ }
+
results->measurments[trace_idx] = run_benchmarks_for_path(scratch.arena, paths[trace_idx]);
}
generate_html(scratch.arena, results);
return 0;
}
+catch (const wil::ResultException& e)
+{
+ printf("Exception: %08x\n", e.GetErrorCode());
+ return 1;
+}
+catch (...)
+{
+ printf("Unknown exception\n");
+ return 1;
+}
static bool print_warning()
{
@@ -284,7 +582,7 @@ static AccumulatedResults* prepare_results(mem::Arena& arena, std::span(9001);
- memset(buf, '\n', 9001);
- WriteFile(ctx.output, buf, 9001, nullptr, nullptr);
+ const auto buf = scratch.arena.push_uninitialized(s_buffer_size.Y);
+ memset(buf, '\n', s_buffer_size.Y);
+ WriteFile(ctx.output, buf, s_buffer_size.Y, nullptr, nullptr);
}
static std::span run_benchmarks_for_path(mem::Arena& arena, const wchar_t* path)
@@ -364,7 +663,7 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
const auto parent_hwnd = GetConsoleWindow();
const auto freq = query_perf_freq();
- const auto handle = spawn_conhost(scratch.arena, path);
+ auto handle = spawn_conhost(scratch.arena, path);
set_active_connection(handle.connection.get());
const auto print_with_parent_connection = [&](auto&&... args) {
@@ -377,45 +676,56 @@ static std::span run_benchmarks_for_path(mem::Arena& arena, const
.hwnd = GetConsoleWindow(),
.input = GetStdHandle(STD_INPUT_HANDLE),
.output = GetStdHandle(STD_OUTPUT_HANDLE),
+
.arena = scratch.arena,
- .utf8_4Ki = mem::repeat_string(scratch.arena, payload_utf8, 4 * 1024 / 128),
- .utf8_128Ki = mem::repeat_string(scratch.arena, payload_utf8, 128 * 1024 / 128),
- .utf16_4Ki = mem::repeat_string(scratch.arena, payload_utf16, 4 * 1024 / 128),
- .utf16_128Ki = mem::repeat_string(scratch.arena, payload_utf16, 128 * 1024 / 128),
+ .utf8_4Ki = mem::repeat(scratch.arena, s_payload_utf8, 4 * 1024 / s_payload_utf8.size()),
+ .utf8_128Ki = mem::repeat(scratch.arena, s_payload_utf8, 128 * 1024 / s_payload_utf8.size()),
+ .utf16_4Ki = mem::repeat(scratch.arena, s_payload_utf16, 4 * 1024 / s_payload_utf16.size()),
+ .utf16_128Ki = mem::repeat(scratch.arena, s_payload_utf16, 128 * 1024 / s_payload_utf16.size()),
+ .attr_4Ki = mem::repeat(scratch.arena, s_payload_attr, 4 * 1024),
+ .char_4Ki = mem::repeat(scratch.arena, s_payload_char, 4 * 1024),
+ .input_4Ki = mem::repeat(scratch.arena, s_payload_record, 4 * 1024),
+
+ .m_measurements = scratch.arena.push_uninitialized_span(4 * 1024 * 1024),
};
prepare_conhost(ctx, parent_hwnd);
Sleep(1000);
const auto results = arena.push_uninitialized_span(s_benchmarks_count);
- for (auto& measurements : results)
- {
- measurements = arena.push_zeroed_span(2048);
- }
for (size_t bench_idx = 0; bench_idx < s_benchmarks_count; ++bench_idx)
{
const auto& bench = s_benchmarks[bench_idx];
- auto& measurements = results[bench_idx];
print_with_parent_connection("- %s", bench.title);
- // Warmup for 0.1s.
+ // Warmup for 0.1s max.
WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr);
- ctx.time_limit = query_perf_counter() + freq / 10;
- bench.exec(ctx, measurements);
+ ctx.m_measurements_off = 0;
+ ctx.m_time_limit = query_perf_counter() + freq / 10;
+ bench.exec(ctx);
- // Actual run for 1s.
+ // Actual run for 3s max.
WriteConsoleW(ctx.output, L"\033c", 2, nullptr, nullptr);
- ctx.time_limit = query_perf_counter() + freq;
- bench.exec(ctx, measurements);
+ ctx.m_measurements_off = 0;
+ ctx.m_time_limit = query_perf_counter() + freq * 3;
+ bench.exec(ctx);
- // Trim off trailing 0s that resulted from the time_limit.
- size_t len = measurements.size();
- for (; len > 0 && measurements[len - 1] == 0; --len)
+ const auto measurements = arena.push_uninitialized_span(std::min(ctx.m_measurements_off, s_samples_max));
+ if (ctx.m_measurements_off <= s_samples_max)
{
+ mem::copy(measurements.data(), ctx.m_measurements.data(), ctx.m_measurements_off);
}
- measurements = measurements.subspan(0, len);
+ else
+ {
+ const auto total = ctx.m_measurements_off;
+ for (size_t i = 0; i < s_samples_max; ++i)
+ {
+ measurements[i] = ctx.m_measurements[i * total / s_samples_max];
+ }
+ }
+ results[bench_idx] = measurements;
print_with_parent_connection(", done\r\n");
}
@@ -463,7 +773,7 @@ static void generate_html(mem::Arena& arena, const AccumulatedResults* results)
-
+