Compare commits
83 Commits
release-1.
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b713f1eaf2 | ||
|
|
ffb67bc5a2 | ||
|
|
0639c13d60 | ||
|
|
09471c3753 | ||
|
|
80da24ecf8 | ||
|
|
4715bf5525 | ||
|
|
bd93cb5e8a | ||
|
|
3b2ee448f9 | ||
|
|
445da4bae4 | ||
|
|
54a7fce3e0 | ||
|
|
ff27fdfed1 | ||
|
|
ddb3614e30 | ||
|
|
b2973eb573 | ||
|
|
06b50b47ca | ||
|
|
e504bf2140 | ||
|
|
c70c76e041 | ||
|
|
b12420725f | ||
|
|
eda216fbbc | ||
|
|
7d677c5511 | ||
|
|
1c8e83d52d | ||
|
|
89c4ebaafe | ||
|
|
592c634577 | ||
|
|
53b224b1c6 | ||
|
|
3388a486dc | ||
|
|
1bf4c082b4 | ||
|
|
fc083296b9 | ||
|
|
695ebffca1 | ||
|
|
9e26c020e4 | ||
|
|
9e44df0c9f | ||
|
|
313568d0e5 | ||
|
|
91f921154b | ||
|
|
99c33e084a | ||
|
|
182a3bb573 | ||
|
|
63fbd9f1fc | ||
|
|
934ad98786 | ||
|
|
edd8ac8c6c | ||
|
|
5bc31a1e16 | ||
|
|
29f0690ded | ||
|
|
4faa104f6a | ||
|
|
ceeaadc311 | ||
|
|
d350a89324 | ||
|
|
70a7ccc120 | ||
|
|
0651fcff14 | ||
|
|
396cbbb151 | ||
|
|
436fac6afa | ||
|
|
44e80d40b6 | ||
|
|
985f85ddca | ||
|
|
6b43ace690 | ||
|
|
ddbe370d22 | ||
|
|
f0df154ba9 | ||
|
|
02d5f90837 | ||
|
|
b24579d2b0 | ||
|
|
bbf2c705d2 | ||
|
|
c4885f1e6c | ||
|
|
aa1ed0a19c | ||
|
|
2fc1ef04ce | ||
|
|
fefd1408f2 | ||
|
|
a3a9df82b5 | ||
|
|
9215b5282d | ||
|
|
cffd4eb8e9 | ||
|
|
4027ba37a6 | ||
|
|
d8810f2730 | ||
|
|
0845b3c22f | ||
|
|
58f5d7c72e | ||
|
|
b24dbf7c77 | ||
|
|
ff23be04fb | ||
|
|
81eb13542a | ||
|
|
073e732301 | ||
|
|
e7d3dc5da2 | ||
|
|
26d6a37800 | ||
|
|
951f389210 | ||
|
|
15f2535752 | ||
|
|
b91430b64d | ||
|
|
dc5baab3fe | ||
|
|
4f55568a17 | ||
|
|
acd3ba7bd9 | ||
|
|
e337faaaf8 | ||
|
|
1fdceb0ea9 | ||
|
|
4eaa0b83c7 | ||
|
|
78ca722028 | ||
|
|
10bc1a6532 | ||
|
|
6485a2b440 | ||
|
|
ffaba38fd4 |
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,7 +9,8 @@
|
||||
* [ ] Closes #xxx
|
||||
* [ ] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
|
||||
* [ ] Tests added/passed
|
||||
* [ ] Requires documentation to be updated
|
||||
* [ ] Documentation updated. If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/terminal) and link it here: #xxx
|
||||
* [ ] Schema updated.
|
||||
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx
|
||||
|
||||
<!-- Provide a more detailed description of the PR, other things fixed or any additional comments/features here -->
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
|
||||
|
||||
<!-- Use our own NuGet Feed -->
|
||||
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
|
||||
<add key="Windows Terminal NuGet Feed" value="https://terminalnuget.blob.core.windows.net/feed/index.json" />
|
||||
|
||||
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
|
||||
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
|
||||
|
||||
@@ -314,6 +314,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfTerminalTestNetCore", "s
|
||||
{84848BFA-931D-42CE-9ADF-01EE54DE7890} = {84848BFA-931D-42CE-9ADF-01EE54DE7890}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wt", "src\cascadia\wt\wt.vcxproj", "{506FD703-BAA7-4F6E-9361-64F550EC8FCA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
@@ -1995,6 +1997,33 @@ Global
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|DotNet_x86Test.Build.0 = Release|x86
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x64.Build.0 = Debug|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Debug|x86.Build.0 = Debug|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.ActiveCfg = Release|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x64.Build.0 = Release|x64
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.ActiveCfg = Release|Win32
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -2075,6 +2104,7 @@ Global
|
||||
{067F0A06-FCB7-472C-96E9-B03B54E8E18D} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{6BAE5851-50D5-4934-8D5E-30361A8A40F3} = {81C352DB-1818-45B7-A284-18E259F1CC87}
|
||||
{1588FD7C-241E-4E7D-9113-43735F3E6BAD} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
{506FD703-BAA7-4F6E-9361-64F550EC8FCA} = {59840756-302F-44DF-AA47-441A9D673202}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
@@ -101,6 +101,11 @@ Try {
|
||||
Throw "Failed to find cpprest142_2_10.dll -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
If (($null -eq (Get-Item "$AppxPackageRootPath\wtd.exe" -EA:Ignore)) -And
|
||||
($null -eq (Get-Item "$AppxPackageRootPath\wt.exe" -EA:Ignore))) {
|
||||
Throw "Failed to find wt.exe/wtd.exe -- check the WAP packaging project"
|
||||
}
|
||||
|
||||
} Finally {
|
||||
Remove-Item -Recurse -Force $AppxPackageRootPath
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMinor>2</VersionMinor>
|
||||
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
2
dep/gsl
@@ -67,12 +67,12 @@ To update the version of a given package, use the following snippet
|
||||
|
||||
where:
|
||||
- `$PackageName` is the name of the package, e.g. Microsoft.UI.Xaml
|
||||
- `$OldVersionNumber` is the version number currently used, e.g. 2.4.2-prerelease.200604001
|
||||
- `$OldVersionNumber` is the version number currently used, e.g. 2.5.0-prerelease.200609001
|
||||
- `$NewVersionNumber` is the version number you want to migrate to, e.g. 2.4.200117003-prerelease
|
||||
|
||||
Example usage:
|
||||
|
||||
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.4.2-prerelease.200604001/2.4.200117003-prerelease/g'`
|
||||
`git grep -z -l Microsoft.UI.Xaml | xargs -0 sed -i -e 's/2.5.0-prerelease.200609001/2.4.200117003-prerelease/g'`
|
||||
|
||||
## Using .nupkg files instead of downloaded Nuget packages
|
||||
If you want to use .nupkg files instead of the downloaded Nuget package, you can do this with the following steps:
|
||||
|
||||
@@ -9,12 +9,13 @@ Properties listed below affect the entire window, regardless of the profile sett
|
||||
| `alwaysShowTabs` | _Required_ | Boolean | `true` | When set to `true`, tabs are always displayed. When set to `false` and `showTabsInTitlebar` is set to `false`, tabs only appear after typing <kbd>Ctrl</kbd> + <kbd>T</kbd>. |
|
||||
| `copyOnSelect` | Optional | Boolean | `false` | When set to `true`, a selection is immediately copied to your clipboard upon creation. When set to `false`, the selection persists and awaits further action. |
|
||||
| `copyFormatting` | Optional | Boolean | `false` | When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. |
|
||||
| `largePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste. |
|
||||
| `multiLinePasteWarning` | Optional | Boolean | `true` | When set to `true`, trying to paste text with a _new line_ character will display a warning asking you whether to continue or not with the paste. |
|
||||
| `defaultProfile` | _Required_ | String | PowerShell guid | Sets the default profile. Opens by typing <kbd>Ctrl</kbd> + <kbd>T</kbd> or by clicking the '+' icon. The guid of the desired default profile is used as the value. |
|
||||
| `initialCols` | _Required_ | Integer | `120` | The number of columns displayed in the window upon first load. |
|
||||
| `initialPosition` | Optional | String | `","` | The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If `launchMode` is set to `"maximized"`, the window will be maximized on the monitor specified by those coordinates. |
|
||||
| `initialRows` | _Required_ | Integer | `30` | The number of rows displayed in the window upon first load. |
|
||||
| `launchMode` | Optional | String | `default` | Defines whether the Terminal will launch as maximized or not. Possible values: `"default"`, `"maximized"` |
|
||||
| `rowsToScroll` | Optional | Integer | `system` | The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or "system". |
|
||||
| `theme` | _Required_ | String | `system` | Sets the theme of the application. Possible values: `"light"`, `"dark"`, `"system"` |
|
||||
| `showTerminalTitleInTitlebar` | _Required_ | Boolean | `true` | When set to `true`, titlebar displays the title of the selected tab. When set to `false`, titlebar displays "Windows Terminal". |
|
||||
| `showTabsInTitlebar` | Optional | Boolean | `true` | When set to `true`, the tabs are moved into the titlebar and the titlebar disappears. When set to `false`, the titlebar sits above the tabs. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$id": "https://github.com/microsoft/terminal/blob/master/doc/cascadia/profiles.schema.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$schema": "https://json-schema.org/draft/2019-09/schema#",
|
||||
"title": "Microsoft's Windows Terminal Settings Profile Schema",
|
||||
"definitions": {
|
||||
"KeyChordSegment": {
|
||||
@@ -54,8 +54,15 @@
|
||||
"scrollUpPage",
|
||||
"splitPane",
|
||||
"switchToTab",
|
||||
"toggleFocusMode",
|
||||
"toggleFullscreen",
|
||||
"toggleAlwaysOnTop",
|
||||
"toggleRetroEffect",
|
||||
"find",
|
||||
"setTabColor",
|
||||
"openTabColorPicker",
|
||||
"renameTab",
|
||||
"commandPalette",
|
||||
"unbound"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -257,6 +264,22 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SetTabColorAction": {
|
||||
"description": "Arguments corresponding to a Set Tab Color Action",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/ShortcutAction" },
|
||||
{
|
||||
"properties": {
|
||||
"action": { "type": "string", "pattern": "setTabColor" },
|
||||
"color": {
|
||||
"$ref": "#/definitions/Color",
|
||||
"default": null,
|
||||
"description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"Keybinding": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
@@ -272,6 +295,7 @@
|
||||
{ "$ref": "#/definitions/ResizePaneAction" },
|
||||
{ "$ref": "#/definitions/SplitPaneAction" },
|
||||
{ "$ref": "#/definitions/OpenSettingsAction" },
|
||||
{ "$ref": "#/definitions/SetTabColorAction" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
@@ -301,6 +325,11 @@
|
||||
"additionalProperties": true,
|
||||
"description": "Properties that affect the entire window, regardless of the profile settings.",
|
||||
"properties": {
|
||||
"alwaysOnTop": {
|
||||
"default": false,
|
||||
"description": "When set to true, the window is created on top of all other windows. If multiple windows are all \"always on top\", the most recently focused one will be the topmost",
|
||||
"type": "boolean"
|
||||
},
|
||||
"alwaysShowTabs": {
|
||||
"default": true,
|
||||
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
|
||||
@@ -316,6 +345,16 @@
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"largePasteWarning": {
|
||||
"default": true,
|
||||
"description": "When set to true, trying to paste text with more than 5 KiB of characters will display a warning asking you whether to continue or not with the paste.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"multiLinePasteWarning": {
|
||||
"default": true,
|
||||
"description": "When set to true, trying to paste text with a \"new line\" character will display a warning asking you whether to continue or not with the paste.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"defaultProfile": {
|
||||
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
|
||||
"type": "string"
|
||||
@@ -353,16 +392,10 @@
|
||||
"minimum": 1,
|
||||
"type": "integer"
|
||||
},
|
||||
"startOnUserLogin": {
|
||||
"default": false,
|
||||
"description": "When set to true, this enables the launch of Windows Terminal at startup. Setting this to false will disable the startup task entry. If the Windows Terminal startup task entry is disabled either by org policy or by user action this setting will have no effect.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"launchMode": {
|
||||
"default": "default",
|
||||
"description": "Defines whether the terminal will launch as maximized, full screen, or in a window.",
|
||||
"description": "Defines whether the Terminal will launch as maximized or not.",
|
||||
"enum": [
|
||||
"fullscreen",
|
||||
"maximized",
|
||||
"default"
|
||||
],
|
||||
@@ -370,10 +403,11 @@
|
||||
},
|
||||
"rowsToScroll": {
|
||||
"default": "system",
|
||||
"description": "The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or \"system\".",
|
||||
"description": "This parameter once allowed you to override the systemwide \"choose how many lines to scroll at one time\" setting. It no longer does so.",
|
||||
"maximum": 999,
|
||||
"minimum": 0,
|
||||
"type": [ "integer", "string" ]
|
||||
"type": [ "integer", "string" ],
|
||||
"deprecated": true
|
||||
},
|
||||
"keybindings": {
|
||||
"description": "Properties are specific to each custom key binding.",
|
||||
|
||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 15 KiB |
BIN
doc/specs/#1502 - Advanced Tab Switcher/img/VSMinimumSize.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 345 KiB |
BIN
doc/specs/#1502 - Advanced Tab Switcher/img/VSTabSwitcher.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 34 KiB |
227
doc/specs/#1502 - Advanced Tab Switcher/spec.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
author: Leon Liang @leonMSFT
|
||||
created on: 2019-11-27
|
||||
last updated: 2020-06-16
|
||||
issue id: 1502
|
||||
---
|
||||
|
||||
# Advanced Tab Switcher
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently the user is able to cycle through tabs on the tab bar. However, this horizontal cycling can be pretty inconvenient when the tab titles are long or when there are too many tabs on the tab bar. It could also get hard to see all your available tabs if the tab titles are long and your screen is small. In addition, there's a common use case to quickly switch between two tabs, e.g. when one tab is used as reference and the other is the actively worked-on tab. If the tabs are not right next to each other on the tab bar, it could be difficult to quickly swap between the two. Having the tabs displayed in Most Recently Used (MRU) order would help with this problem. It could also make the user experience better when there are a handful of tabs that are frequently used, but are nowhere near each other on the tab bar.
|
||||
|
||||
Having a tab switcher UI, like the ones in Visual Studio and Visual Studio Code, could help with the tab experience. Presenting the tabs vertically in their own little UI allows the user to see more of the tabs at once, compared to scanning the tab row horizontally and scrolling left/right to find the tab you want. The tab order in those tab switchers are also in MRU order by default.
|
||||
|
||||
To try to alleviate some of these user scenarios, we want to create a tab switcher similar to the ones found in VSCode and VS. This spec will cover the design of the switcher, and how a user would interact with the switcher. It would be primarily keyboard driven, and would give a pop-up display of a vertical list of tabs. The tab switcher would also be able to display the tabs in Most Recently Used (MRU) order.
|
||||
|
||||
## Inspiration
|
||||
|
||||
This was mainly inspired by the tab switcher that's found in Visual Studio Code and Visual Studio.
|
||||
|
||||
VS Code's tab switcher appears directly underneath the tab bar.
|
||||
|
||||

|
||||
|
||||
Visual Studio's tab switcher presents itself as a box in the middle of the editor.
|
||||
|
||||

|
||||
|
||||
In terms of navigating the switcher, both VSCode and Visual Studio behave very similarly. Both open with the press of <kbd>ctrl+tab</kbd> and dismiss on release of <kbd>ctrl</kbd>. They both also allow the user to select the tab with the mouse and with <kbd>enter</kbd>. <kbd>esc</kbd> and a mouse click outside of the switcher both dismiss the window as well.
|
||||
|
||||
I'm partial towards looking like VSCode's Tab Switcher - specifically because it seems like both their Command Palette and Tab Switcher use the same UI. You can observe this by first bringing up the command palette, then hitting the keybinding to bring up the tab switcher. You'll notice that they're both using the same centered drop-down from the tab row. In fact, hitting the Tab Switcher keybinding in VSCode while the Command Palette is open simply auto fills the search box with "edit active", signifying that the user wants to select one of the tabs to edit, effectively "swapping" to the tab that's highlighted.
|
||||
|
||||
Since Terminal now has a command palette, it would be amazing to reuse that UI and simply fill it with the names of a user's currently open tabs!
|
||||
|
||||
## Solution Design
|
||||
|
||||
To extend upon the command palette, we simply need to create and maintain two Vector<Commands>, where each command will simply dispatch a `SwitchToTab` `ShortcutAction`. One vector will have the commands in tab row order, and the other will be in MRU order. They'll both have to be maintained along with our existing vector of tabs.
|
||||
|
||||
These vectors of commands can then be set as the commands to pull from in the command palette, and as long as the tab titles are available in these commands, the command palette will be able to naturally filter through the tabs as a user types in its search bar. Just like the command palette, a user will be able to navigate through the list of tabs with the arrow keys and pointer interactions. As part of this implementation, I can supplement these actions with "tab switcher specific" navigation keybindings that would only work if the command palette is in tab switcher mode.
|
||||
|
||||
The `TabSwitcherControl` will use `TerminalPage`'s `ShortcutActionDispatch` to dispatch a `SwitchToTab` `ShortcutAction`. This will eventually cause `TerminalPage::_OnTabSelectionChanged` to be called. We can update the MRU in this function to be sure that changing tabs from the TabSwitcher, clicking on a tab, or nextTab/prevTab-ing will keep the MRU up-to-date. Adding or closing tabs are handled in `_OpenNewTab` and `_CloseFocusedTab`, which will need to be modified to update the command vectors.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
The Tab Switcher will reuse a lot of the XAML code that's used in the command palette. This means it'll show up as a drop-down from the horizontal center of the tab row. It'll appear as a single overlay over the whole Terminal window. There will also be a search box on top of the list of tabs. Here's a rough mockup of how the command palette/tab switcher looks like:
|
||||
|
||||

|
||||
|
||||
Each entry in the list will show the tab's titles and their assigned number for quick switching, and only one line will be highlighted to signify the tab that is currently selected. The top 9 tabs in the list are numbered for quick switching, and the rest of the tabs will simply have an empty space where a number would be.
|
||||
|
||||
The list would look (roughly) like this:
|
||||
```
|
||||
1 foo (highlighted)
|
||||
2 boo
|
||||
3 Windows
|
||||
4 /c/Users/booboo
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d/
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
```
|
||||
|
||||
The highlighted line can move up or down, and if the user moves up while the highlighted line is already at the top of the list, the highlight will wrap around to the bottom of the list. Similarly, it will wrap to the top if the highlight is at the bottom of the list and the user moves down.
|
||||
|
||||
If there's more tabs than the UI can display, the list of tabs will scroll up/down as the user keeps iterating up/down. Even if some of the numbered tabs (the first 9 tabs) are not visible, the user can still press any number 1 through 9 to quick switch to that tab.
|
||||
|
||||
To give an example of what happens after scrolling past the end, imagine a user is starting from the state in the mock above. The user then iterates down past the end of the visible list four times. The below mock shows the result.
|
||||
|
||||
```
|
||||
5 Git Moo
|
||||
6 shoo
|
||||
7 /c/
|
||||
8 /d
|
||||
9 /e/
|
||||
/f/
|
||||
/g/
|
||||
/h/
|
||||
/i/
|
||||
/j/
|
||||
/k/
|
||||
/l/ (highlighted)
|
||||
```
|
||||
|
||||
The tabs designated by numbers 1 through 4 are no longer visible (but still quick-switchable), and the list now starts with "Git Moo", which is associated with number 5.
|
||||
|
||||
### Using the Switcher
|
||||
|
||||
#### Opening the Tab Switcher
|
||||
|
||||
The user can press a keybinding named `tabSwitcher` to bring up the command palette UI with a list of tab titles.
|
||||
The user can also bring up the command palette first, and type a "tab switcher" prefix like "@" into the search bar to switch into "tab switcher mode".
|
||||
The user will be able to change it to whatever they like.
|
||||
There will also be an optional `anchor` arg that may be provided to this keybinding.
|
||||
|
||||
#### Keeping it open
|
||||
|
||||
We use the term `anchor` to illustrate the idea that the UI stays visible as long as something is "anchoring" it down.
|
||||
|
||||
Here's an example of how to set the `anchor` key in the settings:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor": "ctrl" }}
|
||||
```
|
||||
|
||||
This user provided the `anchor` key arg, and set it to <kbd>ctrl</kbd>. So, the user would open the UI with <kbd>ctrl+tab</kbd>, and as long as the user is holding <kbd>ctrl</kbd> down, the UI won't dismiss. The moment the user releases <kbd>ctrl</kbd>, the UI dismisses. The `anchor` key needs to be one of the keys in the `openTabSwitcher` keybinding. If it isn't, we'll display a warning dialog in this case saying that the `anchor` key isn't actually part of the keybinding, and the user might run into some weird behavior.
|
||||
|
||||
If `openTabSwitcher` is not given an `anchor` key, the switcher will stay visible even after the release of the keybinding.
|
||||
|
||||
#### Switching through Tabs
|
||||
|
||||
The user will be able to navigate through the switcher with the following keybindings:
|
||||
|
||||
- Switching Down: <kbd>tab</kbd> or <kbd>downArrow</kbd>
|
||||
- Switching Up: <kbd>shift+tab</kbd> or <kbd>upArrow</kbd>
|
||||
|
||||
As the user is cycling through the tab list, the selected tab will be highlighted but the terminal won't actually switch focus to the selected tab. This also applies to pointer interaction. Hovering over an item with a mouse will highlight the item but not switch to the tab.
|
||||
|
||||
#### Closing the Switcher and Bringing a Tab into Focus
|
||||
|
||||
There are two _dismissal_ keybindings:
|
||||
|
||||
1. <kbd>enter</kbd> : brings the currently selected tab into focus and dismisses the UI.
|
||||
2. <kbd>esc</kbd> : dismisses the UI without changing tab focus.
|
||||
|
||||
The following are ways a user can dismiss the UI, _whether or not_ the `Anchor` key is provided to `openTabSwitcher`.
|
||||
|
||||
1. The user can press a number associated with a tab to instantly switch to the tab and dismiss the switcher.
|
||||
2. The user can click on a tab to instantly switch to the tab and dismiss the switcher.
|
||||
3. The user can click outside of the UI to dismiss the switcher without bringing the selected tab into focus.
|
||||
4. The user can press any of the dismissal keybindings.
|
||||
|
||||
If the `anchor` key is provided, then in addition to the above methods, the UI will dismiss upon the release of the `anchor` key.
|
||||
|
||||
Pressing the `openTabSwitcher` keychord again will not close the switcher, it'll do nothing.
|
||||
|
||||
### Most Recently Used Order
|
||||
|
||||
We'll provide a setting that will allow the list of tabs to be presented in either _in-order_ (how the tabs are ordered on the tab bar), or _Most Recently Used Order_ (MRU). MRU means that the tab that the terminal most recently visited will be on the top of the list, and the tab that the terminal has not visited for the longest time will be on the bottom.
|
||||
|
||||
There will be an argument for the `openTabSwitcher` action called `displayOrder`. This can be either `inOrder` or `mruOrder`. Making the setting an argument passed into `openTabSwitcher` would allow the user to have one keybinding to open an MRU Tab Switcher, and different one for the In-Order Tab Switcher. For example:
|
||||
```
|
||||
{"keys": ["ctrl+tab"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"mruOrder"}}
|
||||
{"keys": ["ctrl+shift+p"], "command": {"action": "openTabSwitcher", "anchor":"ctrl", "displayOrder":"inOrder"}}
|
||||
```
|
||||
By default (when the arg isn't specified), `displayOrder` will be "mruOrder".
|
||||
|
||||
### Numbered Tabs
|
||||
|
||||
Similar to how the user can currently switch to a particular tab with a combination of keys such as <kbd>ctrl+shift+1</kbd>, we want to have the tab switcher provide a number to the first nine tabs (1-9) in the list for quick switching. If there are more than nine tabs in the list, then the rest of the tabs will not have a number assigned.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
- The tab switcher will be using WinUI, and so it'll be automatically linked to the UIA tree. This allows screen readers to find it, and so narrator will be able to navigate the switcher easily.
|
||||
- The UI is also fully keyboard-driven, with the option of using a mouse to interact with the UI.
|
||||
- When the tab switcher pops up, the focus immediately swaps to it.
|
||||
- For the sake of more contrast with the background, we could use a ThemeShadow to bring the UI closer to the user, making the focus clearer.
|
||||
|
||||
### Security
|
||||
|
||||
This shouldn't introduce any security issues.
|
||||
|
||||
### Reliability
|
||||
|
||||
How we're updating the MRU is something to watch out for since it triggers on a lot of tab interactions. However, I don't foresee the update taking long at all, and I can't imagine that users can create and delete tabs fast enough to matter.
|
||||
|
||||
### Compatibility
|
||||
|
||||
- The existing way of navigating horizontally through the tabs on the tab bar should not break.
|
||||
- These should also be separate keybindings from the keybindings associated with using the tab switcher.
|
||||
- When a user reorders their tabs on the tab bar, the MRU order remains unchanged. For example:
|
||||
- Tab Bar:`[cmd(focused), ps, wsl]` and MRU:`[cmd, ps, wsl]`
|
||||
- Reordered Tab Bar:`[wsl, cmd(focused), ps]` and MRU:`[cmd, ps, wsl]`
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
## Potential Issues
|
||||
|
||||
We'll need to be careful about how the UI is presented depending on different sizes of the terminal. We also should test how the UI looks as it's open and resizing is happening. Visual Studio's tab switcher is a fixed size, and is always in the middle. Even when the VS window is smaller than the tab switcher size, the tab switcher will show up larger than the VS window itself.
|
||||
|
||||

|
||||

|
||||
|
||||
Visual Studio Code only allows the user to shrink the window until it hits a minimum width and height. This minimum width and height gives its tab switcher enough space to show a meaningful amount of information.
|
||||
|
||||

|
||||
|
||||
Terminal can't really replicate Visual Studio's version of the tab switcher in this situation. The TabSwitcher needs to be contained within the Terminal. So, if the TabSwitcher is always centered and has a percentage padding from the borders of the Terminal, it'll shrink as Terminal shrinks. Since the Terminal also has a minimum width, the switcher should always have enough space to be usefully visible.
|
||||
|
||||
## Future considerations
|
||||
|
||||
### Pane Navigation
|
||||
|
||||
There was discussion in [#1502] that brought up the idea of pane navigation, inspired by tmux.
|
||||
|
||||

|
||||
|
||||
Tmux allows the user to navigate directly to a pane and even give a preview of the pane. This would be extremely useful since it would allow the user to see a tree of their open tabs and panes. Currently there's no way to see what panes are open in each tab, so if you're looking for a particular pane, you'd need to cycle through your tabs to find it. If something like pane profile names (not sure what information to present in the switcher for panes) were presented in the TabSwitcher, the user could see all the panes in one box.
|
||||
|
||||
To support pane navigation, the tab switcher can simply have another column to the right of the tab list to show a list of panes inside the selected tab. As the user iterates through the tab list, they can simply hit right to dig deeper into the tab's panes, and hit left to come back to the tab list. Each tab's list of panes will be MRU or in-order, depending on which `displayOrder` arg was provided to the `openTabSwitcher` keybinding.
|
||||
|
||||
Pane navigation is a clear next step to build on top of the tab switcher, but this spec will specifically deal with just tab navigation in order to keep the scope tight. The tab switcher implementation just needs to allow for pane navigation to be added in later.
|
||||
|
||||
### Tab Preview on Hover
|
||||
|
||||
With this feature, having a tab highlighted in the switcher would make the Terminal display that tab as if it switched to it. I believe currently there is no way to set focus to a tab in a "preview" mode. This is important because MRU updates whenever a tab is focused, but we don't want the MRU to update on a preview. Given that this feature is a "nice thing to have", I'll leave it for
|
||||
after the tab switcher has landed.
|
||||
|
||||
## Resources
|
||||
|
||||
Feature Request: An advanced tab switcher [#1502]
|
||||
Ctrl+Tab toggle between last two windows like Alt+Tab [#973]
|
||||
The Command Palette Thread [#2046]
|
||||
The Command Palette Spec [#5674]
|
||||
Feature Request: Search [#605]
|
||||
|
||||
<!-- Footnotes -->
|
||||
[#605]: https://github.com/microsoft/terminal/issues/605
|
||||
[#973]: https://github.com/microsoft/terminal/issues/973
|
||||
[#1502]: https://github.com/microsoft/terminal/issues/1502
|
||||
[#2046]: https://github.com/microsoft/terminal/issues/2046
|
||||
[#5674]: https://github.com/microsoft/terminal/pull/5674
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2019-08-01
|
||||
last updated: 2020-06-10
|
||||
last updated: 2020-06-16
|
||||
issue id: 2046
|
||||
---
|
||||
|
||||
@@ -522,6 +522,13 @@ default. These are largely the actions that are bound by default.
|
||||
]
|
||||
```
|
||||
|
||||
## Addenda
|
||||
|
||||
This spec also has a follow-up spec which introduces further changes upon this
|
||||
original draft. Please also refer to:
|
||||
|
||||
* June 2020: Unified keybindings and commands, and synthesized action names.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Commands will provide an easy point for allowing an extension to add its
|
||||
|
||||
@@ -0,0 +1,608 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-06-15
|
||||
last updated: 2020-06-19
|
||||
issue id: 2046
|
||||
---
|
||||
|
||||
# Command Palette, Addendum 1 - Unified keybindings and commands, and synthesized action names
|
||||
|
||||
## Abstract
|
||||
|
||||
This document is intended to serve as an addition to the [Command Palette Spec].
|
||||
While that spec is complete in it's own right, subsequent discussion revealed
|
||||
additional ways to improve the functionality and usability of the command
|
||||
palette. This document builds largely on the topics already introduced in the
|
||||
original spec, so readers should first familiarize themselves with that
|
||||
document.
|
||||
|
||||
One point of note from the original document was that the original specification
|
||||
was entirely too verbose when defining both keybindings and commands for
|
||||
actions. Consider, for instance, a user that wants to bind the action "duplicate
|
||||
the current pane". In that spec, they need to add both a keybinding and a
|
||||
command:
|
||||
|
||||
```json
|
||||
{
|
||||
"keybindings": [
|
||||
{ "keys": [ "ctrl+alt+t" ], "command": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" } },
|
||||
],
|
||||
"commands": [
|
||||
{ "name": "Duplicate Pane", "action": { "action": "splitPane", "split":"auto", "splitMode": "duplicate" }, "icon": null },
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
These two entries are practically the same, except for two key differentiators:
|
||||
* the keybinding has a `keys` property, indicating which key chord activates the
|
||||
action.
|
||||
* The command has a `name` property, indicating what name to display for the
|
||||
command in the Command Palette.
|
||||
|
||||
What if the user didn't have to duplicate this action? What if the user could
|
||||
just add this action once, in their `keybindings` or `commands`, and have it
|
||||
work both as a keybinding AND a command?
|
||||
|
||||
|
||||
## Solution Design
|
||||
|
||||
This spec will outline two primary changes to keybindings and commands.
|
||||
1. Unify keybindings and commands, so both `keybindings` and `commands` can
|
||||
specify either actions bound to keys, and/or actions bound to entries in the
|
||||
Command Palette.
|
||||
2. Propose a mechanism by which actions do not _require_ a `name` to appear in
|
||||
the Command Palette.
|
||||
|
||||
These proposals are two atomic units - either could be approved or rejected
|
||||
independently of one another. They're presented together here in the same doc
|
||||
because together, they present a compelling story.
|
||||
|
||||
### Proposal 1: Unify Keybindings and Commands
|
||||
|
||||
As noted above, keybindings and commands have nearly the exact same syntax, save
|
||||
for a couple properties. To make things easier for the user, I'm proposing
|
||||
treating everything in _both_ the `keybindings` _and_ the `commands` arrays as
|
||||
**BOTH** a keybinding and a command.
|
||||
|
||||
Furthermore, as a change from the previous spec, we'll be using `bindings` from
|
||||
here on as the unified `keybindings` and `commands` lists. This is considering
|
||||
that we'll currently be using `bindings` for both commands and keybindings, but
|
||||
we'll potentially also have mouse & touch bindings in this array in the future.
|
||||
We'll "deprecate" the existing `keybindings` property, and begin to exclusively
|
||||
use `bindings` as the new property name. For compatibility reasons, we'll
|
||||
continue to parse `keybindings` in the same way we parse `bindings`. We'll
|
||||
simply layer `bindings` on top of the legacy `keybindings`.
|
||||
|
||||
* Anything entry that has a `keys` value will be added to the keybindings.
|
||||
Pressing that keybinding will activate the action defined in `command`.
|
||||
* Anything with a `name`<sup>[1]</sup> will be added as an entry (using that
|
||||
name) to the Command Palette's Action Mode.
|
||||
|
||||
###### Caveats
|
||||
|
||||
* **Nested commands** (commands with other nested commands). If a command has
|
||||
nested commands in the `commands` property, AND a `keys` property, then
|
||||
pressing that keybinding should open the Command Palette directly to that
|
||||
level of nesting of commands.
|
||||
* **"Iterable" commands** (with an `iterateOn` property): These are commands
|
||||
that are expanded into one command per profile. These cannot really be bound
|
||||
as keybindings - which action should be bound to the key? They can't all be
|
||||
bound to the same key. If a KeyBinding/Command json blob has a valid
|
||||
`iterateOn` property, then we'll ignore it as a keybinding. This includes any
|
||||
commands that are nested as children of this command - we won't be able to
|
||||
know which of the expanded children will be the one to bind the keys to.
|
||||
|
||||
<sup>[1]</sup>: This requirement will be relaxed given **Proposal 2**, below,
|
||||
but ignored for the remainder of this section, for illustrative purposes.
|
||||
|
||||
#### Example
|
||||
|
||||
Consider the following settings:
|
||||
|
||||
```json
|
||||
"bindings": [
|
||||
{ "name": "Duplicate Tab", "command": "duplicateTab", "keys": "ctrl+alt+a" },
|
||||
{ "command": "nextTab", "keys": "ctrl+alt+b" },
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "NewTabWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "NewTabWithProfileCommandName" },
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": "Connect to ssh...",
|
||||
"commands": [
|
||||
{
|
||||
"keys": "ctrl+alt+c",
|
||||
"icon": "...",
|
||||
"name": "first.com",
|
||||
"command": { "action": "newTab", "commandline": "ssh me@first.com" }
|
||||
},
|
||||
{
|
||||
"keys": "ctrl+alt+d",
|
||||
"icon": "...",
|
||||
"name": "second.com",
|
||||
"command": { "action": "newTab", "commandline": "ssh me@second.com" }
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"keys": "ctrl+alt+e",
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "SplitPaneWithProfileCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"keys": "ctrl+alt+f",
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneVerticalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
|
||||
},
|
||||
{
|
||||
"icon": "...",
|
||||
"name": { "key": "SplitPaneHorizontalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
This will generate a tree of commands as follows:
|
||||
|
||||
```
|
||||
<Command Palette>
|
||||
├─ Duplicate tab { ctrl+alt+a }
|
||||
├─ New Tab With Profile...
|
||||
│ ├─ Profile 1
|
||||
│ ├─ Profile 2
|
||||
│ └─ Profile 3
|
||||
├─ Connect to ssh...
|
||||
│ ├─ first.com { ctrl+alt+c }
|
||||
│ └─ second.com { ctrl+alt+d }
|
||||
└─ New Pane... { ctrl+alt+e }
|
||||
├─ Profile 1...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
├─ Profile 2...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
└─ Profile 3...
|
||||
├─ Split Automatically
|
||||
├─ Split Vertically
|
||||
└─ Split Horizontally
|
||||
```
|
||||
|
||||
Note also the keybindings in the above example:
|
||||
* <kbd>ctrl+alt+a</kbd>: This key chord is bound to the "Duplicate tab"
|
||||
(`duplicateTab`) action, which is also bound to the command with the same
|
||||
name.
|
||||
* <kbd>ctrl+alt+b</kbd>: This key chord is bound to the `nextTab` action, which
|
||||
doesn't have an associated command.
|
||||
* <kbd>ctrl+alt+c</kbd>: This key chord is bound to the "Connect to
|
||||
ssh../first.com" action, which will open a new tab with the `commandline`
|
||||
`"ssh me@first.com"`. When the user presses this keybinding, the action will
|
||||
be executed immediately, without the Command Palette appearing.
|
||||
* <kbd>ctrl+alt+d</kbd>: This is the same as the above, but with the "Connect to
|
||||
ssh../second.com" action.
|
||||
* <kbd>ctrl+alt+e</kbd>: This key chord is bound to opening the Command Palette
|
||||
to the "New Pane..." command's menu. When the user presses this keybinding,
|
||||
they'll be prompted with this command's sub-commands:
|
||||
```
|
||||
Profile 1...
|
||||
Profile 2...
|
||||
Profile 3...
|
||||
```
|
||||
* <kbd>ctrl+alt+f</kbd>: This key will _not_ be bound to any action. The parent
|
||||
action is iterable, which means that the `SplitPaneName` command is going to
|
||||
get turned into one command for each and every profile, and therefore cannot
|
||||
be bound to just a single action.
|
||||
|
||||
### Proposal 2: Automatically synthesize action names
|
||||
|
||||
Previously, all Commands were required to have a `name`. This name was used as
|
||||
the text for the action in the Action Mode of the Command Palette. However, this
|
||||
is a little tedious for users who already have lots of keys bound. They'll need
|
||||
to go through and add names to each of their existing keybindings to ensure that
|
||||
the actions appear in the palette. Could we instead synthesize the names for the
|
||||
commands ourselves? This would enable users to automatically get each of their
|
||||
existing keybindings to appear in the palette without any extra work.
|
||||
|
||||
To support this, the following changes will be made:
|
||||
* `ActionAndArgs` will get a `GenerateName()` method defined. This will create a
|
||||
string describing the `ShortcutAction` and it's associated `ActionArgs`.
|
||||
- Not EVERY action _needs_ to define a result for `GenerateName`. Actions that
|
||||
don't _won't_ be automatically added to the Command Palette.
|
||||
- Each of the strings used in `GenerateName` will need to come from our
|
||||
resources, so they can be localized appropriately.
|
||||
* When we're parsing commands, if a command doesn't have a `name`, we'll instead
|
||||
attempt to use `GenerateName` to create the unique string for the action
|
||||
associated with this command. If the command does have a `name` set, we'll use
|
||||
that string instead, allowing the user to override the default name.
|
||||
- If a command has it's name set to `null`, then we'll ignore the command
|
||||
entirely, not just use the generated name.
|
||||
|
||||
[**Appendix 1**](#appendix-1-name-generation-samples-for-ShortcutActions) below
|
||||
shows a complete sample of the strings that will be generated for each of the existing
|
||||
`ShortcutActions`, and many of the actions that have been proposed, but not yet
|
||||
implemented.
|
||||
|
||||
These strings should be human-friendly versions of the actions and their
|
||||
associated args. For some of these actions, with very few arguments, the strings
|
||||
can be relatively simple. Take for example, `CopyText`:
|
||||
|
||||
JSON | Generated String
|
||||
-- | --
|
||||
`{ "action":"copyText" }` | "Copy text"
|
||||
`{ "action":"copyText", "singleLine": true }` | "Copy text as a single line"
|
||||
`{ "action":"copyText", "singleLine": false, "copyFormatting": false }` | "Copy text without formatting"
|
||||
`{ "action":"copyText", "singleLine": true, "copyFormatting": true }` | "Copy text as a single line without formatting"
|
||||
|
||||
CopyText is a bit of a simplistic case however, with very few args or
|
||||
permutations of argument values. For things like `newTab`, `splitPane`, where
|
||||
there are many possible arguments and values, it will be acceptable to simply
|
||||
append `", property:value"` strings to the generated names for each of the set
|
||||
values.
|
||||
|
||||
For example:
|
||||
|
||||
JSON | Generated String
|
||||
-- | --
|
||||
`{ "action":"newTab", "profile": "Hello" }` | "Open a new tab, profile:Hello"
|
||||
`{ "action":"newTab", "profile": "Hello", "directory":"C:\\", "commandline": "wsl.exe", title": "Foo" }` | "Open a new tab, profile:Hello, directory:C:\\, commandline:wsl.exe, title:Foo"
|
||||
|
||||
|
||||
This is being chosen in favor of something that might be more human-friendly,
|
||||
like "Open a new tab with profile {profile name} in {directory} with
|
||||
{commandline} and a title of {title}". This string would be much harder to
|
||||
synthesize, especially considering localization concerns.
|
||||
|
||||
#### Remove the resource key notation
|
||||
|
||||
Since we'll be using localized names for each of the actions in `GenerateName`,
|
||||
we no longer _need_ to provide the `{ "name":{ "key": "SomeResourceKey" } }`
|
||||
syntax introduced in the original spec. This functionality was used to allow us
|
||||
to define localizable names for the default commands.
|
||||
|
||||
However, I think we should keep this functionality, to allow us additional
|
||||
flexibility when defining default commands.
|
||||
|
||||
### Complete Defaults
|
||||
|
||||
Considering both of the above proposals, the default keybindings and commands
|
||||
will be defined as follows:
|
||||
|
||||
* The current default keybindings will be untouched. These actions will
|
||||
automatically be added to the Command Palette, using their names generated
|
||||
from `GenerateName`.
|
||||
- **TODO: FOR DISCUSSION**: Should we manually set the names for the default
|
||||
"New Tab, profile index: 0" keybindings to `null`? This seems like a not
|
||||
terribly helpful name for the Command Palette, especially considering the
|
||||
iterable commands listed below.
|
||||
* We'll add a few new commands:
|
||||
- A nested, iterable command for "Open new tab with
|
||||
profile..."/"Profile:{profile name}"
|
||||
- A nested, iterable command for "Select color scheme..."/"{scheme name}"
|
||||
- A nested, iterable command for "New Pane..."/"Profile:{profile
|
||||
name}..."/["Automatic", "Horizontal", "Vertical"]
|
||||
> 👉 NOTE: These default nested commands can be removed by the user defining
|
||||
> `{ "name": "Open new tab with profile...", "action":null }` (et al) in their
|
||||
> settings.
|
||||
- If we so chose, in the future we can add further commands that we think are
|
||||
helpful to `defaults.json`, without needing to give them keys. For example,
|
||||
we could add
|
||||
```json
|
||||
{ "command": { "action": "copy", "singleLine": true } }
|
||||
```
|
||||
to `bindings`, to add a "copy text as a single line" command, without
|
||||
necessarily binding it to a keystroke.
|
||||
|
||||
|
||||
These changes to the `defaults.json` are represented in json as the following:
|
||||
|
||||
```json
|
||||
"bindings": [
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "NewTabWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": "${profile.name}",
|
||||
"command": { "action": "newTab", "profile": "${profile.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SelectColorSchemeRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "schemes",
|
||||
"icon": null,
|
||||
"name": "${scheme.name}",
|
||||
"command": { "action": "selectColorScheme", "scheme": "${scheme.name}" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneWithProfileRootCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"iterateOn": "profiles",
|
||||
"icon": "${profile.icon}",
|
||||
"name": { "key": "SplitPaneWithProfileCommandName" },
|
||||
"commands": [
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "automatic" }
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneVerticalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "vertical" }
|
||||
},
|
||||
{
|
||||
"icon": null,
|
||||
"name": { "key": "SplitPaneHorizontalName" },
|
||||
"command": { "action": "splitPane", "profile": "${profile.name}", "split": "horizontal" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
A complete diagram of what the default Command Palette will look like given the
|
||||
default keybindings and these changes is given in [**Appendix
|
||||
2**](#appendix-2-complete-default-command-palette).
|
||||
|
||||
## Concerns
|
||||
|
||||
**DISCUSSION**: "New tab with index {index}". How does this play with
|
||||
the new tab dropdown customizations in [#5888]? In recent iterations of that
|
||||
spec, we changed the meaning of `{ "action": "newTab", "index": 1 }` to mean
|
||||
"open the first entry in the new tab menu". If that's a profile, then we'll open
|
||||
a new tab with it. If it's an action, we'll perform that action. If it's a
|
||||
nested menu, then we'll open the menu to that entry.
|
||||
|
||||
Additionally, how exactly does that play with something like `{ "action":
|
||||
"newTab", "index": 1, "commandline": "wsl.exe" }`? This is really a discussion
|
||||
for that spec, but is an issue highlighted by this spec. If the first entry is
|
||||
anything other than a `profile`, then the `commandline` parameter doesn't really
|
||||
mean anything anymore. I'm tempted to revert this particular portion of the new
|
||||
tab menu customization spec over this.
|
||||
|
||||
We could instead add an `index` to `openNewTabDropdown`, and have that string
|
||||
instead be "Open new tab dropdown, index:1". That would help disambiguate the
|
||||
two.
|
||||
|
||||
Following discussion, it was decided that this was in fact the cleanest
|
||||
solution, when accounting for both the needs of the new tab dropdown and the
|
||||
command palette. The [#5888] spec has been updated to reflect this.
|
||||
|
||||
## Future considerations
|
||||
|
||||
* Some of these command names are starting to get _very_ long. Perhaps we need a
|
||||
netting to display Command Palette entries on two lines (or multiple, as
|
||||
necessary).
|
||||
* When displaying the entries of a nested command to the user, should we display
|
||||
a small label showing the name of the previous command? My gut says _yes_. In
|
||||
the Proposal 1 example, pressing `ctrl+alt+e` to jump to "Split Pane..."
|
||||
should probably show a small label that displays "Split Pane..." above the
|
||||
list of nested commands.
|
||||
* It wouldn't be totally impossible to allow keys to be bound to an iterable
|
||||
command, and then simply have the key work as "open the command palette with
|
||||
only the commands generated by this iterable command". This is left as a
|
||||
future option, as it might require some additional technical plumbing.
|
||||
|
||||
## Appendix 1: Name generation samples for `ShortcutAction`s
|
||||
|
||||
### Current `ShortcutActions`
|
||||
|
||||
* `CopyText`
|
||||
- "Copy text"
|
||||
- "Copy text as a single line"
|
||||
- "Copy text without formatting"
|
||||
- "Copy text as a single line without formatting"
|
||||
* `PasteText`
|
||||
- "Paste text"
|
||||
* `OpenNewTabDropdown`
|
||||
- "Open new tab dropdown"
|
||||
* `DuplicateTab`
|
||||
- "Duplicate tab"
|
||||
* `NewTab`
|
||||
- "Open a new tab, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `NewWindow`
|
||||
- "Open a new window"
|
||||
- "Open a new window, profile:{profile name}, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `CloseWindow`
|
||||
- "Close window"
|
||||
* `CloseTab`
|
||||
- "Close tab"
|
||||
* `ClosePane`
|
||||
- "Close pane"
|
||||
* `NextTab`
|
||||
- "Switch to the next tab"
|
||||
* `PrevTab`
|
||||
- "Switch to the previous tab"
|
||||
* `SplitPane`
|
||||
- "Open a new pane, profile:{profile name}, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
- "Duplicate the current pane, split direction:{direction}, split size:{X%/Y chars}, resize parents, directory:{directory}, commandline:{commandline}, title:{title}"
|
||||
* `SwitchToTab`
|
||||
- "Switch to tab {index}"
|
||||
* `AdjustFontSize`
|
||||
- "Increase the font size"
|
||||
- "Decrease the font size"
|
||||
* `ResetFontSize`
|
||||
- "Reset the font size"
|
||||
* `ScrollUp`
|
||||
- "Scroll up a line"
|
||||
- "Scroll up {amount} lines"
|
||||
* `ScrollDown`
|
||||
- "Scroll down a line"
|
||||
- "Scroll down {amount} lines"
|
||||
* `ScrollUpPage`
|
||||
- "Scroll up a page"
|
||||
- "Scroll up {amount} pages"
|
||||
* `ScrollDownPage`
|
||||
- "Scroll down a page"
|
||||
- "Scroll down {amount} pages"
|
||||
* `ResizePane`
|
||||
- "Resize pane {direction}"
|
||||
- "Resize pane {direction} {percent}%"
|
||||
* `MoveFocus`
|
||||
- "Move focus {direction}"
|
||||
* `Find`
|
||||
- "Toggle the search box"
|
||||
* `ToggleFullscreen`
|
||||
- "Toggle fullscreen mode"
|
||||
* `OpenSettings`
|
||||
- "Open settings"
|
||||
- "Open settings file"
|
||||
- "Open default settings file"
|
||||
* `ToggleCommandPalette`
|
||||
- "Toggle the Command Palette"
|
||||
- "Toggle the Command Palette in commandline mode"
|
||||
|
||||
### Other yet unimplemented actions:
|
||||
* `SwitchColorScheme`
|
||||
- "Select color scheme {name}"
|
||||
* `ToggleRetroEffect`
|
||||
- "Toggle the retro terminal effect"
|
||||
* `ExecuteCommandline`
|
||||
- "Run a wt commandline: {cmdline}"
|
||||
* `ExecuteActions`
|
||||
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
|
||||
default. The user knows what they're putting in the settings by adding this
|
||||
action, let them name it.
|
||||
- Alternatively: "Run actions: {action.ToName() for action in actions}"
|
||||
* `SendInput`
|
||||
- OPINION: THIS ONE SHOULDN'T HAVE A NAME. We're not including any of these by
|
||||
default. The user knows what they're putting in the settings by adding this
|
||||
action, let them name it.
|
||||
* `ToggleMarkMode`
|
||||
- "Toggle Mark Mode"
|
||||
* `NextTab`
|
||||
- "Switch to the next most-recent tab"
|
||||
* `SetTabColor`
|
||||
- "Set the color of the current tab to {#color}"
|
||||
* It would be _really_ cool if we could display a sample of the color
|
||||
inline, but that's left as a future consideration.
|
||||
- "Set the color for this tab..."
|
||||
* this command isn't nested, but hitting enter immediately does something
|
||||
with the UI, so that's _fine_
|
||||
* `RenameTab`
|
||||
- "Rename this tab to {name}"
|
||||
- "Rename this tab..."
|
||||
* this command isn't nested, but hitting enter immediately does something
|
||||
with the UI, so that's _fine_
|
||||
|
||||
|
||||
## Appendix 2: Complete Default Command Palette
|
||||
|
||||
This diagram shows what the default value of the Command Palette would be. This
|
||||
assumes that the user has 3 profiles, "Profile 1", "Profile 2", and "Profile 3",
|
||||
as well as 3 schemes: "Scheme 1", "Scheme 2", and "Scheme 3".
|
||||
|
||||
```
|
||||
<Command Palette>
|
||||
├─ Close Window
|
||||
├─ Toggle fullscreen mode
|
||||
├─ Open new tab dropdown
|
||||
├─ Open settings
|
||||
├─ Open default settings file
|
||||
├─ Toggle the search box
|
||||
├─ New Tab
|
||||
├─ New Tab, profile index: 0
|
||||
├─ New Tab, profile index: 1
|
||||
├─ New Tab, profile index: 2
|
||||
├─ New Tab, profile index: 3
|
||||
├─ New Tab, profile index: 4
|
||||
├─ New Tab, profile index: 5
|
||||
├─ New Tab, profile index: 6
|
||||
├─ New Tab, profile index: 7
|
||||
├─ New Tab, profile index: 8
|
||||
├─ Duplicate tab
|
||||
├─ Switch to the next tab
|
||||
├─ Switch to the previous tab
|
||||
├─ Switch to tab 0
|
||||
├─ Switch to tab 1
|
||||
├─ Switch to tab 2
|
||||
├─ Switch to tab 3
|
||||
├─ Switch to tab 4
|
||||
├─ Switch to tab 5
|
||||
├─ Switch to tab 6
|
||||
├─ Switch to tab 7
|
||||
├─ Switch to tab 8
|
||||
├─ Close pane
|
||||
├─ Open a new pane, split: horizontal
|
||||
├─ Open a new pane, split: vertical
|
||||
├─ Duplicate the current pane
|
||||
├─ Resize pane down
|
||||
├─ Resize pane left
|
||||
├─ Resize pane right
|
||||
├─ Resize pane up
|
||||
├─ Move focus down
|
||||
├─ Move focus left
|
||||
├─ Move focus right
|
||||
├─ Move focus up
|
||||
├─ Copy Text
|
||||
├─ Paste Text
|
||||
├─ Scroll down a line
|
||||
├─ Scroll down a page
|
||||
├─ Scroll up a line
|
||||
├─ Scroll up a page
|
||||
├─ Increase the font size
|
||||
├─ Decrease the font size
|
||||
├─ Reset the font size
|
||||
├─ New Tab With Profile...
|
||||
│ ├─ Profile 1
|
||||
│ ├─ Profile 2
|
||||
│ └─ Profile 3
|
||||
├─ Select Color Scheme...
|
||||
│ ├─ Scheme 1
|
||||
│ ├─ Scheme 2
|
||||
│ └─ Scheme 3
|
||||
└─ New Pane...
|
||||
├─ Profile 1...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
├─ Profile 2...
|
||||
| ├─ Split Automatically
|
||||
| ├─ Split Vertically
|
||||
| └─ Split Horizontally
|
||||
└─ Profile 3...
|
||||
├─ Split Automatically
|
||||
├─ Split Vertically
|
||||
└─ Split Horizontally
|
||||
```
|
||||
|
||||
|
||||
<!-- Footnotes -->
|
||||
[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md
|
||||
122
doc/specs/#980 - SnapOnOutput.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
author: Carlos Zamora @carlos-zamora
|
||||
created on: 2019-08-22
|
||||
last updated: 2020-07-06
|
||||
issue id: 980
|
||||
---
|
||||
|
||||
# Snap On Output
|
||||
|
||||
## Abstract
|
||||
|
||||
The goal of this change is to determine the Terminal's scroll response to newly generated output.
|
||||
|
||||
Currently, new output causes the Terminal to always scroll to it. Some users want to be able to scroll through the buffer without interruptions.
|
||||
|
||||
## Inspiration
|
||||
|
||||
In ConHost, a selection causes the active process to be completely paused. When the selection is removed, the process continues.
|
||||
|
||||
Typical Unix terminals work differently. Rather than disabling the output, they disable the automatic scrolling. This allows the user to continue to see more output by choice.
|
||||
|
||||
## Solution Design
|
||||
|
||||
By default, the viewport will scroll to new output if the following conditions are met:
|
||||
- no selection is active
|
||||
- the viewport is at the "virtual bottom" (the bottom of the scroll history)
|
||||
|
||||
This behavior will not be configurable. If the user wants the viewport to stop autoscrolling, the user will simply create a selection or scroll any distance above the virtual bottom. Conversely, if the user wants the viewport to automatically scroll, the user must scroll to the bottom. Scrolling to the bottom is most easily achieved using the `snapOnInput` functionality.
|
||||
|
||||
Alternative solutions were considered and are recorded below. These solutions may be revisited if users desire an additional level of configurability.
|
||||
|
||||
Researching other terminal emulators has shown that this behavior is not configurable.
|
||||
|
||||
## Alternative Solutions
|
||||
|
||||
### Solution 1: `snapOnOutput` profile setting - enum flags
|
||||
`SnapOnOutput` will be a profile-level `ICoreSettings` setting of type enum or enum array. It can be set to one or multiple of the following values:
|
||||
- `never`: new output does not cause the viewport to update to the bottom of the scroll region
|
||||
- `noSelection`: new output causes the viewport to update to the bottom of the scroll region **IF** no selection is active
|
||||
- `atBottom`: new output causes the viewport to update **IF** the viewport is already at the virtual bottom
|
||||
- `always`: new output causes the viewport to update to the bottom of the scroll region
|
||||
|
||||
The `TerminalCore` is responsible for moving the viewport on a scroll event. All of the logic for this feature should be handled here.
|
||||
|
||||
A new private enum array `_snapOnOutput` will be introduced to save which of these settings are included. The `_NotifyScrollEvent()` calls (and nearby code) will be surrounded by conditional checks for the enums above. This allows it to be used to determine if the viewport should update given a specific situation.
|
||||
|
||||
The `snapOnOutput` setting is introduced as a profile setting to match `snapOnInput`.
|
||||
|
||||
The default `snapOnOutput` value will be `[ "noSelection", "atBottom" ]`.
|
||||
|
||||
When an enum array is defined in the settings, it will be interpreted using boolean logic. The following scenarios will be invalid using the FlagMapper:
|
||||
- `[ "always", "atBottom" ]`
|
||||
- `[ "never", "atBottom" ]`
|
||||
|
||||
### Solution 2: `scrollLock` keybinding action
|
||||
|
||||
A `scrollLock` keybinding action would toggle automatically scrolling to new output.
|
||||
|
||||
**NOTE**: This can be easily confused with the <kbd>ScrollLock</kbd> key. Researching the use of the <kbd>ScrollLock</kbd> key has shown that programs rarely use this key. In most apps, pressing the <kbd>ScrollLock</kbd> key does not actually prevent scrolling the application. Additionally, finding a way to bing the `scrollLock` action to the <kbd>ScrollLock</kbd> key would be difficult. A physical keyboard may not necessarily have a <kbd>ScrollLock</kbd> key. Also, we would have to poll for the internal state of "is the scroll lock key enabled", which may change while the user is not necessarily using Terminal.
|
||||
|
||||
The introduction of a `scrollLock` action would require a visual indicator for the user to know when scrolling has been disabled. However, this introduces a number of problems:
|
||||
- if the indicator is persistent, it may block the view
|
||||
- if the indicator is not persistent, the user may be unaware of being in a state where scrolling doesn't work properly
|
||||
|
||||
**Additionally relevant research**:
|
||||
- In Unix consoles, <kbd>ctrl+s</kbd> and <kbd>ctrl+q</kbd> freeze and unfreeze output respectively. However, this is a feature that is implemented outside of the scope for Terminal. Other shells like PowerShell do not have this feature, for example. There, <kbd>ctrl+s</kbd> does a 'Forward Search History' instead.
|
||||
- Additionally, there is a <kbd>Pause</kbd> key that pauses the output in the conhost console. Pressing any other key will resume scrolling.
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Accessibility
|
||||
|
||||
N/A
|
||||
|
||||
### Security
|
||||
|
||||
N/A
|
||||
|
||||
### Reliability
|
||||
|
||||
N/A
|
||||
|
||||
### Compatibility
|
||||
|
||||
N/A
|
||||
|
||||
### Performance, Power, and Efficiency
|
||||
|
||||
N/A
|
||||
|
||||
## Potential Issues
|
||||
|
||||
### Circling the buffer
|
||||
If the text buffer fills up, the text buffer begins 'circling'. This means that new output shifts lines of the buffer up to make space. In a case like this, if `snapOnOutput` is set to `never`, the viewport should actually scroll up to keep the same content on the viewport.
|
||||
|
||||
In the event that the buffer is circling and the viewport has been moved to the top of the buffer, that content of the buffer is now lost (as the 'Infinite Scrollback' feature does not exist or is disabled). At that point, the viewport will remain at the top of the buffer and the new output will push old output out of the buffer.
|
||||
|
||||
### Infinite Scrollback
|
||||
See **Future considerations** > **Infinite Scrollback**.
|
||||
|
||||
## Future considerations
|
||||
|
||||
### Extensibility
|
||||
The introduction of `enum SnapOnOutput` allows for this feature to be enabled/disabled in more complex scenarios. A potential extension would be to introduce a new UI element or keybinding to toggle this feature.
|
||||
|
||||
### Infinite Scrollback
|
||||
At the time of introducing this, the infinite scrollback feature is not supported. This means that the buffer saves the history up to the `historySize` amount of lines. When infinite scrollback is introduced, the buffer needs to change its own contents to allow the user to scroll beyond the `historySize`. With infinite scrollback enabled and the mutable viewport **NOT** snapping to new output, the `TerminalCore` needs to keep track of...
|
||||
- what contents are currently visible to the user (in the current location of the mutable viewport)
|
||||
- how to respond to a user's action of changing the location of the mutable viewport (i.e.: snapOnInput, scroll up/down)
|
||||
|
||||
### Private Mode Escape Sequences
|
||||
There are a couple of private mode escape sequences that some terminals use to control this kind of thing. DECSET 1010, for example, snaps the viewport to the bottom on output, whereas DECSET 1011 spans the viewport to the bottom on a keypress.
|
||||
|
||||
DECSET 1010 should set the `SnapOnOutput` value via a Terminal API.
|
||||
DECSET 1011 should set the `SnapOnInput` value via a Terminal API.
|
||||
|
||||
## Resources
|
||||
|
||||
[GH#980](https://github.com/microsoft/terminal/issues/980)
|
||||
[DECSET 1010](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-?-Pm-h:Ps-=-1-0-1-0.1F79)
|
||||
[DECSET 1011](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h4-Functions-using-CSI-_-ordered-by-the-final-character-lparen-s-rparen:CSI-?-Pm-h:Ps-=-1-0-1-1.1F7A)
|
||||
@@ -7,7 +7,7 @@ This document outlines the roadmap towards delivering Windows Terminal 2.0 by Sp
|
||||
|
||||
## Milestones
|
||||
|
||||
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
The Windows Terminal project is engineered and delivered as a set of 4-week milestones. New features will go into [Windows Terminal Preview](https://aka.ms/terminal-preview) first, then a month after they've been in Preview, those features will move into [Windows Terminal](https://aka.ms/terminal).
|
||||
|
||||
| Duration | Activity | Releases |
|
||||
| --- | --- | --- |
|
||||
@@ -21,7 +21,7 @@ Below is the schedule for when milestones will be included in release builds of
|
||||
|
||||
| Milestone End Date | Milestone Name | Preview Release Blog Post |
|
||||
| ------------------ | -------------- | ------------------------- |
|
||||
| 2020-06-30 | [1.1] in Windows Terminal Preview | |
|
||||
| 2020-06-18 | [1.1] in Windows Terminal Preview | [Windows Terminal Preview 1.1 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-1-release/) |
|
||||
| 2020-07-31 | [1.2] in Windows Terminal Preview<br>[1.1] in Windows Terminal | |
|
||||
| 2020-08-31 | 1.3 in Windows Terminal Preview<br>[1.2] in Windows Terminal | |
|
||||
| 2020-09-30 | 1.4 in Windows Terminal Preview<br>1.3 in Windows Terminal | |
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "git",
|
||||
"git": {
|
||||
"repositoryUrl": "https://github.com/fmtlib/fmt",
|
||||
"commitHash": "9bdd1596cef1b57b9556f8bef32dc4a32322ef3e"
|
||||
"commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
// From fits in To without any problem.
|
||||
} else {
|
||||
// From does not always fit in To, resort to a dynamic check.
|
||||
if (from < T::min() || from > T::max()) {
|
||||
if (from < (T::min)() || from > (T::max)()) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
@@ -74,7 +74,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (fmt::internal::is_negative(from)) {
|
||||
if (fmt::detail::is_negative(from)) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
@@ -84,7 +84,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>(T::max())) {
|
||||
if (from > static_cast<From>((T::max)())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
@@ -97,7 +97,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
// yes, From always fits in To.
|
||||
} else {
|
||||
// from may not fit in To, we have to do a dynamic check
|
||||
if (from > static_cast<From>(T::max())) {
|
||||
if (from > static_cast<From>((T::max)())) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
@@ -141,7 +141,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
|
||||
// catch the only happy case
|
||||
if (std::isfinite(from)) {
|
||||
if (from >= T::lowest() && from <= T::max()) {
|
||||
if (from >= T::lowest() && from <= (T::max)()) {
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
// not within range.
|
||||
@@ -195,12 +195,13 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
||||
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
const auto min1 =
|
||||
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
@@ -269,7 +270,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 = internal::max_value<IntermediateRep>() /
|
||||
constexpr auto max1 = detail::max_value<IntermediateRep>() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
@@ -306,12 +307,12 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
// Usage: f FMT_NOMACRO()
|
||||
#define FMT_NOMACRO
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||
inline null<> localtime_s(...) { return null<>(); }
|
||||
inline null<> gmtime_r(...) { return null<>(); }
|
||||
inline null<> gmtime_s(...) { return null<>(); }
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
// Thread-safe replacement for std::localtime
|
||||
inline std::tm localtime(std::time_t time) {
|
||||
@@ -322,22 +323,22 @@ inline std::tm localtime(std::time_t time) {
|
||||
dispatcher(std::time_t t) : time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
using namespace fmt::detail;
|
||||
return handle(localtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
bool handle(detail::null<>) {
|
||||
using namespace fmt::detail;
|
||||
return fallback(localtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VER
|
||||
bool fallback(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
bool fallback(detail::null<>) {
|
||||
using namespace fmt::detail;
|
||||
std::tm* tm = std::localtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != nullptr;
|
||||
@@ -359,21 +360,21 @@ inline std::tm gmtime(std::time_t time) {
|
||||
dispatcher(std::time_t t) : time_(t) {}
|
||||
|
||||
bool run() {
|
||||
using namespace fmt::internal;
|
||||
using namespace fmt::detail;
|
||||
return handle(gmtime_r(&time_, &tm_));
|
||||
}
|
||||
|
||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||
|
||||
bool handle(internal::null<>) {
|
||||
using namespace fmt::internal;
|
||||
bool handle(detail::null<>) {
|
||||
using namespace fmt::detail;
|
||||
return fallback(gmtime_s(&tm_, &time_));
|
||||
}
|
||||
|
||||
bool fallback(int res) { return res == 0; }
|
||||
|
||||
#if !FMT_MSC_VER
|
||||
bool fallback(internal::null<>) {
|
||||
bool fallback(detail::null<>) {
|
||||
std::tm* tm = std::gmtime(&time_);
|
||||
if (tm) tm_ = *tm;
|
||||
return tm != nullptr;
|
||||
@@ -386,17 +387,17 @@ inline std::tm gmtime(std::time_t time) {
|
||||
return gt.tm_;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
inline std::size_t strftime(char* str, std::size_t count, const char* format,
|
||||
const std::tm* time) {
|
||||
namespace detail {
|
||||
inline size_t strftime(char* str, size_t count, const char* format,
|
||||
const std::tm* time) {
|
||||
return std::strftime(str, count, format, time);
|
||||
}
|
||||
|
||||
inline std::size_t strftime(wchar_t* str, std::size_t count,
|
||||
const wchar_t* format, const std::tm* time) {
|
||||
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||
const std::tm* time) {
|
||||
return std::wcsftime(str, count, format, time);
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char> struct formatter<std::tm, Char> {
|
||||
template <typename ParseContext>
|
||||
@@ -405,7 +406,7 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
if (it != ctx.end() && *it == ':') ++it;
|
||||
auto end = it;
|
||||
while (end != ctx.end() && *end != '}') ++end;
|
||||
tm_format.reserve(internal::to_unsigned(end - it + 1));
|
||||
tm_format.reserve(detail::to_unsigned(end - it + 1));
|
||||
tm_format.append(it, end);
|
||||
tm_format.push_back('\0');
|
||||
return end;
|
||||
@@ -414,11 +415,10 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
std::size_t start = buf.size();
|
||||
size_t start = buf.size();
|
||||
for (;;) {
|
||||
std::size_t size = buf.capacity() - start;
|
||||
std::size_t count =
|
||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
size_t size = buf.capacity() - start;
|
||||
size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||
if (count != 0) {
|
||||
buf.resize(start + count);
|
||||
break;
|
||||
@@ -430,7 +430,7 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
// https://github.com/fmtlib/fmt/issues/367
|
||||
break;
|
||||
}
|
||||
const std::size_t MIN_GROWTH = 10;
|
||||
const size_t MIN_GROWTH = 10;
|
||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||
}
|
||||
return std::copy(buf.begin(), buf.end(), ctx.out());
|
||||
@@ -439,7 +439,7 @@ template <typename Char> struct formatter<std::tm, Char> {
|
||||
basic_memory_buffer<Char> tm_format;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
template <typename Period> FMT_CONSTEXPR const char* get_units() {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -768,19 +768,25 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
|
||||
val);
|
||||
}
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
||||
return std::copy(unit.begin(), unit.end(), out);
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
||||
// This works when wchar_t is UTF-32 because units only contain characters
|
||||
// that have the same representation in UTF-16 and UTF-32.
|
||||
utf8_to_utf16 u(unit);
|
||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename Period, typename OutputIt>
|
||||
OutputIt format_duration_unit(OutputIt out) {
|
||||
if (const char* unit = get_units<Period>()) {
|
||||
string_view s(unit);
|
||||
if (const_check(std::is_same<Char, wchar_t>())) {
|
||||
utf8_to_utf16 u(s);
|
||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||
}
|
||||
return std::copy(s.begin(), s.end(), out);
|
||||
}
|
||||
if (const char* unit = get_units<Period>())
|
||||
return copy_unit(string_view(unit), out, Char());
|
||||
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
|
||||
if (Period::den == 1) return format_to(out, num_f, Period::num);
|
||||
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num);
|
||||
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
|
||||
return format_to(out, num_def_f, Period::num, Period::den);
|
||||
}
|
||||
@@ -874,9 +880,9 @@ struct chrono_formatter {
|
||||
if (isnan(value)) return write_nan();
|
||||
uint32_or_64_or_128_t<int> n =
|
||||
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
||||
int num_digits = internal::count_digits(n);
|
||||
int num_digits = detail::count_digits(n);
|
||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
||||
out = format_decimal<char_type>(out, n, num_digits);
|
||||
out = format_decimal<char_type>(out, n, num_digits).end;
|
||||
}
|
||||
|
||||
void write_nan() { std::copy_n("nan", 3, out); }
|
||||
@@ -1004,14 +1010,14 @@ struct chrono_formatter {
|
||||
out = format_duration_unit<char_type, Period>(out);
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename Rep, typename Period, typename Char>
|
||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
private:
|
||||
basic_format_specs<Char> specs;
|
||||
int precision;
|
||||
using arg_ref_type = internal::arg_ref<Char>;
|
||||
using arg_ref_type = detail::arg_ref<Char>;
|
||||
arg_ref_type width_ref;
|
||||
arg_ref_type precision_ref;
|
||||
mutable basic_string_view<Char> format_str;
|
||||
@@ -1032,7 +1038,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
|
||||
return arg_ref_type(context.next_arg_id());
|
||||
}
|
||||
|
||||
@@ -1062,17 +1068,17 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if (begin == end || *begin == '}') return {begin, begin};
|
||||
spec_handler handler{*this, ctx, format_str};
|
||||
begin = internal::parse_align(begin, end, handler);
|
||||
begin = detail::parse_align(begin, end, handler);
|
||||
if (begin == end) return {begin, begin};
|
||||
begin = internal::parse_width(begin, end, handler);
|
||||
begin = detail::parse_width(begin, end, handler);
|
||||
if (begin == end) return {begin, begin};
|
||||
if (*begin == '.') {
|
||||
if (std::is_floating_point<Rep>::value)
|
||||
begin = internal::parse_precision(begin, end, handler);
|
||||
begin = detail::parse_precision(begin, end, handler);
|
||||
else
|
||||
handler.on_error("precision not allowed for this argument type");
|
||||
}
|
||||
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
|
||||
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
|
||||
return {begin, end};
|
||||
}
|
||||
|
||||
@@ -1083,7 +1089,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
-> decltype(ctx.begin()) {
|
||||
auto range = do_parse(ctx);
|
||||
format_str = basic_string_view<Char>(
|
||||
&*range.begin, internal::to_unsigned(range.end - range.begin));
|
||||
&*range.begin, detail::to_unsigned(range.end - range.begin));
|
||||
return range.end;
|
||||
}
|
||||
|
||||
@@ -1094,23 +1100,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
// is not specified.
|
||||
basic_memory_buffer<Char> buf;
|
||||
auto out = std::back_inserter(buf);
|
||||
using range = internal::output_range<decltype(ctx.out()), Char>;
|
||||
internal::basic_writer<range> w(range(ctx.out()));
|
||||
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
|
||||
width_ref, ctx);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
precision, precision_ref, ctx);
|
||||
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref,
|
||||
ctx);
|
||||
detail::handle_dynamic_spec<detail::precision_checker>(precision,
|
||||
precision_ref, ctx);
|
||||
if (begin == end || *begin == '}') {
|
||||
out = internal::format_duration_value<Char>(out, d.count(), precision);
|
||||
internal::format_duration_unit<Char, Period>(out);
|
||||
out = detail::format_duration_value<Char>(out, d.count(), precision);
|
||||
detail::format_duration_unit<Char, Period>(out);
|
||||
} else {
|
||||
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||
ctx, out, d);
|
||||
f.precision = precision;
|
||||
parse_chrono_format(begin, end, f);
|
||||
}
|
||||
w.write(buf.data(), buf.size(), specs);
|
||||
return w.out();
|
||||
return detail::write(
|
||||
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ struct rgb {
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
@@ -221,7 +221,7 @@ struct color_type {
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
// Experimental text formatting support.
|
||||
class text_style {
|
||||
@@ -298,11 +298,11 @@ class text_style {
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
@@ -313,7 +313,7 @@ class text_style {
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
internal::color_type text_color) FMT_NOEXCEPT
|
||||
detail::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
@@ -326,23 +326,23 @@ class text_style {
|
||||
}
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground)
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
internal::color_type foreground_color;
|
||||
internal::color_type background_color;
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/true, foreground);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(/*is_foreground=*/false, background);
|
||||
}
|
||||
|
||||
@@ -350,21 +350,21 @@ FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == internal::data::background_color;
|
||||
bool is_background = esc == detail::data::background_color;
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
std::size_t index = 0;
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
@@ -398,7 +398,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
|
||||
std::size_t index = 0;
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
@@ -429,14 +429,14 @@ template <typename Char> struct ansi_color_escape {
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
internal::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
|
||||
detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
internal::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, internal::data::background_color);
|
||||
detail::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, detail::data::background_color);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
@@ -455,11 +455,11 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(internal::data::reset_color, stream);
|
||||
fputs(detail::data::reset_color, stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(internal::data::wreset_color, stream);
|
||||
fputs(detail::data::wreset_color, stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
@@ -476,33 +476,31 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground =
|
||||
internal::make_foreground_color<Char>(ts.get_foreground());
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background =
|
||||
internal::make_background_color<Char>(ts.get_background());
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
internal::vformat_to(buf, format_str, args);
|
||||
if (has_style) internal::reset_color<Char>(buf);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format), args);
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
internal::fputs(buf.data(), f);
|
||||
detail::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -513,10 +511,10 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
internal::check_format_string<Args...>(format_str);
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
@@ -530,7 +528,7 @@ void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
@@ -540,7 +538,7 @@ inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
@@ -560,7 +558,7 @@ template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -13,7 +13,33 @@
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts a string literal *s* into a format string that will be parsed at
|
||||
compile time and converted into efficient formatting code. Requires C++17
|
||||
``constexpr if`` compiler support.
|
||||
|
||||
**Example**::
|
||||
|
||||
// Converts 42 into std::string using the most efficient method and no
|
||||
// runtime format string processing.
|
||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string)
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
@@ -62,13 +88,15 @@ template <typename Char> struct part_counter {
|
||||
if (begin != end) ++num_parts;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
|
||||
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
|
||||
return ++num_parts, 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned brace_counter = 0;
|
||||
@@ -116,25 +144,28 @@ class format_string_compiler : public error_handler {
|
||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
FMT_CONSTEXPR int on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(int id) {
|
||||
FMT_CONSTEXPR int on_arg_id(int id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
|
||||
const Char* end) {
|
||||
auto repl = typename part::replacement();
|
||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
||||
@@ -160,23 +191,24 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||
}
|
||||
|
||||
template <typename Range, typename Context, typename Id>
|
||||
template <typename OutputIt, typename Context, typename Id>
|
||||
void format_arg(
|
||||
basic_format_parse_context<typename Range::value_type>& parse_ctx,
|
||||
basic_format_parse_context<typename Context::char_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
|
||||
ctx.advance_to(visit_format_arg(
|
||||
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
|
||||
ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
||||
-> typename Context::iterator {
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
auto vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) -> typename Context::iterator {
|
||||
using char_type = typename Context::char_type;
|
||||
basic_format_parse_context<char_type> parse_ctx(
|
||||
to_string_view(cf.format_str_));
|
||||
Context ctx(out.begin(), args);
|
||||
Context ctx(out, args);
|
||||
|
||||
const auto& parts = cf.parts();
|
||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
||||
@@ -197,12 +229,12 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
||||
|
||||
case format_part_t::kind::arg_index:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.str);
|
||||
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::replacement: {
|
||||
@@ -226,7 +258,9 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
||||
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
||||
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
|
||||
ctx, nullptr, &specs),
|
||||
arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +274,7 @@ struct basic_compiled_format {};
|
||||
template <typename S, typename = void>
|
||||
struct compiled_format_base : basic_compiled_format {
|
||||
using char_type = char_t<S>;
|
||||
using parts_container = std::vector<internal::format_part<char_type>>;
|
||||
using parts_container = std::vector<detail::format_part<char_type>>;
|
||||
|
||||
parts_container compiled_parts;
|
||||
|
||||
@@ -305,7 +339,7 @@ struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
internal::to_string_view(S()));
|
||||
detail::to_string_view(S()));
|
||||
return compiled_parts.data;
|
||||
}
|
||||
};
|
||||
@@ -318,8 +352,8 @@ class compiled_format : private compiled_format_base<S> {
|
||||
private:
|
||||
basic_string_view<char_type> format_str_;
|
||||
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
|
||||
template <typename Context, typename OutputIt, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
|
||||
basic_format_args<Context> args) ->
|
||||
typename Context::iterator;
|
||||
|
||||
@@ -359,8 +393,7 @@ template <typename Char> struct text {
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
// TODO: reserve
|
||||
return copy_str<Char>(data.begin(), data.end(), out);
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -373,33 +406,6 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
OutputIt format_default(OutputIt out, T value) {
|
||||
// TODO: reserve
|
||||
format_int fi(value);
|
||||
return std::copy(fi.data(), fi.data() + fi.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, double value) {
|
||||
writer w(out);
|
||||
w.write(value);
|
||||
return w.out();
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, Char value) {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, const Char* value) {
|
||||
auto length = std::char_traits<Char>::length(value);
|
||||
return copy_str<Char>(value, value + length, out);
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
@@ -408,13 +414,30 @@ template <typename Char, typename T, int N> struct field {
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return format_default<Char>(out, arg);
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
mutable formatter<T, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, {});
|
||||
return fmt.format(arg, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
@@ -450,7 +473,8 @@ constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS != to_string_view(format_str).size()) {
|
||||
if constexpr (POS !=
|
||||
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
@@ -462,6 +486,21 @@ constexpr auto parse_tail(T head, S format_str) {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + (end - str.data()) + 1};
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
@@ -475,12 +514,13 @@ constexpr auto compile_format_string(S format_str) {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
if constexpr (std::is_same<type, int>::value) {
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else if constexpr (str[POS + 1] == ':') {
|
||||
using type = get_type<ID, Args>;
|
||||
constexpr auto result = parse_specs<type>(str, POS + 2);
|
||||
return parse_tail<Args, result.end, ID + 1>(
|
||||
spec_field<char_type, type, ID>{result.fmt}, format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
@@ -494,100 +534,130 @@ constexpr auto compile_format_string(S format_str) {
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value ||
|
||||
detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return internal::make_text(str, 0, 0);
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
internal::unknown_format>()) {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
detail::unknown_format>()) {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
cf.format(std::back_inserter(buffer), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# else
|
||||
#else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
|
||||
return detail::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
#endif // __cpp_if_constexpr
|
||||
|
||||
// Compiles the format string which must be a string literal.
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N])
|
||||
-> internal::compiled_format<const Char*, Args...> {
|
||||
return internal::compiled_format<const Char*, Args...>(
|
||||
-> detail::compiled_format<const Char*, Args...> {
|
||||
return detail::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
// DEPRECATED! use FMT_COMPILE instead.
|
||||
template <typename... Args>
|
||||
FMT_DEPRECATED auto compile(const Args&... args)
|
||||
-> decltype(detail::compile(args...)) {
|
||||
return detail::compile(args...);
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
internal::cf::vformat_to<context>(range(buffer), cf,
|
||||
make_format_args<context>(args...));
|
||||
detail::buffer<Char>& base = buffer;
|
||||
cf.format(std::back_inserter(base), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using context = buffer_context<Char>;
|
||||
detail::buffer<Char>& base = buffer;
|
||||
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
|
||||
make_format_args<context>(args...));
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
constexpr basic_string_view<typename S::char_type> str = S();
|
||||
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
|
||||
return fmt::to_string(detail::first(args...));
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using range = internal::output_range<OutputIt, char_type>;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return internal::cf::vformat_to<context>(range(out), cf,
|
||||
make_format_args<context>(args...));
|
||||
return detail::cf::vformat_to<context>(out, cf,
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
OutputIt format_to(OutputIt out, const S&, const Args&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
return format_to(out, compiled, args...);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
|
||||
detail::basic_compiled_format, CompiledFormat>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto it =
|
||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(internal::counting_iterator(), cf, args...).count();
|
||||
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), cf, args...).count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <cstdarg>
|
||||
#include <cstring> // for std::memmove
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
|
||||
#include "format.h"
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
@@ -22,8 +23,16 @@
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# if !defined(NOMINMAX) && !defined(WIN32_LEAN_AND_MEAN)
|
||||
# define NOMINMAX
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
# undef WIN32_LEAN_AND_MEAN
|
||||
# undef NOMINMAX
|
||||
# else
|
||||
# include <windows.h>
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -33,15 +42,19 @@
|
||||
|
||||
// Dummy implementations of strerror_r and strerror_s called if corresponding
|
||||
// system functions are not available.
|
||||
inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; }
|
||||
inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; }
|
||||
inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; }
|
||||
inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; }
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
|
||||
print(stderr, "{}:{}: assertion failed: {}", file, line, message);
|
||||
std::abort();
|
||||
// Use unchecked std::fprintf to avoid triggering another assertion when
|
||||
// writing to stderr fails
|
||||
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
|
||||
// Chosen instead of std::abort to satisfy Clang in CUDA mode during device
|
||||
// code pass.
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
#ifndef _MSC_VER
|
||||
@@ -67,14 +80,14 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) {
|
||||
// other - failure
|
||||
// Buffer should be at least of size 1.
|
||||
FMT_FUNC int safe_strerror(int error_code, char*& buffer,
|
||||
std::size_t buffer_size) FMT_NOEXCEPT {
|
||||
size_t buffer_size) FMT_NOEXCEPT {
|
||||
FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer");
|
||||
|
||||
class dispatcher {
|
||||
private:
|
||||
int error_code_;
|
||||
char*& buffer_;
|
||||
std::size_t buffer_size_;
|
||||
size_t buffer_size_;
|
||||
|
||||
// A noop assignment operator to avoid bogus warnings.
|
||||
void operator=(const dispatcher&) {}
|
||||
@@ -97,7 +110,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
|
||||
|
||||
// Handle the case when strerror_r is not available.
|
||||
FMT_MAYBE_UNUSED
|
||||
int handle(internal::null<>) {
|
||||
int handle(detail::null<>) {
|
||||
return fallback(strerror_s(buffer_, buffer_size_, error_code_));
|
||||
}
|
||||
|
||||
@@ -111,7 +124,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
|
||||
|
||||
#if !FMT_MSC_VER
|
||||
// Fallback to strerror if strerror_r and strerror_s are not available.
|
||||
int fallback(internal::null<>) {
|
||||
int fallback(detail::null<>) {
|
||||
errno = 0;
|
||||
buffer_ = strerror(error_code_);
|
||||
return errno;
|
||||
@@ -119,7 +132,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
|
||||
#endif
|
||||
|
||||
public:
|
||||
dispatcher(int err_code, char*& buf, std::size_t buf_size)
|
||||
dispatcher(int err_code, char*& buf, size_t buf_size)
|
||||
: error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
|
||||
|
||||
int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); }
|
||||
@@ -127,7 +140,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer,
|
||||
return dispatcher(error_code, buffer, buffer_size).run();
|
||||
}
|
||||
|
||||
FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
|
||||
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
// Report error code making sure that the output fits into
|
||||
// inline_buffer_size to avoid dynamic memory allocation and potential
|
||||
@@ -136,20 +149,17 @@ FMT_FUNC void format_error_code(internal::buffer<char>& out, int error_code,
|
||||
static const char SEP[] = ": ";
|
||||
static const char ERROR_STR[] = "error ";
|
||||
// Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
|
||||
std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
|
||||
auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
|
||||
if (internal::is_negative(error_code)) {
|
||||
if (detail::is_negative(error_code)) {
|
||||
abs_value = 0 - abs_value;
|
||||
++error_code_size;
|
||||
}
|
||||
error_code_size += internal::to_unsigned(internal::count_digits(abs_value));
|
||||
internal::writer w(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size) {
|
||||
w.write(message);
|
||||
w.write(SEP);
|
||||
}
|
||||
w.write(ERROR_STR);
|
||||
w.write(error_code);
|
||||
error_code_size += detail::to_unsigned(detail::count_digits(abs_value));
|
||||
auto it = std::back_inserter(out);
|
||||
if (message.size() <= inline_buffer_size - error_code_size)
|
||||
format_to(it, "{}{}", message, SEP);
|
||||
format_to(it, "{}{}", ERROR_STR, error_code);
|
||||
assert(out.size() <= inline_buffer_size);
|
||||
}
|
||||
|
||||
@@ -168,10 +178,10 @@ FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count,
|
||||
size_t written = std::fwrite(ptr, size, count, stream);
|
||||
if (written < count) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
template <typename Locale>
|
||||
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
|
||||
@@ -194,18 +204,16 @@ template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref loc) {
|
||||
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
|
||||
.decimal_point();
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
#else
|
||||
template <typename Char>
|
||||
FMT_FUNC std::string internal::grouping_impl(locale_ref) {
|
||||
FMT_FUNC std::string detail::grouping_impl(locale_ref) {
|
||||
return "\03";
|
||||
}
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::thousands_sep_impl(locale_ref) {
|
||||
template <typename Char> FMT_FUNC Char detail::thousands_sep_impl(locale_ref) {
|
||||
return FMT_STATIC_THOUSANDS_SEPARATOR;
|
||||
}
|
||||
template <typename Char>
|
||||
FMT_FUNC Char internal::decimal_point_impl(locale_ref) {
|
||||
template <typename Char> FMT_FUNC Char detail::decimal_point_impl(locale_ref) {
|
||||
return '.';
|
||||
}
|
||||
#endif
|
||||
@@ -222,9 +230,9 @@ FMT_FUNC void system_error::init(int err_code, string_view format_str,
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
|
||||
template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
|
||||
// fallback_uintptr is always stored in little endian.
|
||||
int i = static_cast<int>(sizeof(void*)) - 1;
|
||||
while (i > 0 && n.value[i] == 0) --i;
|
||||
@@ -233,12 +241,27 @@ template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const char basic_data<T>::digits[] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
const typename basic_data<T>::digit_pair basic_data<T>::digits[] = {
|
||||
{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'},
|
||||
{'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'},
|
||||
{'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'},
|
||||
{'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'},
|
||||
{'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'},
|
||||
{'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
|
||||
{'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'},
|
||||
{'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'},
|
||||
{'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'},
|
||||
{'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'},
|
||||
{'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'},
|
||||
{'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
|
||||
{'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'},
|
||||
{'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'},
|
||||
{'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'},
|
||||
{'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'},
|
||||
{'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'},
|
||||
{'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
|
||||
{'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'},
|
||||
{'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
|
||||
|
||||
template <typename T>
|
||||
const char basic_data<T>::hex_digits[] = "0123456789abcdef";
|
||||
@@ -317,6 +340,10 @@ const char basic_data<T>::background_color[] = "\x1b[48;2;";
|
||||
template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
|
||||
template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
|
||||
template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
|
||||
template <typename T>
|
||||
const char basic_data<T>::left_padding_shifts[] = {31, 31, 0, 1, 0};
|
||||
template <typename T>
|
||||
const char basic_data<T>::right_padding_shifts[] = {0, 31, 0, 1, 0};
|
||||
|
||||
template <typename T> struct bits {
|
||||
static FMT_CONSTEXPR_DECL const int value =
|
||||
@@ -576,9 +603,10 @@ class bigint {
|
||||
void operator=(const bigint&) = delete;
|
||||
|
||||
void assign(const bigint& other) {
|
||||
bigits_.resize(other.bigits_.size());
|
||||
auto size = other.bigits_.size();
|
||||
bigits_.resize(size);
|
||||
auto data = other.bigits_.data();
|
||||
std::copy(data, data + other.bigits_.size(), bigits_.data());
|
||||
std::copy(data, data + size, make_checked(bigits_.data(), size));
|
||||
exp_ = other.exp_;
|
||||
}
|
||||
|
||||
@@ -594,7 +622,7 @@ class bigint {
|
||||
|
||||
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
|
||||
|
||||
bigint& operator<<=(int shift) {
|
||||
FMT_NOINLINE bigint& operator<<=(int shift) {
|
||||
assert(shift >= 0);
|
||||
exp_ += shift / bigit_bits;
|
||||
shift %= bigit_bits;
|
||||
@@ -1125,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
precision = (precision >= 0 ? precision : 6) - 1;
|
||||
|
||||
// Build the format string.
|
||||
enum { max_format_size = 7 }; // Ths longest format is "%#.*Le".
|
||||
enum { max_format_size = 7 }; // The longest format is "%#.*Le".
|
||||
char format[max_format_size];
|
||||
char* format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
@@ -1145,13 +1173,13 @@ int snprintf_float(T value, int precision, float_specs specs,
|
||||
for (;;) {
|
||||
auto begin = buf.data() + offset;
|
||||
auto capacity = buf.capacity() - offset;
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about a nonliteral format string.
|
||||
// Cannot use auto becase of a bug in MinGW (#1532).
|
||||
// Cannot use auto because of a bug in MinGW (#1532).
|
||||
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
|
||||
int result = precision >= 0
|
||||
? snprintf_ptr(begin, capacity, format, precision, value)
|
||||
@@ -1268,14 +1296,14 @@ FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) {
|
||||
|
||||
return next;
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <> struct formatter<internal::bigint> {
|
||||
template <> struct formatter<detail::bigint> {
|
||||
format_parse_context::iterator parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
format_context::iterator format(const internal::bigint& n,
|
||||
format_context::iterator format(const detail::bigint& n,
|
||||
format_context& ctx) {
|
||||
auto out = ctx.out();
|
||||
bool first = true;
|
||||
@@ -1289,12 +1317,12 @@ template <> struct formatter<internal::bigint> {
|
||||
out = format_to(out, "{:08x}", value);
|
||||
}
|
||||
if (n.exp_ > 0)
|
||||
out = format_to(out, "p{}", n.exp_ * internal::bigint::bigit_bits);
|
||||
out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
auto transcode = [this](const char* p) {
|
||||
auto cp = uint32_t();
|
||||
auto error = 0;
|
||||
@@ -1325,7 +1353,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
|
||||
buffer_.push_back(0);
|
||||
}
|
||||
|
||||
FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
|
||||
FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
memory_buffer buf;
|
||||
@@ -1333,12 +1361,9 @@ FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
|
||||
for (;;) {
|
||||
char* system_message = &buf[0];
|
||||
int result =
|
||||
internal::safe_strerror(error_code, system_message, buf.size());
|
||||
detail::safe_strerror(error_code, system_message, buf.size());
|
||||
if (result == 0) {
|
||||
internal::writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(system_message);
|
||||
format_to(std::back_inserter(out), "{}: {}", message, system_message);
|
||||
return;
|
||||
}
|
||||
if (result != ERANGE)
|
||||
@@ -1350,7 +1375,7 @@ FMT_FUNC void format_system_error(internal::buffer<char>& out, int error_code,
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
FMT_FUNC void internal::error_handler::on_error(const char* message) {
|
||||
FMT_FUNC void detail::error_handler::on_error(const char* message) {
|
||||
FMT_THROW(format_error(message));
|
||||
}
|
||||
|
||||
@@ -1359,14 +1384,39 @@ FMT_FUNC void report_system_error(int error_code,
|
||||
report_error(format_system_error, error_code, message);
|
||||
}
|
||||
|
||||
struct stringifier {
|
||||
template <typename T> FMT_INLINE std::string operator()(T value) const {
|
||||
return to_string(value);
|
||||
}
|
||||
std::string operator()(basic_format_arg<format_context>::handle h) const {
|
||||
memory_buffer buf;
|
||||
detail::buffer<char>& base = buf;
|
||||
format_parse_context parse_ctx({});
|
||||
format_context format_ctx(std::back_inserter(base), {}, {});
|
||||
h.format(parse_ctx, format_ctx);
|
||||
return to_string(buf);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) {
|
||||
if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
|
||||
auto arg = args.get(0);
|
||||
if (!arg) error_handler().on_error("argument not found");
|
||||
return visit_format_arg(stringifier(), arg);
|
||||
}
|
||||
memory_buffer buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
|
||||
memory_buffer buffer;
|
||||
internal::vformat_to(buffer, format_str,
|
||||
basic_format_args<buffer_context<char>>(args));
|
||||
detail::vformat_to(buffer, format_str,
|
||||
basic_format_args<buffer_context<char>>(args));
|
||||
#ifdef _WIN32
|
||||
auto fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
internal::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
|
||||
detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size()));
|
||||
auto written = DWORD();
|
||||
if (!WriteConsoleW(reinterpret_cast<HANDLE>(_get_osfhandle(fd)),
|
||||
u16.c_str(), static_cast<DWORD>(u16.size()), &written,
|
||||
@@ -1376,16 +1426,16 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
internal::fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
||||
detail::fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Print assuming legacy (non-Unicode) encoding.
|
||||
FMT_FUNC void internal::vprint_mojibake(std::FILE* f, string_view format_str,
|
||||
format_args args) {
|
||||
FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str,
|
||||
format_args args) {
|
||||
memory_buffer buffer;
|
||||
internal::vformat_to(buffer, format_str,
|
||||
basic_format_args<buffer_context<char>>(args));
|
||||
detail::vformat_to(buffer, format_str,
|
||||
basic_format_args<buffer_context<char>>(args));
|
||||
fwrite_fully(buffer.data(), 1, buffer.size(), f);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
template <typename Char>
|
||||
typename buffer_context<Char>::iterator vformat_to(
|
||||
const std::locale& loc, buffer<Char>& buf,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
using range = buffer_range<Char>;
|
||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
|
||||
internal::locale_ref(loc));
|
||||
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
|
||||
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
|
||||
args, detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
@@ -30,43 +30,43 @@ std::basic_string<Char> vformat(
|
||||
const std::locale& loc, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::vformat_to(loc, buffer, format_str, args);
|
||||
detail::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
}
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const std::locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
return internal::vformat(loc, to_string_view(format_str), args);
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return internal::vformat(
|
||||
return detail::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = enable_if_t<
|
||||
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const std::locale& loc, const S& format_str,
|
||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
||||
using range = internal::output_range<OutputIt, Char>;
|
||||
return vformat_to<arg_formatter<range>>(
|
||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
|
||||
using af = detail::arg_formatter<OutputIt, Char>;
|
||||
return vformat_to<af>(out, to_string_view(format_str), args,
|
||||
detail::locale_ref(loc));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
|
||||
internal::is_string<S>::value)>
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
|
||||
detail::is_string<S>::value)>
|
||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
internal::check_format_string<Args...>(format_str);
|
||||
detail::check_format_string<Args...>(format_str);
|
||||
using context = format_context_t<OutputIt, char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
return vformat_to(out, loc, to_string_view(format_str),
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
@@ -133,7 +133,7 @@ class error_code {
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
@@ -156,7 +156,7 @@ class utf16_to_utf8 {
|
||||
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
/** A Windows error. */
|
||||
class windows_error : public system_error {
|
||||
@@ -277,7 +277,8 @@ class file {
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
@@ -313,10 +314,10 @@ class file {
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API std::size_t read(void* buffer, std::size_t count);
|
||||
FMT_API size_t read(void* buffer, size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API std::size_t write(const void* buffer, std::size_t count);
|
||||
FMT_API size_t write(const void* buffer, size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
@@ -341,6 +342,63 @@ class file {
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
class direct_buffered_file;
|
||||
|
||||
template <typename S, typename... Args>
|
||||
void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args);
|
||||
|
||||
// A buffered file with a direct buffer access and no synchronization.
|
||||
class direct_buffered_file {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
enum { buffer_size = 4096 };
|
||||
char buffer_[buffer_size];
|
||||
int pos_;
|
||||
|
||||
void flush() {
|
||||
if (pos_ == 0) return;
|
||||
file_.write(buffer_, pos_);
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
int free_capacity() const { return buffer_size - pos_; }
|
||||
|
||||
public:
|
||||
direct_buffered_file(cstring_view path, int oflag)
|
||||
: file_(path, oflag), pos_(0) {}
|
||||
|
||||
~direct_buffered_file() {
|
||||
flush();
|
||||
}
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args>
|
||||
friend void print(direct_buffered_file& f, const S& format_str,
|
||||
const Args&... args) {
|
||||
// We could avoid double buffering.
|
||||
auto buf = fmt::memory_buffer();
|
||||
fmt::format_to(std::back_inserter(buf), format_str, args...);
|
||||
auto remaining_pos = 0;
|
||||
auto remaining_size = buf.size();
|
||||
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
|
||||
auto size = f.free_capacity();
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
|
||||
f.pos_ += size;
|
||||
f.flush();
|
||||
remaining_pos += size;
|
||||
remaining_size -= size;
|
||||
}
|
||||
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
|
||||
f.pos_ += static_cast<int>(remaining_size);
|
||||
}
|
||||
};
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
|
||||
@@ -9,10 +9,15 @@
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
@@ -75,7 +80,7 @@ template <typename T, typename Char> class is_streamable {
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
@@ -93,34 +98,53 @@ void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: formatter<basic_string_view<Char>, Char> {
|
||||
template <typename Context>
|
||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,10 +157,10 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
detail::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#include "os.h"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include "ostream.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
@@ -90,11 +90,11 @@ template <typename T, typename Context> class arg_converter {
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = internal::make_arg<Context>(
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
} else {
|
||||
@@ -102,9 +102,9 @@ template <typename T, typename Context> class arg_converter {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ template <typename Context> class char_converter {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = internal::make_arg<Context>(
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
}
|
||||
|
||||
@@ -141,6 +141,13 @@ template <typename Context> class char_converter {
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
@@ -155,7 +162,7 @@ template <typename Char> class printf_width_handler {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
@@ -172,23 +179,25 @@ template <typename Char> class printf_width_handler {
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void printf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(std::back_inserter(buf), format, args).format();
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename OutputIt, typename Char, typename Context>
|
||||
internal::truncating_iterator<OutputIt> printf(
|
||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return Context(it, format, args).format();
|
||||
// For printing into memory_buffer.
|
||||
template <typename Char, typename Context>
|
||||
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
|
||||
basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return detail::vprintf(buf, format, args);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
using internal::printf; // For printing into memory_buffer.
|
||||
|
||||
template <typename Range> class printf_arg_formatter;
|
||||
using detail::vprintf;
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
/**
|
||||
@@ -196,15 +205,15 @@ template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
The ``printf`` argument formatter.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Range>
|
||||
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
template <typename OutputIt, typename Char>
|
||||
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
|
||||
public:
|
||||
using iterator = typename Range::iterator;
|
||||
using iterator = OutputIt;
|
||||
|
||||
private:
|
||||
using char_type = typename Range::value_type;
|
||||
using base = internal::arg_formatter_base<Range>;
|
||||
using context_type = basic_printf_context<iterator, char_type>;
|
||||
using char_type = Char;
|
||||
using base = detail::arg_formatter_base<OutputIt, Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
@@ -229,9 +238,9 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
\endrst
|
||||
*/
|
||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
|
||||
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
|
||||
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
@@ -246,7 +255,11 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.align = align::right;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return base::operator()(value);
|
||||
} else {
|
||||
return base::operator()(value);
|
||||
@@ -312,18 +325,21 @@ template <typename T> struct printf_formatter {
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
internal::format_value(internal::get_container(ctx.out()), value);
|
||||
detail::format_value(detail::get_container(ctx.out()), value);
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/** This template formats data and writes the output to a writer. */
|
||||
/**
|
||||
This template formats data and writes the output through an output iterator.
|
||||
*/
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
using char_type = Char;
|
||||
using iterator = OutputIt;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
@@ -331,7 +347,7 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
basic_format_parse_context<Char> parse_ctx_;
|
||||
parse_context_type parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
@@ -346,9 +362,8 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments and
|
||||
the writer are stored in the context object so make sure they have
|
||||
appropriate lifetimes.
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
@@ -358,18 +373,18 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
internal::locale_ref locale() { return {}; }
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
basic_format_parse_context<Char>& parse_context() { return parse_ctx_; }
|
||||
parse_context_type& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
|
||||
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
@@ -389,7 +404,9 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
specs.sign = sign::space;
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
@@ -407,7 +424,7 @@ basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
return internal::get_arg(*this, arg_index);
|
||||
return detail::get_arg(*this, arg_index);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
@@ -419,7 +436,7 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
internal::error_handler eh;
|
||||
detail::error_handler eh;
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
@@ -438,12 +455,12 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
internal::error_handler eh;
|
||||
detail::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
internal::printf_width_handler<char_type>(specs), get_arg()));
|
||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
@@ -471,38 +488,52 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument index out of range");
|
||||
if (arg_index == 0) on_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
internal::error_handler eh;
|
||||
detail::error_handler eh;
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(internal::printf_precision_handler(), get_arg()));
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(arg_index);
|
||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
|
||||
str,
|
||||
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic())
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
using internal::convert_arg;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
@@ -526,7 +557,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<std::size_t>(arg, t);
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
@@ -551,7 +582,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
|
||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
@@ -560,15 +591,14 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
out = visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
||||
}
|
||||
return std::copy(start, it, out);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
|
||||
Char>;
|
||||
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
@@ -605,7 +635,7 @@ inline std::basic_string<Char> vsprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
@@ -619,7 +649,7 @@ inline std::basic_string<Char> vsprintf(
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
||||
@@ -630,8 +660,8 @@ inline int vfprintf(
|
||||
std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
std::size_t size = buffer.size();
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
@@ -647,7 +677,7 @@ inline int vfprintf(
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
@@ -671,7 +701,7 @@ inline int vprintf(
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline int printf(const S& format_str, const Args&... args) {
|
||||
using context = basic_printf_context_t<char_t<S>>;
|
||||
return vprintf(to_string_view(format_str),
|
||||
@@ -683,8 +713,8 @@ inline int vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
internal::write(os, buffer);
|
||||
vprintf(buffer, to_string_view(format), args);
|
||||
detail::write_buffer(os, buffer);
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
@@ -693,7 +723,7 @@ template <typename ArgFormatter, typename Char,
|
||||
typename Context =
|
||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
||||
typename ArgFormatter::iterator vprintf(
|
||||
internal::buffer<Char>& out, basic_string_view<Char> format_str,
|
||||
detail::buffer<Char>& out, basic_string_view<Char> format_str,
|
||||
basic_format_args<type_identity_t<Context>> args) {
|
||||
typename ArgFormatter::iterator iter(out);
|
||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
||||
|
||||
@@ -33,7 +33,7 @@ template <typename Char> struct formatting_base {
|
||||
|
||||
template <typename Char, typename Enable = void>
|
||||
struct formatting_range : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
|
||||
static FMT_CONSTEXPR_DECL const size_t range_length_limit =
|
||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
||||
// range.
|
||||
Char prefix;
|
||||
@@ -54,7 +54,7 @@ struct formatting_tuple : formatting_base<Char> {
|
||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
@@ -118,26 +118,24 @@ template <typename T> class is_tuple_like_ {
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = std::make_index_sequence<N>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <std::size_t... N>
|
||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, std::size_t N, T... Ns>
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <std::size_t N>
|
||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
@@ -185,11 +183,11 @@ FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
||||
return add_space ? L" '{}'" : L"'{}'";
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
@@ -202,17 +200,17 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
out = internal::copy(formatting.delimiter, out);
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
internal::format_str_quoted(
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), v),
|
||||
v);
|
||||
++i;
|
||||
}
|
||||
|
||||
formatting_tuple<Char>& formatting;
|
||||
std::size_t& i;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<decltype(
|
||||
std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
@@ -228,14 +226,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
std::size_t i = 0;
|
||||
internal::copy(formatting.prefix, out);
|
||||
size_t i = 0;
|
||||
detail::copy(formatting.prefix, out);
|
||||
|
||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
if (formatting.add_prepostfix_space) {
|
||||
*out++ = ' ';
|
||||
}
|
||||
internal::copy(formatting.postfix, out);
|
||||
detail::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
@@ -243,10 +241,9 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value &&
|
||||
!internal::is_like_std_string<T>::value &&
|
||||
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<internal::std_string_view<Char>, T>::value;
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
@@ -262,15 +259,17 @@ struct formatter<RangeT, Char,
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const RangeT& values,
|
||||
FormatContext& ctx) {
|
||||
auto out = internal::copy(formatting.prefix, ctx.out());
|
||||
std::size_t i = 0;
|
||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto it = values.begin();
|
||||
auto end = values.end();
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) {
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
out = internal::copy(formatting.delimiter, out);
|
||||
out = detail::copy(formatting.delimiter, out);
|
||||
}
|
||||
out = format_to(out,
|
||||
internal::format_str_quoted(
|
||||
detail::format_str_quoted(
|
||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
||||
*it);
|
||||
if (++i > formatting.range_length_limit) {
|
||||
@@ -279,11 +278,11 @@ struct formatter<RangeT, Char,
|
||||
}
|
||||
}
|
||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
||||
return internal::copy(formatting.postfix, out);
|
||||
return detail::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
|
||||
template <typename Char, typename... T> struct tuple_arg_join : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
@@ -301,14 +300,14 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
|
||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
internal::index_sequence<N...>) {
|
||||
detail::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
@@ -371,14 +370,14 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
|
||||
std::initializer_list<T> list, string_view sep) {
|
||||
arg_join<const T*, const T*, char> join(std::initializer_list<T> list,
|
||||
string_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
|
||||
std::initializer_list<T> list, wstring_view sep) {
|
||||
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
|
||||
wstring_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
@@ -23,154 +23,47 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
struct sprintf_specs {
|
||||
int precision;
|
||||
char type;
|
||||
bool alt : 1;
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
constexpr sprintf_specs(basic_format_specs<Char> specs)
|
||||
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
|
||||
|
||||
constexpr bool has_precision() const { return precision >= 0; }
|
||||
};
|
||||
|
||||
// This is deprecated and is kept only to preserve ABI compatibility.
|
||||
template <typename Double>
|
||||
char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
sprintf_specs specs) {
|
||||
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
||||
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
|
||||
|
||||
// Build format string.
|
||||
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
|
||||
char format[max_format_size];
|
||||
char* format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
if (specs.alt || !specs.type) *format_ptr++ = '#';
|
||||
if (specs.precision >= 0) {
|
||||
*format_ptr++ = '.';
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
|
||||
|
||||
char type = specs.type;
|
||||
|
||||
if (type == '%')
|
||||
type = 'f';
|
||||
else if (type == 0 || type == 'n')
|
||||
type = 'g';
|
||||
#if FMT_MSC_VER
|
||||
if (type == 'F') {
|
||||
// MSVC's printf doesn't support 'F'.
|
||||
type = 'f';
|
||||
}
|
||||
#endif
|
||||
*format_ptr++ = type;
|
||||
*format_ptr = '\0';
|
||||
|
||||
// Format using snprintf.
|
||||
char* start = nullptr;
|
||||
char* decimal_point_pos = nullptr;
|
||||
for (;;) {
|
||||
std::size_t buffer_size = buf.capacity();
|
||||
start = &buf[0];
|
||||
int result =
|
||||
format_float(start, buffer_size, format, specs.precision, value);
|
||||
if (result >= 0) {
|
||||
unsigned n = internal::to_unsigned(result);
|
||||
if (n < buf.capacity()) {
|
||||
// Find the decimal point.
|
||||
auto p = buf.data(), end = p + n;
|
||||
if (*p == '+' || *p == '-') ++p;
|
||||
if (specs.type != 'a' && specs.type != 'A') {
|
||||
while (p < end && *p >= '0' && *p <= '9') ++p;
|
||||
if (p < end && *p != 'e' && *p != 'E') {
|
||||
decimal_point_pos = p;
|
||||
if (!specs.type) {
|
||||
// Keep only one trailing zero after the decimal point.
|
||||
++p;
|
||||
if (*p == '0') ++p;
|
||||
while (p != end && *p >= '1' && *p <= '9') ++p;
|
||||
char* where = p;
|
||||
while (p != end && *p == '0') ++p;
|
||||
if (p == end || *p < '0' || *p > '9') {
|
||||
if (p != end) std::memmove(where, p, to_unsigned(end - p));
|
||||
n -= static_cast<unsigned>(p - where);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.resize(n);
|
||||
break; // The buffer is large enough - continue with formatting.
|
||||
}
|
||||
buf.reserve(n + 1);
|
||||
} else {
|
||||
// If result is negative we ask to increase the capacity by at least 1,
|
||||
// but as std::vector, the buffer grows exponentially.
|
||||
buf.reserve(buf.capacity() + 1);
|
||||
}
|
||||
}
|
||||
return decimal_point_pos;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
template FMT_API char* internal::sprintf_format(long double,
|
||||
internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
|
||||
template struct FMT_INSTANTIATION_DEF_API internal::basic_data<void>;
|
||||
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, internal::float_specs,
|
||||
internal::buffer<char>&) =
|
||||
internal::format_float;
|
||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||
detail::buffer<char>&) = detail::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
|
||||
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||
#endif
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API char internal::decimal_point_impl(locale_ref);
|
||||
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<char>::append(const char*, const char*);
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API void internal::arg_map<format_context>::init(
|
||||
const basic_format_args<format_context>& args);
|
||||
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
|
||||
detail::buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
|
||||
|
||||
template FMT_API std::string internal::vformat<char>(
|
||||
string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API format_context::iterator internal::vformat_to(
|
||||
internal::buffer<char>&, string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API int internal::snprintf_float(double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::snprintf_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(double, int, internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::snprintf_float(long double, int,
|
||||
detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
|
||||
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
@@ -73,14 +73,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int internal::utf16_to_utf8::convert(wstring_view s) {
|
||||
int detail::utf16_to_utf8::convert(wstring_view s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
@@ -105,13 +105,13 @@ void windows_error::init(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error& base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
void internal::format_windows_error(internal::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
wmemory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
@@ -124,10 +124,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
|
||||
if (result != 0) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
internal::writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(utf8_message);
|
||||
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -143,7 +140,7 @@ void internal::format_windows_error(internal::buffer<char>& out, int error_code,
|
||||
|
||||
void report_windows_error(int error_code,
|
||||
fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
@@ -234,14 +231,14 @@ std::size_t file::read(void* buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return internal::to_unsigned(result);
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
RWResult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return internal::to_unsigned(result);
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
@@ -292,7 +289,11 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
#if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
#else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
#endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
|
||||
BIN
res/Cascadia.ttf
@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2007.15)
|
||||
* from microsoft/cascadia-code@2a54363b2c867f7ae811b9a034c0024cef67de96
|
||||
* Cascadia Code, Cascadia Mono (2007.01)
|
||||
* from microsoft/cascadia-code@311cc603f30635da704b6a7d13050e245e61667b
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include "targetver.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstdio>
|
||||
#include <tchar.h>
|
||||
|
||||
// TODO: reference additional headers your program requires here
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Samples.Terminal
|
||||
internal ReadConsoleInputStream(HFILE handle,
|
||||
BlockingCollection<Kernel32.INPUT_RECORD> nonKeyEvents)
|
||||
{
|
||||
Debug.Assert(handle.IsInvalid == false, "handle.IsInvalid == false");
|
||||
Debug.Assert(!handle.IsInvalid, "handle.IsInvalid == false");
|
||||
|
||||
_handle = handle.DangerousGetHandle();
|
||||
_nonKeyEvents = nonKeyEvents;
|
||||
@@ -111,7 +111,7 @@ namespace Samples.Terminal
|
||||
if (record.EventType == Kernel32.EVENT_TYPE.KEY_EVENT)
|
||||
{
|
||||
// skip key up events - if not, every key will be duped in the stream
|
||||
if (record.Event.KeyEvent.bKeyDown == false) continue;
|
||||
if (!record.Event.KeyEvent.bKeyDown) continue;
|
||||
|
||||
// pack ucs-2/utf-16le/unicode chars into position in our byte[] buffer.
|
||||
var glyph = (ushort) record.Event.KeyEvent.uChar;
|
||||
|
||||
@@ -223,7 +223,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// Return Value:
|
||||
// - STATUS_NO_MEMORY if there wasn't enough memory to insert the runs
|
||||
// otherwise STATUS_SUCCESS if we were successful.
|
||||
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const std::basic_string_view<TextAttributeRun> newAttrs,
|
||||
[[nodiscard]] HRESULT ATTR_ROW::InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
|
||||
const size_t iStart,
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth)
|
||||
@@ -250,7 +250,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
if (newAttrs.size() == 1)
|
||||
{
|
||||
// Get the new color attribute we're trying to apply
|
||||
const TextAttribute NewAttr = newAttrs.at(0).GetAttributes();
|
||||
const TextAttribute NewAttr = til::at(newAttrs, 0).GetAttributes();
|
||||
|
||||
// If the existing run was only 1 element...
|
||||
// ...and the new color is the same as the old, we don't have to do anything and can exit quick.
|
||||
@@ -264,15 +264,16 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
{
|
||||
// First we try to find the run where the insertion happens, using lowerBound and upperBound to track
|
||||
// where we are currently at.
|
||||
const auto begin = _list.begin();
|
||||
size_t lowerBound = 0;
|
||||
size_t upperBound = 0;
|
||||
for (size_t i = 0; i < _list.size(); i++)
|
||||
{
|
||||
upperBound += _list.at(i).GetLength();
|
||||
const auto curr = begin + i;
|
||||
upperBound += curr->GetLength();
|
||||
|
||||
if (iStart >= lowerBound && iStart < upperBound)
|
||||
{
|
||||
const auto curr = std::next(_list.begin(), i);
|
||||
|
||||
// The run that we try to insert into has the same color as the new one.
|
||||
// e.g.
|
||||
// AAAAABBBBBBBCCC
|
||||
@@ -371,7 +372,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
if (iStart == 0 && iEnd == iLastBufferCol)
|
||||
{
|
||||
// Just dump what we're given over what we have and call it a day.
|
||||
_list.assign(newAttrs.cbegin(), newAttrs.cend());
|
||||
_list.assign(newAttrs.begin(), newAttrs.end());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -385,7 +386,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// fact that an existing piece of the run was split in half (to hold the latter half).
|
||||
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
|
||||
std::vector<TextAttributeRun> newRun;
|
||||
newRun.resize(cNewRun);
|
||||
newRun.reserve(cNewRun);
|
||||
|
||||
// We will start analyzing from the beginning of our existing run.
|
||||
// Use some pointers to keep track of where we are in walking through our runs.
|
||||
@@ -393,10 +394,9 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// Get the existing run that we'll be updating/manipulating.
|
||||
const auto existingRun = _list.begin();
|
||||
auto pExistingRunPos = existingRun;
|
||||
const auto pExistingRunEnd = existingRun + _list.size();
|
||||
const auto pExistingRunEnd = _list.end();
|
||||
auto pInsertRunPos = newAttrs.begin();
|
||||
size_t cInsertRunRemaining = newAttrs.size();
|
||||
auto pNewRunPos = newRun.begin();
|
||||
size_t iExistingRunCoverage = 0;
|
||||
|
||||
// Copy the existing run into the new buffer up to the "start index" where the new run will be injected.
|
||||
@@ -410,7 +410,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
iExistingRunCoverage += pExistingRunPos->GetLength();
|
||||
|
||||
// Copy it to the new run buffer and advance both pointers.
|
||||
*pNewRunPos++ = *pExistingRunPos++;
|
||||
newRun.push_back(*pExistingRunPos++);
|
||||
}
|
||||
|
||||
// When we get to this point, we've copied full segments from the original existing run
|
||||
@@ -429,12 +429,8 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// We need to fix this up below so it says G2 instead to leave room for the Y3 to fit in
|
||||
// the new/final run.
|
||||
|
||||
// Copying above advanced the pointer to an empty cell beyond what we copied.
|
||||
// Back up one cell so we can manipulate the final item we copied from the existing run to the new run.
|
||||
pNewRunPos--;
|
||||
|
||||
// Fetch out the length so we can fix it up based on the below conditions.
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
|
||||
// If we've covered more cells already than the start of the attributes to be inserted...
|
||||
if (iExistingRunCoverage > iStart)
|
||||
@@ -449,7 +445,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// Now we're still on that "last cell copied" into the new run.
|
||||
// If the color of that existing copied cell matches the color of the first segment
|
||||
// of the run we're about to insert, we can just increment the length to extend the coverage.
|
||||
if (pNewRunPos->GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
if (newRun.back().GetAttributes() == pInsertRunPos->GetAttributes())
|
||||
{
|
||||
length += pInsertRunPos->GetLength();
|
||||
|
||||
@@ -460,18 +456,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
}
|
||||
|
||||
// We're done manipulating the length. Store it back.
|
||||
pNewRunPos->SetLength(length);
|
||||
|
||||
// Now that we're done adjusting the last copied item, advance the pointer into a fresh/blank
|
||||
// part of the new run array.
|
||||
pNewRunPos++;
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
|
||||
// Bulk copy the majority (or all, depending on circumstance) of the insert run into the final run buffer.
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, pNewRunPos);
|
||||
|
||||
// Advance the new run pointer into the position just after everything we copied.
|
||||
pNewRunPos += cInsertRunRemaining;
|
||||
std::copy_n(pInsertRunPos, cInsertRunRemaining, std::back_inserter(newRun));
|
||||
|
||||
// We're technically done with the insert run now and have 0 remaining, but won't bother updating its pointers
|
||||
// and counts any further because we won't use them.
|
||||
@@ -488,9 +477,6 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// If we still have original existing run cells remaining, copy them into the final new run.
|
||||
if (pExistingRunPos != pExistingRunEnd || iExistingRunCoverage != (iEnd + 1))
|
||||
{
|
||||
// Back up one cell so we can inspect the most recent item copied into the new run for optimizations.
|
||||
pNewRunPos--;
|
||||
|
||||
// We advanced the existing run pointer and its count to on or past the end of what the insertion run filled in.
|
||||
// If this ended up being past the end of what the insertion run covers, we have to account for the cells after
|
||||
// the insertion run but before the next piece of the original existing run.
|
||||
@@ -514,11 +500,11 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// This case is slightly off from the example above. This case is for if the B2 above was actually Y2.
|
||||
// That Y2 from the existing run is the same color as the Y2 we just filled a few columns left in the final run
|
||||
// so we can just adjust the final run's column count instead of adding another segment here.
|
||||
if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += (iExistingRunCoverage - (iEnd + 1));
|
||||
pNewRunPos->SetLength(length);
|
||||
newRun.back().SetLength(length);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -526,14 +512,14 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// its length for the discrepancy in columns not yet covered by the final/new run.
|
||||
|
||||
// Move forward to a blank spot in the new run
|
||||
pNewRunPos++;
|
||||
newRun.emplace_back();
|
||||
|
||||
// Copy the existing run's color information to the new run
|
||||
pNewRunPos->SetAttributes(pExistingRunPos->GetAttributes());
|
||||
newRun.back().SetAttributes(pExistingRunPos->GetAttributes());
|
||||
|
||||
// Adjust the length of that copied color to cover only the reduced number of columns needed
|
||||
// now that some have been replaced by the insert run.
|
||||
pNewRunPos->SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
newRun.back().SetLength(iExistingRunCoverage - (iEnd + 1));
|
||||
}
|
||||
|
||||
// Now that we're done recovering a piece of the existing run we skipped, move the pointer forward again.
|
||||
@@ -551,34 +537,25 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// New Run desired when done = R3 -> B7
|
||||
// Existing run pointer is on B2.
|
||||
// We want to merge the 2 from the B2 into the B5 so we get B7.
|
||||
else if (pNewRunPos->GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
else if (newRun.back().GetAttributes() == pExistingRunPos->GetAttributes())
|
||||
{
|
||||
// Add the value from the existing run into the current new run position.
|
||||
size_t length = pNewRunPos->GetLength();
|
||||
size_t length = newRun.back().GetLength();
|
||||
length += pExistingRunPos->GetLength();
|
||||
pNewRunPos->SetLength(length);
|
||||
newRun.back().SetLength(length);
|
||||
|
||||
// Advance the existing run position since we consumed its value and merged it in.
|
||||
pExistingRunPos++;
|
||||
}
|
||||
|
||||
// OK. We're done inspecting the most recently copied cell for optimizations.
|
||||
pNewRunPos++;
|
||||
|
||||
// Now bulk copy any segments left in the original existing run
|
||||
if (pExistingRunPos < pExistingRunEnd)
|
||||
{
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), pNewRunPos);
|
||||
|
||||
// Fix up the end pointer so we know where we are for counting how much of the new run's memory space we used.
|
||||
pNewRunPos += (pExistingRunEnd - pExistingRunPos);
|
||||
std::copy_n(pExistingRunPos, (pExistingRunEnd - pExistingRunPos), std::back_inserter(newRun));
|
||||
}
|
||||
}
|
||||
|
||||
// OK, phew. We're done. Now we just need to free the existing run, store the new run in its place,
|
||||
// and update the count for the correct length of the new run now that we've filled it up.
|
||||
|
||||
newRun.erase(pNewRunPos, newRun.end());
|
||||
// OK, phew. We're done. Now we just need to free the existing run and store the new run in its place.
|
||||
_list.swap(newRun);
|
||||
|
||||
return S_OK;
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
|
||||
void Resize(const size_t newWidth);
|
||||
|
||||
[[nodiscard]] HRESULT InsertAttrRuns(const std::basic_string_view<TextAttributeRun> newAttrs,
|
||||
[[nodiscard]] HRESULT InsertAttrRuns(const gsl::span<const TextAttributeRun> newAttrs,
|
||||
const size_t iStart,
|
||||
const size_t iEnd,
|
||||
const size_t cBufferWidth);
|
||||
|
||||
@@ -112,34 +112,24 @@ OutputCellIterator::OutputCellIterator(const std::wstring_view utf16Text, const
|
||||
// - This is an iterator over legacy colors only. The text is not modified.
|
||||
// Arguments:
|
||||
// - legacyAttrs - One legacy color item per cell
|
||||
// - unused - useless bool to change function signature for legacyAttrs constructor because the C++ compiler in
|
||||
// razzle cannot distinguish between a std::wstring_view and a std::basic_string_view<WORD>
|
||||
// NOTE: This one internally casts to wchar_t because Razzle sees WORD and wchar_t as the same type
|
||||
// despite that Visual Studio build can tell the difference.
|
||||
#pragma warning(push)
|
||||
#pragma warning(suppress : 26490)
|
||||
// Suppresses reinterpret_cast. We're only doing this because Windows doesn't understand the type difference between wchar_t and DWORD.
|
||||
// It is not worth trying to separate that out further or risking performance over this particular warning here.
|
||||
// TODO GH 2673 - Investigate real wchar_t flag in Windows and resolve this audit issue
|
||||
OutputCellIterator::OutputCellIterator(const std::basic_string_view<WORD> legacyAttrs, const bool /*unused*/) noexcept :
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const WORD> legacyAttrs) noexcept :
|
||||
_mode(Mode::LegacyAttr),
|
||||
_currentView(s_GenerateViewLegacyAttr(legacyAttrs.at(0))),
|
||||
_run(std::wstring_view(reinterpret_cast<const wchar_t*>(legacyAttrs.data()), legacyAttrs.size())),
|
||||
_currentView(s_GenerateViewLegacyAttr(til::at(legacyAttrs, 0))),
|
||||
_run(legacyAttrs),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
_pos(0),
|
||||
_fillLimit(0)
|
||||
{
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
// Routine Description:
|
||||
// - This is an iterator over legacy cell data. We will use the unicode text and the legacy color attribute.
|
||||
// Arguments:
|
||||
// - charInfos - Multiple cell with unicode text and legacy color data.
|
||||
OutputCellIterator::OutputCellIterator(const std::basic_string_view<CHAR_INFO> charInfos) noexcept :
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept :
|
||||
_mode(Mode::CharInfo),
|
||||
_currentView(s_GenerateView(charInfos.at(0))),
|
||||
_currentView(s_GenerateView(til::at(charInfos, 0))),
|
||||
_run(charInfos),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
@@ -152,9 +142,9 @@ OutputCellIterator::OutputCellIterator(const std::basic_string_view<CHAR_INFO> c
|
||||
// - This is an iterator over existing OutputCells with full text and color data.
|
||||
// Arguments:
|
||||
// - cells - Multiple cells in a run
|
||||
OutputCellIterator::OutputCellIterator(const std::basic_string_view<OutputCell> cells) :
|
||||
OutputCellIterator::OutputCellIterator(const gsl::span<const OutputCell> cells) :
|
||||
_mode(Mode::Cell),
|
||||
_currentView(s_GenerateView(cells.at(0))),
|
||||
_currentView(s_GenerateView(til::at(cells, 0))),
|
||||
_run(cells),
|
||||
_attr(InvalidTextAttribute),
|
||||
_distance(0),
|
||||
@@ -190,15 +180,15 @@ OutputCellIterator::operator bool() const noexcept
|
||||
}
|
||||
case Mode::Cell:
|
||||
{
|
||||
return _pos < std::get<std::basic_string_view<OutputCell>>(_run).length();
|
||||
return _pos < std::get<gsl::span<const OutputCell>>(_run).size();
|
||||
}
|
||||
case Mode::CharInfo:
|
||||
{
|
||||
return _pos < std::get<std::basic_string_view<CHAR_INFO>>(_run).length();
|
||||
return _pos < std::get<gsl::span<const CHAR_INFO>>(_run).size();
|
||||
}
|
||||
case Mode::LegacyAttr:
|
||||
{
|
||||
return _pos < std::get<std::wstring_view>(_run).length();
|
||||
return _pos < std::get<gsl::span<const WORD>>(_run).size();
|
||||
}
|
||||
default:
|
||||
FAIL_FAST_HR(E_NOTIMPL);
|
||||
@@ -275,7 +265,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::basic_string_view<OutputCell>>(_run).at(_pos));
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const OutputCell>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -285,7 +275,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateView(std::get<std::basic_string_view<CHAR_INFO>>(_run).at(_pos));
|
||||
_currentView = s_GenerateView(til::at(std::get<gsl::span<const CHAR_INFO>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -295,7 +285,7 @@ OutputCellIterator& OutputCellIterator::operator++()
|
||||
_pos++;
|
||||
if (operator bool())
|
||||
{
|
||||
_currentView = s_GenerateViewLegacyAttr(std::get<std::wstring_view>(_run).at(_pos));
|
||||
_currentView = s_GenerateViewLegacyAttr(til::at(std::get<gsl::span<const WORD>>(_run), _pos));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ public:
|
||||
OutputCellIterator(const CHAR_INFO& charInfo, const size_t fillLimit = 0) noexcept;
|
||||
OutputCellIterator(const std::wstring_view utf16Text);
|
||||
OutputCellIterator(const std::wstring_view utf16Text, const TextAttribute attribute);
|
||||
OutputCellIterator(const std::basic_string_view<WORD> legacyAttributes, const bool unused) noexcept;
|
||||
OutputCellIterator(const std::basic_string_view<CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const std::basic_string_view<OutputCell> cells);
|
||||
OutputCellIterator(const gsl::span<const WORD> legacyAttributes) noexcept;
|
||||
OutputCellIterator(const gsl::span<const CHAR_INFO> charInfos) noexcept;
|
||||
OutputCellIterator(const gsl::span<const OutputCell> cells);
|
||||
~OutputCellIterator() = default;
|
||||
|
||||
OutputCellIterator& operator=(const OutputCellIterator& it) = default;
|
||||
@@ -86,12 +86,13 @@ private:
|
||||
};
|
||||
Mode _mode;
|
||||
|
||||
std::basic_string_view<WORD> _legacyAttrs;
|
||||
gsl::span<const WORD> _legacyAttrs;
|
||||
|
||||
std::variant<
|
||||
std::wstring_view,
|
||||
std::basic_string_view<CHAR_INFO>,
|
||||
std::basic_string_view<OutputCell>,
|
||||
gsl::span<const WORD>,
|
||||
gsl::span<const CHAR_INFO>,
|
||||
gsl::span<const OutputCell>,
|
||||
std::monostate>
|
||||
_run;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ gsl::span<OutputCell> OutputCellRect::GetRow(const size_t row)
|
||||
// - Read-only iterator of OutputCells
|
||||
OutputCellIterator OutputCellRect::GetRowIter(const size_t row) const
|
||||
{
|
||||
const std::basic_string_view<OutputCell> view(_FindRowOffset(row), _cols);
|
||||
const gsl::span<const OutputCell> view(_FindRowOffset(row), _cols);
|
||||
|
||||
return OutputCellIterator(view);
|
||||
}
|
||||
|
||||
@@ -5,18 +5,74 @@
|
||||
#include "TextAttribute.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
BYTE TextAttribute::s_legacyDefaultForeground = 7;
|
||||
BYTE TextAttribute::s_legacyDefaultBackground = 0;
|
||||
|
||||
// Routine Description:
|
||||
// - Returns a WORD with legacy-style attributes for this textattribute.
|
||||
// - Sets the legacy attributes which map to and from the default colors.
|
||||
// Parameters:
|
||||
// - defaultAttributes: the attribute values to be used for default colors.
|
||||
// Return value:
|
||||
// - a WORD with legacy-style attributes for this textattribute.
|
||||
WORD TextAttribute::GetLegacyAttributes(const WORD defaultAttributes) const noexcept
|
||||
// - None
|
||||
void TextAttribute::SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept
|
||||
{
|
||||
const BYTE fgIndex = _foreground.GetLegacyIndex(defaultAttributes & FG_ATTRS);
|
||||
const BYTE bgIndex = _background.GetLegacyIndex((defaultAttributes & BG_ATTRS) >> 4);
|
||||
s_legacyDefaultForeground = defaultAttributes & FG_ATTRS;
|
||||
s_legacyDefaultBackground = (defaultAttributes & BG_ATTRS) >> 4;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// Pursuant to GH#6807
|
||||
// This routine replaces VT colors from the 16-color set with the "default"
|
||||
// flag. It is intended to be used as part of the "VT Quirk" in
|
||||
// WriteConsole[AW].
|
||||
//
|
||||
// There is going to be a very long tail of applications that will
|
||||
// explicitly request VT SGR 40/37 when what they really want is to
|
||||
// SetConsoleTextAttribute() with a black background/white foreground.
|
||||
// Instead of making those applications look bad (and therefore making us
|
||||
// look bad, because we're releasing this as an update to something that
|
||||
// "looks good" already), we're introducing this compatibility hack. Before
|
||||
// the color reckoning in GH#6698 + GH#6506, *every* color was subject to
|
||||
// being spontaneously and erroneously turned into the default color. Now,
|
||||
// only the 16-color palette value that matches the active console
|
||||
// background color will be destroyed when the quirk is enabled.
|
||||
//
|
||||
// This is not intended to be a long-term solution. This comment will be
|
||||
// discovered in forty years(*) time and people will laugh at our hubris.
|
||||
//
|
||||
// *it doesn't matter when you're reading this, it will always be 40 years
|
||||
// from now.
|
||||
TextAttribute TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept
|
||||
{
|
||||
const auto fg{ attribute.GetForeground() };
|
||||
const auto bg{ attribute.GetBackground() };
|
||||
auto copy{ attribute };
|
||||
if (fg.IsIndex16() &&
|
||||
attribute.IsBold() == WI_IsFlagSet(s_legacyDefaultForeground, FOREGROUND_INTENSITY) &&
|
||||
fg.GetIndex() == (s_legacyDefaultForeground & ~FOREGROUND_INTENSITY))
|
||||
{
|
||||
// We don't want to turn 1;37m into 39m (or even 1;39m), as this was meant to mimic a legacy color.
|
||||
copy.SetDefaultForeground();
|
||||
}
|
||||
if (bg.IsIndex16() && bg.GetIndex() == s_legacyDefaultBackground)
|
||||
{
|
||||
copy.SetDefaultBackground();
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Returns a WORD with legacy-style attributes for this textattribute.
|
||||
// Parameters:
|
||||
// - None
|
||||
// Return value:
|
||||
// - a WORD with legacy-style attributes for this textattribute.
|
||||
WORD TextAttribute::GetLegacyAttributes() const noexcept
|
||||
{
|
||||
const BYTE fgIndex = _foreground.GetLegacyIndex(s_legacyDefaultForeground);
|
||||
const BYTE bgIndex = _background.GetLegacyIndex(s_legacyDefaultBackground);
|
||||
const WORD metaAttrs = _wAttrLegacy & META_ATTRS;
|
||||
const bool brighten = _foreground.IsIndex16() && IsBold();
|
||||
const bool brighten = IsBold() && _foreground.CanBeBrightened();
|
||||
return fgIndex | (bgIndex << 4) | metaAttrs | (brighten ? FOREGROUND_INTENSITY : 0);
|
||||
}
|
||||
|
||||
@@ -25,54 +81,55 @@ bool TextAttribute::IsLegacy() const noexcept
|
||||
return _foreground.IsLegacy() && _background.IsLegacy();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Calculates rgb colors based off of current color table and active modification attributes.
|
||||
// Arguments:
|
||||
// - None
|
||||
// - colorTable: the current color table rgb values.
|
||||
// - defaultFgColor: the default foreground color rgb value.
|
||||
// - defaultBgColor: the default background color rgb value.
|
||||
// - reverseScreenMode: true if the screen mode is reversed.
|
||||
// Return Value:
|
||||
// - color that should be displayed as the foreground color
|
||||
COLORREF TextAttribute::CalculateRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultFgColor,
|
||||
COLORREF defaultBgColor) const noexcept
|
||||
// - the foreground and background colors that should be displayed.
|
||||
std::pair<COLORREF, COLORREF> TextAttribute::CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode) const noexcept
|
||||
{
|
||||
return IsReverseVideo() ? _GetRgbBackground(colorTable, defaultBgColor) : _GetRgbForeground(colorTable, defaultFgColor);
|
||||
auto fg = _foreground.GetColor(colorTable, defaultFgColor, IsBold());
|
||||
auto bg = _background.GetColor(colorTable, defaultBgColor);
|
||||
if (IsFaint())
|
||||
{
|
||||
fg = (fg >> 1) & 0x7F7F7F; // Divide foreground color components by two.
|
||||
}
|
||||
if (IsReverseVideo() ^ reverseScreenMode)
|
||||
{
|
||||
std::swap(fg, bg);
|
||||
}
|
||||
if (IsInvisible())
|
||||
{
|
||||
fg = bg;
|
||||
}
|
||||
return { fg, bg };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Calculates rgb background color based off of current color table and active modification attributes
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - color that should be displayed as the background color
|
||||
COLORREF TextAttribute::CalculateRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultFgColor,
|
||||
COLORREF defaultBgColor) const noexcept
|
||||
TextColor TextAttribute::GetForeground() const noexcept
|
||||
{
|
||||
return IsReverseVideo() ? _GetRgbForeground(colorTable, defaultFgColor) : _GetRgbBackground(colorTable, defaultBgColor);
|
||||
return _foreground;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets rgb foreground color, possibly based off of current color table. Does not take active modification
|
||||
// attributes into account
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - color that is stored as the foreground color
|
||||
COLORREF TextAttribute::_GetRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept
|
||||
TextColor TextAttribute::GetBackground() const noexcept
|
||||
{
|
||||
return _foreground.GetColor(colorTable, defaultColor, IsBold());
|
||||
return _background;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets rgb background color, possibly based off of current color table. Does not take active modification
|
||||
// attributes into account
|
||||
// Arguments:
|
||||
// - None
|
||||
// Return Value:
|
||||
// - color that is stored as the background color
|
||||
COLORREF TextAttribute::_GetRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept
|
||||
void TextAttribute::SetForeground(const TextColor foreground) noexcept
|
||||
{
|
||||
return _background.GetColor(colorTable, defaultColor, false);
|
||||
_foreground = foreground;
|
||||
}
|
||||
|
||||
void TextAttribute::SetBackground(const TextColor background) noexcept
|
||||
{
|
||||
_background = background;
|
||||
}
|
||||
|
||||
void TextAttribute::SetForeground(const COLORREF rgbForeground) noexcept
|
||||
@@ -162,6 +219,11 @@ bool TextAttribute::IsBold() const noexcept
|
||||
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Bold);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsFaint() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Faint);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsItalic() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Italics);
|
||||
@@ -188,6 +250,11 @@ bool TextAttribute::IsUnderlined() const noexcept
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_UNDERSCORE);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsOverlined() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL);
|
||||
}
|
||||
|
||||
bool TextAttribute::IsReverseVideo() const noexcept
|
||||
{
|
||||
return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO);
|
||||
@@ -198,6 +265,11 @@ void TextAttribute::SetBold(bool isBold) noexcept
|
||||
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Bold, isBold);
|
||||
}
|
||||
|
||||
void TextAttribute::SetFaint(bool isFaint) noexcept
|
||||
{
|
||||
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Faint, isFaint);
|
||||
}
|
||||
|
||||
void TextAttribute::SetItalics(bool isItalic) noexcept
|
||||
{
|
||||
WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Italics, isItalic);
|
||||
@@ -224,6 +296,11 @@ void TextAttribute::SetUnderline(bool isUnderlined) noexcept
|
||||
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_UNDERSCORE, isUnderlined);
|
||||
}
|
||||
|
||||
void TextAttribute::SetOverline(bool isOverlined) noexcept
|
||||
{
|
||||
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined);
|
||||
}
|
||||
|
||||
void TextAttribute::SetReverseVideo(bool isReversed) noexcept
|
||||
{
|
||||
WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_REVERSE_VIDEO, isReversed);
|
||||
|
||||
@@ -42,8 +42,8 @@ public:
|
||||
|
||||
explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept :
|
||||
_wAttrLegacy{ gsl::narrow_cast<WORD>(wLegacyAttr & META_ATTRS) },
|
||||
_foreground{ gsl::narrow_cast<BYTE>(wLegacyAttr & FG_ATTRS), true },
|
||||
_background{ gsl::narrow_cast<BYTE>((wLegacyAttr & BG_ATTRS) >> 4), true },
|
||||
_foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) },
|
||||
_background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) },
|
||||
_extendedAttrs{ ExtendedAttributes::Normal }
|
||||
{
|
||||
// If we're given lead/trailing byte information with the legacy color, strip it.
|
||||
@@ -59,14 +59,14 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
WORD GetLegacyAttributes(const WORD defaultAttributes = 0x07) const noexcept;
|
||||
static void SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept;
|
||||
static TextAttribute StripErroneousVT16VersionsOfLegacyDefaults(const TextAttribute& attribute) noexcept;
|
||||
WORD GetLegacyAttributes() const noexcept;
|
||||
|
||||
COLORREF CalculateRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultFgColor,
|
||||
COLORREF defaultBgColor) const noexcept;
|
||||
COLORREF CalculateRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultFgColor,
|
||||
COLORREF defaultBgColor) const noexcept;
|
||||
std::pair<COLORREF, COLORREF> CalculateRgbColors(const gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultFgColor,
|
||||
const COLORREF defaultBgColor,
|
||||
const bool reverseScreenMode = false) const noexcept;
|
||||
|
||||
bool IsLeadingByte() const noexcept;
|
||||
bool IsTrailingByte() const noexcept;
|
||||
@@ -89,23 +89,31 @@ public:
|
||||
|
||||
bool IsLegacy() const noexcept;
|
||||
bool IsBold() const noexcept;
|
||||
bool IsFaint() const noexcept;
|
||||
bool IsItalic() const noexcept;
|
||||
bool IsBlinking() const noexcept;
|
||||
bool IsInvisible() const noexcept;
|
||||
bool IsCrossedOut() const noexcept;
|
||||
bool IsUnderlined() const noexcept;
|
||||
bool IsOverlined() const noexcept;
|
||||
bool IsReverseVideo() const noexcept;
|
||||
|
||||
void SetBold(bool isBold) noexcept;
|
||||
void SetFaint(bool isFaint) noexcept;
|
||||
void SetItalics(bool isItalic) noexcept;
|
||||
void SetBlinking(bool isBlinking) noexcept;
|
||||
void SetInvisible(bool isInvisible) noexcept;
|
||||
void SetCrossedOut(bool isCrossedOut) noexcept;
|
||||
void SetUnderline(bool isUnderlined) noexcept;
|
||||
void SetOverline(bool isOverlined) noexcept;
|
||||
void SetReverseVideo(bool isReversed) noexcept;
|
||||
|
||||
ExtendedAttributes GetExtendedAttributes() const noexcept;
|
||||
|
||||
TextColor GetForeground() const noexcept;
|
||||
TextColor GetBackground() const noexcept;
|
||||
void SetForeground(const TextColor foreground) noexcept;
|
||||
void SetBackground(const TextColor background) noexcept;
|
||||
void SetForeground(const COLORREF rgbForeground) noexcept;
|
||||
void SetBackground(const COLORREF rgbBackground) noexcept;
|
||||
void SetIndexedForeground(const BYTE fgIndex) noexcept;
|
||||
@@ -146,10 +154,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
COLORREF _GetRgbForeground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
COLORREF _GetRgbBackground(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF defaultColor) const noexcept;
|
||||
static constexpr TextColor s_LegacyIndexOrDefault(const BYTE requestedIndex, const BYTE defaultIndex)
|
||||
{
|
||||
return requestedIndex == defaultIndex ? TextColor{} : TextColor{ requestedIndex, true };
|
||||
}
|
||||
|
||||
static BYTE s_legacyDefaultForeground;
|
||||
static BYTE s_legacyDefaultBackground;
|
||||
|
||||
WORD _wAttrLegacy;
|
||||
TextColor _foreground;
|
||||
|
||||
@@ -50,6 +50,11 @@ constexpr std::array<BYTE, 256> Index256ToIndex16 = {
|
||||
|
||||
// clang-format on
|
||||
|
||||
bool TextColor::CanBeBrightened() const noexcept
|
||||
{
|
||||
return IsIndex16() || IsDefault();
|
||||
}
|
||||
|
||||
bool TextColor::IsLegacy() const noexcept
|
||||
{
|
||||
return IsIndex16() || (IsIndex256() && _index < 16);
|
||||
@@ -133,7 +138,7 @@ void TextColor::SetDefault() noexcept
|
||||
// - brighten: if true, we'll brighten a dark color table index.
|
||||
// Return Value:
|
||||
// - a COLORREF containing the real value of this TextColor.
|
||||
COLORREF TextColor::GetColor(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF TextColor::GetColor(gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultColor,
|
||||
bool brighten) const noexcept
|
||||
{
|
||||
@@ -153,9 +158,9 @@ COLORREF TextColor::GetColor(std::basic_string_view<COLORREF> colorTable,
|
||||
// If we find a match, return instead the bright version of this color
|
||||
for (size_t i = 0; i < 8; i++)
|
||||
{
|
||||
if (colorTable.at(i) == defaultColor)
|
||||
if (til::at(colorTable, i) == defaultColor)
|
||||
{
|
||||
return colorTable.at(i + 8);
|
||||
return til::at(colorTable, i + 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,15 +169,15 @@ COLORREF TextColor::GetColor(std::basic_string_view<COLORREF> colorTable,
|
||||
}
|
||||
else if (IsRgb())
|
||||
{
|
||||
return _GetRGB();
|
||||
return GetRGB();
|
||||
}
|
||||
else if (IsIndex16() && brighten)
|
||||
{
|
||||
return colorTable.at(_index | 8);
|
||||
return til::at(colorTable, _index | 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return colorTable.at(_index);
|
||||
return til::at(colorTable, _index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +219,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a COLORREF containing our stored value
|
||||
COLORREF TextColor::_GetRGB() const noexcept
|
||||
COLORREF TextColor::GetRGB() const noexcept
|
||||
{
|
||||
return RGB(_red, _green, _blue);
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
friend constexpr bool operator==(const TextColor& a, const TextColor& b) noexcept;
|
||||
friend constexpr bool operator!=(const TextColor& a, const TextColor& b) noexcept;
|
||||
|
||||
bool CanBeBrightened() const noexcept;
|
||||
bool IsLegacy() const noexcept;
|
||||
bool IsIndex16() const noexcept;
|
||||
bool IsIndex256() const noexcept;
|
||||
@@ -87,9 +88,9 @@ public:
|
||||
void SetIndex(const BYTE index, const bool isIndex256) noexcept;
|
||||
void SetDefault() noexcept;
|
||||
|
||||
COLORREF GetColor(std::basic_string_view<COLORREF> colorTable,
|
||||
COLORREF GetColor(gsl::span<const COLORREF> colorTable,
|
||||
const COLORREF defaultColor,
|
||||
const bool brighten) const noexcept;
|
||||
const bool brighten = false) const noexcept;
|
||||
|
||||
BYTE GetLegacyIndex(const BYTE defaultIndex) const noexcept;
|
||||
|
||||
@@ -98,6 +99,8 @@ public:
|
||||
return _index;
|
||||
}
|
||||
|
||||
COLORREF GetRGB() const noexcept;
|
||||
|
||||
private:
|
||||
ColorType _meta : 2;
|
||||
union
|
||||
@@ -107,8 +110,6 @@ private:
|
||||
BYTE _green;
|
||||
BYTE _blue;
|
||||
|
||||
COLORREF _GetRGB() const noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class TextBufferTests;
|
||||
template<typename TextColor>
|
||||
@@ -149,7 +150,7 @@ namespace WEX
|
||||
}
|
||||
else if (color.IsRgb())
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"{RGB:0x%06x}", color._GetRGB());
|
||||
return WEX::Common::NoThrowString().Format(L"{RGB:0x%06x}", color.GetRGB());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@ void UnicodeStorage::Erase(const key_type key)
|
||||
// - rowMap - A map of the old row IDs to the new row IDs.
|
||||
// - width - The width of the new row. Remove any items that are beyond the row width.
|
||||
// - Use nullopt if we're not resizing the width of the row, just renumbering the rows.
|
||||
void UnicodeStorage::Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
void UnicodeStorage::Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width)
|
||||
{
|
||||
// Make a temporary map to hold all the new row positioning
|
||||
std::unordered_map<key_type, mapped_type> newMap;
|
||||
|
||||
@@ -55,7 +55,7 @@ public:
|
||||
|
||||
void Erase(const key_type key);
|
||||
|
||||
void Remap(const std::map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
void Remap(const std::unordered_map<SHORT, SHORT>& rowMap, const std::optional<SHORT> width);
|
||||
|
||||
private:
|
||||
std::unordered_map<key_type, mapped_type> _map;
|
||||
|
||||
@@ -261,7 +261,7 @@ wchar_t Search::_ApplySensitivity(const wchar_t wch) const noexcept
|
||||
// - Helper to increment a coordinate in respect to the associated screen buffer
|
||||
// Arguments
|
||||
// - coord - Updated by function to increment one position (will wrap X and Y direction)
|
||||
void Search::_IncrementCoord(COORD& coord) const
|
||||
void Search::_IncrementCoord(COORD& coord) const noexcept
|
||||
{
|
||||
_uiaData.GetTextBuffer().GetSize().IncrementInBoundsCircular(coord);
|
||||
}
|
||||
@@ -270,7 +270,7 @@ void Search::_IncrementCoord(COORD& coord) const
|
||||
// - Helper to decrement a coordinate in respect to the associated screen buffer
|
||||
// Arguments
|
||||
// - coord - Updated by function to decrement one position (will wrap X and Y direction)
|
||||
void Search::_DecrementCoord(COORD& coord) const
|
||||
void Search::_DecrementCoord(COORD& coord) const noexcept
|
||||
{
|
||||
_uiaData.GetTextBuffer().GetSize().DecrementInBoundsCircular(coord);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ private:
|
||||
bool _CompareChars(const std::wstring_view one, const std::wstring_view two) const noexcept;
|
||||
void _UpdateNextPosition();
|
||||
|
||||
void _IncrementCoord(COORD& coord) const;
|
||||
void _DecrementCoord(COORD& coord) const;
|
||||
void _IncrementCoord(COORD& coord) const noexcept;
|
||||
void _DecrementCoord(COORD& coord) const noexcept;
|
||||
|
||||
static COORD s_GetInitialAnchor(Microsoft::Console::Types::IUiaData& uiaData, const Direction dir);
|
||||
|
||||
|
||||
@@ -33,13 +33,16 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
|
||||
_cursor{ cursorSize, *this },
|
||||
_storage{},
|
||||
_unicodeStorage{},
|
||||
_renderTarget{ renderTarget }
|
||||
_renderTarget{ renderTarget },
|
||||
_size{}
|
||||
{
|
||||
// initialize ROWs
|
||||
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
|
||||
{
|
||||
_storage.emplace_back(static_cast<SHORT>(i), screenBufferSize.X, _currentAttributes, this);
|
||||
}
|
||||
|
||||
_UpdateSize();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -623,7 +626,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
||||
// 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
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const noexcept
|
||||
{
|
||||
COORD coordPosition = GetCursor().GetPosition();
|
||||
|
||||
@@ -652,9 +655,15 @@ const SHORT TextBuffer::GetFirstRowIndex() const noexcept
|
||||
{
|
||||
return _firstRow;
|
||||
}
|
||||
const Viewport TextBuffer::GetSize() const
|
||||
|
||||
const Viewport TextBuffer::GetSize() const noexcept
|
||||
{
|
||||
return Viewport::FromDimensions({ 0, 0 }, { gsl::narrow<SHORT>(_storage.at(0).size()), gsl::narrow<SHORT>(_storage.size()) });
|
||||
return _size;
|
||||
}
|
||||
|
||||
void TextBuffer::_UpdateSize()
|
||||
{
|
||||
_size = Viewport::FromDimensions({ 0, 0 }, { gsl::narrow<SHORT>(_storage.at(0).size()), gsl::narrow<SHORT>(_storage.size()) });
|
||||
}
|
||||
|
||||
void TextBuffer::_SetFirstRowIndex(const SHORT FirstRowIndex) noexcept
|
||||
@@ -845,6 +854,9 @@ void TextBuffer::Reset()
|
||||
// Also take advantage of the row ID refresh loop to resize the rows in the X dimension
|
||||
// and cleanup the UnicodeStorage characters that might fall outside the resized buffer.
|
||||
_RefreshRowIDs(newSize.X);
|
||||
|
||||
// Update the cached size value
|
||||
_UpdateSize();
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
@@ -872,7 +884,7 @@ UnicodeStorage& TextBuffer::GetUnicodeStorage() noexcept
|
||||
// - newRowWidth - Optional new value for the row width.
|
||||
void TextBuffer::_RefreshRowIDs(std::optional<SHORT> newRowWidth)
|
||||
{
|
||||
std::map<SHORT, SHORT> rowMap;
|
||||
std::unordered_map<SHORT, SHORT> rowMap;
|
||||
SHORT i = 0;
|
||||
for (auto& it : _storage)
|
||||
{
|
||||
@@ -1449,18 +1461,16 @@ void TextBuffer::_ExpandTextRow(SMALL_RECT& textRow) const
|
||||
// - includeCRLF - inject CRLF pairs to the end of each line
|
||||
// - trimTrailingWhitespace - remove the trailing whitespace at the end of each line
|
||||
// - textRects - the rectangular regions from which the data will be extracted from the buffer (i.e.: selection rects)
|
||||
// - GetForegroundColor - function used to map TextAttribute to RGB COLORREF for foreground color. If null, only extract the text.
|
||||
// - GetBackgroundColor - function used to map TextAttribute to RGB COLORREF for background color. If null, only extract the text.
|
||||
// - GetAttributeColors - function used to map TextAttribute to RGB COLORREFs. If null, only extract the text.
|
||||
// Return Value:
|
||||
// - The text, background color, and foreground color data of the selected region of the text buffer.
|
||||
const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& selectionRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor) const
|
||||
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors) const
|
||||
{
|
||||
TextAndColor data;
|
||||
const bool copyTextColor = GetForegroundColor && GetBackgroundColor;
|
||||
const bool copyTextColor = GetAttributeColors != nullptr;
|
||||
|
||||
// preallocate our vectors to reduce reallocs
|
||||
size_t const rows = selectionRects.size();
|
||||
@@ -1505,9 +1515,8 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
|
||||
if (copyTextColor)
|
||||
{
|
||||
auto cellData = cell.TextAttr();
|
||||
COLORREF const CellFgAttr = GetForegroundColor(cellData);
|
||||
COLORREF const CellBkAttr = GetBackgroundColor(cellData);
|
||||
const auto cellData = cell.TextAttr();
|
||||
const auto [CellFgAttr, CellBkAttr] = GetAttributeColors(cellData);
|
||||
for (const wchar_t wch : cell.Chars())
|
||||
{
|
||||
selectionFgAttr.push_back(CellFgAttr);
|
||||
|
||||
@@ -110,7 +110,7 @@ public:
|
||||
|
||||
const SHORT GetFirstRowIndex() const noexcept;
|
||||
|
||||
const Microsoft::Console::Types::Viewport GetSize() const;
|
||||
const Microsoft::Console::Types::Viewport GetSize() const noexcept;
|
||||
|
||||
void ScrollRows(const SHORT firstRow, const SHORT size, const SHORT delta);
|
||||
|
||||
@@ -152,8 +152,7 @@ public:
|
||||
const TextAndColor GetText(const bool lineSelection,
|
||||
const bool trimTrailingWhitespace,
|
||||
const std::vector<SMALL_RECT>& textRects,
|
||||
std::function<COLORREF(TextAttribute&)> GetForegroundColor = nullptr,
|
||||
std::function<COLORREF(TextAttribute&)> GetBackgroundColor = nullptr) const;
|
||||
std::function<std::pair<COLORREF, COLORREF>(const TextAttribute&)> GetAttributeColors = nullptr) const;
|
||||
|
||||
static std::string GenHTML(const TextAndColor& rows,
|
||||
const int fontHeightPoints,
|
||||
@@ -177,6 +176,8 @@ public:
|
||||
std::optional<std::reference_wrapper<PositionInformation>> positionInfo);
|
||||
|
||||
private:
|
||||
void _UpdateSize();
|
||||
Microsoft::Console::Types::Viewport _size;
|
||||
std::deque<ROW> _storage;
|
||||
Cursor _cursor;
|
||||
|
||||
@@ -193,7 +194,7 @@ private:
|
||||
|
||||
void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
|
||||
|
||||
COORD _GetPreviousFromCursor() const;
|
||||
COORD _GetPreviousFromCursor() const noexcept;
|
||||
|
||||
void _SetWrapOnCurrentRow();
|
||||
void _AdjustWrapOnCurrentRow(const bool fSet);
|
||||
|
||||
@@ -21,12 +21,13 @@ class TextAttributeTests
|
||||
TEST_METHOD(TestRoundtripExhaustive);
|
||||
TEST_METHOD(TestTextAttributeColorGetters);
|
||||
TEST_METHOD(TestReverseDefaultColors);
|
||||
TEST_METHOD(TestRoundtripDefaultColors);
|
||||
|
||||
static const int COLOR_TABLE_SIZE = 16;
|
||||
COLORREF _colorTable[COLOR_TABLE_SIZE];
|
||||
COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
std::basic_string_view<COLORREF> _GetTableView();
|
||||
gsl::span<const COLORREF> _GetTableView();
|
||||
};
|
||||
|
||||
bool TextAttributeTests::ClassSetup()
|
||||
@@ -50,9 +51,9 @@ bool TextAttributeTests::ClassSetup()
|
||||
return true;
|
||||
}
|
||||
|
||||
std::basic_string_view<COLORREF> TextAttributeTests::_GetTableView()
|
||||
gsl::span<const COLORREF> TextAttributeTests::_GetTableView()
|
||||
{
|
||||
return std::basic_string_view<COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestRoundtripLegacy()
|
||||
@@ -117,13 +118,10 @@ void TextAttributeTests::TestRoundtripExhaustive()
|
||||
|
||||
auto attr = TextAttribute(wLegacy);
|
||||
|
||||
bool isLegacy = attr.IsLegacy();
|
||||
bool areEqual = (wLegacy == attr.GetLegacyAttributes());
|
||||
if (!(isLegacy && areEqual))
|
||||
if (wLegacy != attr.GetLegacyAttributes())
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Failed on wLegacy=0x%x", wLegacy));
|
||||
VERIFY_IS_TRUE(attr.IsLegacy());
|
||||
VERIFY_ARE_EQUAL(wLegacy, attr.GetLegacyAttributes());
|
||||
}
|
||||
}
|
||||
@@ -132,6 +130,7 @@ void TextAttributeTests::TestRoundtripExhaustive()
|
||||
void TextAttributeTests::TestTextAttributeColorGetters()
|
||||
{
|
||||
const COLORREF red = RGB(255, 0, 0);
|
||||
const COLORREF faintRed = RGB(127, 0, 0);
|
||||
const COLORREF green = RGB(0, 255, 0);
|
||||
TextAttribute attr(red, green);
|
||||
auto view = _GetTableView();
|
||||
@@ -140,21 +139,56 @@ void TextAttributeTests::TestTextAttributeColorGetters()
|
||||
// values when reverse video is not set
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(red, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(green, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(green, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(green, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(red, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
// reset the reverse video
|
||||
attr.SetReverseVideo(false);
|
||||
|
||||
// with faint set, the calculated foreground value should be fainter
|
||||
// while the background and getters stay the same
|
||||
attr.SetFaint(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(faintRed, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched, and the background fainter, while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, faintRed), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// reset the reverse video and faint attributes
|
||||
attr.SetReverseVideo(false);
|
||||
attr.SetFaint(false);
|
||||
|
||||
// with invisible set, the calculated foreground value should match the
|
||||
// background, while getters stay the same
|
||||
attr.SetInvisible(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(green, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, the calculated background value should match
|
||||
// the foreground, while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(red, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestReverseDefaultColors()
|
||||
@@ -168,40 +202,73 @@ void TextAttributeTests::TestReverseDefaultColors()
|
||||
// values when reverse video is not set
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
// with reverse video set, calculated foreground/background values should be
|
||||
// switched while getters stay the same
|
||||
attr.SetReverseVideo(true);
|
||||
VERIFY_IS_TRUE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, _defaultFg), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
attr.SetForeground(red);
|
||||
VERIFY_IS_TRUE(attr.IsReverseVideo());
|
||||
|
||||
VERIFY_ARE_EQUAL(red, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(red, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(red, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultBg, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultBg, red), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
|
||||
attr.Invert();
|
||||
VERIFY_IS_FALSE(attr.IsReverseVideo());
|
||||
attr.SetDefaultForeground();
|
||||
attr.SetBackground(green);
|
||||
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr._GetRgbForeground(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.CalculateRgbForeground(view, _defaultFg, _defaultBg));
|
||||
|
||||
VERIFY_ARE_EQUAL(green, attr._GetRgbBackground(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(green, attr.CalculateRgbBackground(view, _defaultFg, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(_defaultFg, attr.GetForeground().GetColor(view, _defaultFg));
|
||||
VERIFY_ARE_EQUAL(green, attr.GetBackground().GetColor(view, _defaultBg));
|
||||
VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, green), attr.CalculateRgbColors(view, _defaultFg, _defaultBg));
|
||||
}
|
||||
|
||||
void TextAttributeTests::TestRoundtripDefaultColors()
|
||||
{
|
||||
// Set the legacy default colors to yellow on blue.
|
||||
const BYTE fgLegacyDefault = FOREGROUND_RED;
|
||||
const BYTE bgLegacyDefault = BACKGROUND_BLUE;
|
||||
TextAttribute::SetLegacyDefaultAttributes(fgLegacyDefault | bgLegacyDefault);
|
||||
|
||||
WORD legacyAttribute;
|
||||
TextAttribute textAttribute;
|
||||
|
||||
Log::Comment(L"Foreground legacy default index should map to default text color.");
|
||||
legacyAttribute = fgLegacyDefault | BACKGROUND_GREEN;
|
||||
textAttribute.SetDefaultForeground();
|
||||
textAttribute.SetIndexedBackground256(BACKGROUND_GREEN >> 4);
|
||||
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
|
||||
|
||||
Log::Comment(L"Default foreground text color should map back to legacy default index.");
|
||||
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
|
||||
|
||||
Log::Comment(L"Background legacy default index should map to default text color.");
|
||||
legacyAttribute = FOREGROUND_GREEN | bgLegacyDefault;
|
||||
textAttribute.SetIndexedForeground256(FOREGROUND_GREEN);
|
||||
textAttribute.SetDefaultBackground();
|
||||
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
|
||||
|
||||
Log::Comment(L"Default background text color should map back to legacy default index.");
|
||||
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
|
||||
|
||||
Log::Comment(L"Foreground and background legacy defaults should map to default text colors.");
|
||||
legacyAttribute = fgLegacyDefault | bgLegacyDefault;
|
||||
textAttribute.SetDefaultForeground();
|
||||
textAttribute.SetDefaultBackground();
|
||||
VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute });
|
||||
|
||||
Log::Comment(L"Default foreground and background text colors should map back to legacy defaults.");
|
||||
VERIFY_ARE_EQUAL(legacyAttribute, textAttribute.GetLegacyAttributes());
|
||||
|
||||
// Reset the legacy default colors to white on black.
|
||||
TextAttribute::SetLegacyDefaultAttributes(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class TextColorTests
|
||||
COLORREF _colorTable[COLOR_TABLE_SIZE];
|
||||
COLORREF _defaultFg = RGB(1, 2, 3);
|
||||
COLORREF _defaultBg = RGB(4, 5, 6);
|
||||
std::basic_string_view<COLORREF> _GetTableView();
|
||||
gsl::span<const COLORREF> _GetTableView();
|
||||
};
|
||||
|
||||
bool TextColorTests::ClassSetup()
|
||||
@@ -51,9 +51,9 @@ bool TextColorTests::ClassSetup()
|
||||
return true;
|
||||
}
|
||||
|
||||
std::basic_string_view<COLORREF> TextColorTests::_GetTableView()
|
||||
gsl::span<const COLORREF> TextColorTests::_GetTableView()
|
||||
{
|
||||
return std::basic_string_view<COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
return gsl::span<const COLORREF>(&_colorTable[0], COLOR_TABLE_SIZE);
|
||||
}
|
||||
|
||||
void TextColorTests::TestDefaultColor()
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
-->
|
||||
<AppxOSMinVersionReplaceManifestVersion>false</AppxOSMinVersionReplaceManifestVersion>
|
||||
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
|
||||
<OCExecutionAliasName Condition="'$(WindowsTerminalBranding)'==''">wtd</OCExecutionAliasName>
|
||||
<OCExecutionAliasName Condition="'$(OCExecutionAliasName)'==''">wt</OCExecutionAliasName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>CA5CAD1A-224A-4171-B13A-F16E576FDD12</ProjectGuid>
|
||||
@@ -66,6 +68,7 @@
|
||||
<ProjectReference Include="..\..\host\exe\Host.EXE.vcxproj" />
|
||||
<ProjectReference Include="..\TerminalAzBridge\TerminalAzBridge.vcxproj" />
|
||||
<ProjectReference Include="..\ShellExtension\WindowsTerminalShellExt.vcxproj" />
|
||||
<ProjectReference Include="..\wt\wt.vcxproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="OpenConsoleStompSourceProjectForWapProject" BeforeTargets="_ConvertItems">
|
||||
@@ -77,6 +80,8 @@
|
||||
<!-- Blank the SourceProject here to vend all files into the root of the package. -->
|
||||
<SourceProject>
|
||||
</SourceProject>
|
||||
<!-- Replace the filename for wt/wtd.exe with the one the manifest wants. -->
|
||||
<TargetPath Condition="'%(Filename)' == 'wt' and '%(Extension)' == '.exe'">$(OCExecutionAliasName).exe</TargetPath>
|
||||
</_FilteredNonWapProjProjectOutput>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
@@ -142,12 +147,12 @@
|
||||
<!-- **END VC LIBS HACK** -->
|
||||
|
||||
<!-- This is required to get the package dependency in the AppXManifest. -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
|
||||
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="WindowsTerminal.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="wtd.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="wtd.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="WindowsTerminal.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="wt.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="wt.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
</uap:VisualElements>
|
||||
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="WindowsTerminal.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:Extension Category="windows.appExecutionAlias" Executable="wt.exe" EntryPoint="Windows.FullTrustApplication">
|
||||
<uap3:AppExecutionAlias>
|
||||
<desktop:ExecutionAlias Alias="wt.exe" />
|
||||
</uap3:AppExecutionAlias>
|
||||
|
||||
349
src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
Normal file
@@ -0,0 +1,349 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/CascadiaSettings.h"
|
||||
#include "JsonTestClass.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
|
||||
// an updated TAEF that will let us install framework packages when the test
|
||||
// package is deployed. Until then, these tests won't deploy in CI.
|
||||
|
||||
class CommandTests : public JsonTestClass
|
||||
{
|
||||
// Use a custom AppxManifest to ensure that we can activate winrt types
|
||||
// from our test. This property will tell taef to manually use this as
|
||||
// the AppxManifest for this test class.
|
||||
// This does not yet work for anything XAML-y. See TabTests.cpp for more
|
||||
// details on that.
|
||||
BEGIN_TEST_CLASS(CommandTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ManyCommandsSameAction);
|
||||
TEST_METHOD(LayerCommand);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
TEST_METHOD(TestResourceKeyName);
|
||||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void CommandTests::ManyCommandsSameAction()
|
||||
{
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action1", "command": { "action": "copy", "singleLine": false } } ])" };
|
||||
const std::string commands2String{ R"([
|
||||
{ "name":"action2", "command": "paste" },
|
||||
{ "name":"action3", "command": "paste" }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(2u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
}
|
||||
|
||||
void CommandTests::LayerCommand()
|
||||
{
|
||||
// Each one of the commands in this test should layer upon the previous, overriding the action.
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action0", "command": "paste" } ])" };
|
||||
const std::string commands2String{ R"([ { "name":"action0", "command": "newTab" } ])" };
|
||||
const std::string commands3String{ R"([ { "name":"action0", "command": null } ])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
const auto commands3Json = VerifyParseSucceeded(commands3String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
|
||||
VERIFY_IS_NULL(command.Action().Args());
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
// This last command should "unbind" the action.
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestSplitPaneArgs()
|
||||
{
|
||||
// This is the same as KeyBindingsTests::TestSplitPaneArgs, but with
|
||||
// looking up the action and its args from a map of commands, instead
|
||||
// of from keybindings.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
|
||||
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "name": "command4", "command": { "action": "splitPane" } },
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"command0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestResourceKeyName()
|
||||
{
|
||||
// This test checks looking up a name from a resource key.
|
||||
|
||||
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
// NOTE: We're relying on DuplicateTabCommandKey being defined as
|
||||
// "Duplicate Tab" here. If that string changes in our resources,
|
||||
// this test will break.
|
||||
auto command = commands.at(L"Duplicate tab");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestAutogeneratedName()
|
||||
{
|
||||
// This test ensures that we'll correctly create commands for actions
|
||||
// that don't have given names, pursuant to the spec in GH#6532.
|
||||
|
||||
// NOTE: The keys used to look up these commands are partially generated
|
||||
// from strings in our Resources.resw. If those string values should
|
||||
// change, it's likely that this test will break.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane", "split": null } },
|
||||
{ "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
|
||||
// There are only 3 commands here: all of the `"none"`, `"auto"`,
|
||||
// `"foo"`, `null`, and <no args> bindings all generate the same action,
|
||||
// which will generate just a single name for all of them.
|
||||
VERIFY_ARE_EQUAL(3u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestLayerOnAutogeneratedName()
|
||||
{
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "name":"Split pane", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,9 +388,16 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void CommandlineTest::ParseNewTabCommand()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `nt` instead of `new-tab`");
|
||||
const wchar_t* subcommand = useShortForm ? L"nt" : L"new-tab";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -409,7 +416,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"--profile", L"cmd" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--profile", L"cmd" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -429,7 +436,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"--startingDirectory", L"c:\\Foo" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"--startingDirectory", L"c:\\Foo" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -449,7 +456,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"powershell.exe" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -469,7 +476,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg with spaces" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"powershell.exe", L"This is an arg with spaces" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -490,7 +497,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg with spaces", L"another-arg", L"more spaces in this one" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"powershell.exe", L"This is an arg with spaces", L"another-arg", L"more spaces in this one" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -511,7 +518,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"-p", L"Windows PowerShell" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"Windows PowerShell" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -531,7 +538,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"wsl", L"-d", L"Alpine" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"wsl", L"-d", L"Alpine" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -551,7 +558,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"-p", L"1", L"wsl", L"-d", L"Alpine" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"1", L"wsl", L"-d", L"Alpine" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -574,9 +581,16 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void CommandlineTest::ParseSplitPaneIntoArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `sp` instead of `split-pane`");
|
||||
const wchar_t* subcommand = useShortForm ? L"sp" : L"split-pane";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
@@ -595,7 +609,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-H" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-H" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
@@ -614,7 +628,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-V" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-V" };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
@@ -635,7 +649,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"wsl", L"-d", L"Alpine" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"1", L"wsl", L"-d", L"Alpine" };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
@@ -662,7 +676,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"-H", L"wsl", L"-d", L"Alpine" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"1", L"-H", L"wsl", L"-d", L"Alpine" };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
@@ -689,7 +703,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"wsl", L"-d", L"Alpine", L"-H" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"1", L"wsl", L"-d", L"Alpine", L"-H" };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
@@ -718,8 +732,18 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void CommandlineTest::ParseComboCommandlineIntoArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortFormNewTab", L"{false, true}")
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortFormSplitPane", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortFormNewTab, L"If true, use `nt` instead of `new-tab`");
|
||||
INIT_TEST_PROPERTY(bool, useShortFormSplitPane, L"If true, use `sp` instead of `split-pane`");
|
||||
const wchar_t* ntSubcommand = useShortFormNewTab ? L"nt" : L"new-tab";
|
||||
const wchar_t* spSubcommand = useShortFormSplitPane ? L"sp" : L"split-pane";
|
||||
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L";", L"split-pane" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", ntSubcommand, L";", spSubcommand };
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
@@ -834,9 +858,16 @@ namespace TerminalAppLocalTests
|
||||
|
||||
void CommandlineTest::ParseFocusTabArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `ft` instead of `focus-tab`");
|
||||
const wchar_t* subcommand = useShortForm ? L"ft" : L"focus-tab";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
|
||||
@@ -846,7 +877,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-n" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-n" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
@@ -860,7 +891,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-p" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
@@ -874,7 +905,7 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-t", L"2" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-t", L"2" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
@@ -894,7 +925,7 @@ namespace TerminalAppLocalTests
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Attempt an invalid combination of flags"));
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-p", L"-n" };
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-p", L"-n" };
|
||||
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(1u, commandlines.size());
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestStringOverload);
|
||||
|
||||
TEST_METHOD(TestSetTabColorArgs);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -400,6 +402,63 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestSetTabColorArgs()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
{ "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } },
|
||||
{ "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } },
|
||||
{ "keys": ["ctrl+e"], "command": { "action": "setTabColor", "color": "thisStringObviouslyWontWork" } },
|
||||
{ "keys": ["ctrl+f"], "command": "setTabColor" },
|
||||
])" };
|
||||
|
||||
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
|
||||
|
||||
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
|
||||
VERIFY_IS_NOT_NULL(appKeyBindings);
|
||||
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
|
||||
appKeyBindings->LayerJson(bindings0Json);
|
||||
VERIFY_ARE_EQUAL(4u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NOT_NULL(realArgs.TabColor());
|
||||
// Remember that COLORREFs are actually BBGGRR order, while the string is in #RRGGBB order
|
||||
VERIFY_ARE_EQUAL(static_cast<uint32_t>(til::color(0x563412)), realArgs.TabColor().Value());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SetTabColorArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_IS_NULL(realArgs.TabColor());
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBindingsTests::TestStringOverload()
|
||||
{
|
||||
const std::string bindings0String{ R"([
|
||||
|
||||
@@ -80,6 +80,8 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestTrailingCommas);
|
||||
|
||||
TEST_METHOD(TestCommandsAndKeybindings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
@@ -464,15 +466,13 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
"alwaysShowTabs": true,
|
||||
"initialCols" : 120,
|
||||
"initialRows" : 30,
|
||||
"rowsToScroll" : 4
|
||||
"initialRows" : 30
|
||||
})" };
|
||||
const std::string settings1String{ R"(
|
||||
{
|
||||
"showTabsInTitlebar": false,
|
||||
"initialCols" : 240,
|
||||
"initialRows" : 60,
|
||||
"rowsToScroll" : 8
|
||||
"initialRows" : 60
|
||||
})" };
|
||||
const auto settings0Json = VerifyParseSucceeded(settings0String);
|
||||
const auto settings1Json = VerifyParseSucceeded(settings1String);
|
||||
@@ -483,14 +483,12 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(true, settings._globals._AlwaysShowTabs);
|
||||
VERIFY_ARE_EQUAL(120, settings._globals._InitialCols);
|
||||
VERIFY_ARE_EQUAL(30, settings._globals._InitialRows);
|
||||
VERIFY_ARE_EQUAL(4, settings._globals._RowsToScroll);
|
||||
VERIFY_ARE_EQUAL(true, settings._globals._ShowTabsInTitlebar);
|
||||
|
||||
settings.LayerJson(settings1Json);
|
||||
VERIFY_ARE_EQUAL(true, settings._globals._AlwaysShowTabs);
|
||||
VERIFY_ARE_EQUAL(240, settings._globals._InitialCols);
|
||||
VERIFY_ARE_EQUAL(60, settings._globals._InitialRows);
|
||||
VERIFY_ARE_EQUAL(8, settings._globals._RowsToScroll);
|
||||
VERIFY_ARE_EQUAL(false, settings._globals._ShowTabsInTitlebar);
|
||||
}
|
||||
|
||||
@@ -2331,4 +2329,204 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_IS_TRUE(false, L"This call to LayerJson should succeed, even with the trailing comma");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestCommandsAndKeybindings()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{ "keys": "ctrl+a", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "ctrl+b", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+c", "name": "ctrl+c", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+d", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+e", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "keys": "ctrl+f", "name":null, "command": { "action": "splitPane", "split": "horizontal" } }
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
CascadiaSettings settings{};
|
||||
settings._ParseJsonString(settingsJson, false);
|
||||
settings.LayerJson(settings._userSettings);
|
||||
settings._ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.GetProfiles().size());
|
||||
|
||||
const auto profile2Guid = settings._profiles.at(2).GetGuid();
|
||||
VERIFY_ARE_NOT_EQUAL(GUID{ 0 }, profile2Guid);
|
||||
|
||||
auto appKeyBindings = settings._globals._keybindings;
|
||||
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
// A/D, B, C, E will be in the list of commands, for 4 total.
|
||||
// * A and D share the same name, so they'll only generate a single action.
|
||||
// * F's name is set manually to `null`
|
||||
auto commands = settings._globals.GetCommands();
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Now verify the commands");
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+b");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+c");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<ClCompile Include="ProfileTests.cpp" />
|
||||
<ClCompile Include="ColorSchemeTests.cpp" />
|
||||
<ClCompile Include="KeyBindingsTests.cpp" />
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="TabTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
@@ -114,11 +115,11 @@
|
||||
<!-- From Microsoft.UI.Xaml.targets -->
|
||||
<Native-Platform Condition="'$(Platform)' == 'Win32'">x86</Native-Platform>
|
||||
<Native-Platform Condition="'$(Platform)' != 'Win32'">$(Platform)</Native-Platform>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
<_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\runtimes\win10-$(Native-Platform)\native\"</_MUXBinRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- We actually can just straight up reference MUX here, it's fine -->
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets" Condition="Exists('..\..\..\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\build\native\Microsoft.Toolkit.Win32.UI.XamlApplication.targets')" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
<AppContainerApplication>true</AppContainerApplication>
|
||||
<WindowsStoreApp>true</WindowsStoreApp>
|
||||
<ApplicationType>Windows Store</ApplicationType>
|
||||
<WindowsTargetPlatformMinVersion>10.0.18362.0</WindowsTargetPlatformMinVersion>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
@@ -121,7 +122,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.4.2-prerelease.200604001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Import Project="$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.200609001\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
|
||||
<Import Project="$(OpenConsoleDir)\src\common.build.post.props" />
|
||||
|
||||
|
||||
@@ -21,26 +21,7 @@ static constexpr bool _IsMouseMessage(UINT uMsg)
|
||||
return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK ||
|
||||
uMsg == WM_MBUTTONDOWN || uMsg == WM_MBUTTONUP || uMsg == WM_MBUTTONDBLCLK ||
|
||||
uMsg == WM_RBUTTONDOWN || uMsg == WM_RBUTTONUP || uMsg == WM_RBUTTONDBLCLK ||
|
||||
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL;
|
||||
}
|
||||
|
||||
// Helper static function to ensure that all ambiguous-width glyphs are reported as narrow.
|
||||
// See microsoft/terminal#2066 for more info.
|
||||
static bool _IsGlyphWideForceNarrowFallback(const std::wstring_view /* glyph */) noexcept
|
||||
{
|
||||
return false; // glyph is not wide.
|
||||
}
|
||||
|
||||
static bool _EnsureStaticInitialization()
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
static bool initialized = []() {
|
||||
// *** THIS IS A SINGLETON ***
|
||||
SetGlyphWidthFallback(_IsGlyphWideForceNarrowFallback);
|
||||
|
||||
return true;
|
||||
}();
|
||||
return initialized;
|
||||
uMsg == WM_MOUSEMOVE || uMsg == WM_MOUSEWHEEL;
|
||||
}
|
||||
|
||||
LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
|
||||
@@ -55,29 +36,10 @@ try
|
||||
|
||||
if (terminal)
|
||||
{
|
||||
if (_IsMouseMessage(uMsg))
|
||||
if (_IsMouseMessage(uMsg) && terminal->_CanSendVTMouseInput())
|
||||
{
|
||||
if (terminal->_CanSendVTMouseInput() && terminal->_SendMouseEvent(uMsg, wParam, lParam))
|
||||
if (terminal->_SendMouseEvent(uMsg, wParam, lParam))
|
||||
{
|
||||
// GH#6401: Capturing the mouse ensures that we get drag/release events
|
||||
// even if the user moves outside the window.
|
||||
// _SendMouseEvent returns false if the terminal's not in VT mode, so we'll
|
||||
// fall through to release the capture.
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_MBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
SetCapture(hwnd);
|
||||
break;
|
||||
case WM_LBUTTONUP:
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
ReleaseCapture();
|
||||
break;
|
||||
}
|
||||
|
||||
// Suppress all mouse events that made it into the terminal.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -95,10 +57,6 @@ try
|
||||
return 0;
|
||||
case WM_LBUTTONUP:
|
||||
terminal->_singleClickTouchdownPos = std::nullopt;
|
||||
[[fallthrough]];
|
||||
case WM_MBUTTONUP:
|
||||
case WM_RBUTTONUP:
|
||||
ReleaseCapture();
|
||||
break;
|
||||
case WM_MOUSEMOVE:
|
||||
if (WI_IsFlagSet(wParam, MK_LBUTTON))
|
||||
@@ -114,7 +72,7 @@ try
|
||||
{
|
||||
const auto bufferData = terminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
LOG_IF_FAILED(terminal->_CopyTextToSystemClipboard(bufferData, true));
|
||||
TerminalClearSelection(terminal);
|
||||
terminal->_terminal->ClearSelection();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
@@ -123,11 +81,6 @@ try
|
||||
terminal->_PasteTextFromClipboard();
|
||||
}
|
||||
return 0;
|
||||
case WM_DESTROY:
|
||||
// Release Terminal's hwnd so Teardown doesn't try to destroy it again
|
||||
terminal->_hwnd.release();
|
||||
terminal->Teardown();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
@@ -161,16 +114,14 @@ static bool RegisterTermClass(HINSTANCE hInstance) noexcept
|
||||
}
|
||||
|
||||
HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
||||
_desiredFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8 },
|
||||
_actualFont{ L"Consolas", 0, DEFAULT_FONT_WEIGHT, { 0, 14 }, CP_UTF8, false },
|
||||
_desiredFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8 },
|
||||
_actualFont{ L"Consolas", 0, 10, { 0, 14 }, CP_UTF8, false },
|
||||
_uiaProvider{ nullptr },
|
||||
_uiaProviderInitialized{ false },
|
||||
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_pfnWriteCallback{ nullptr },
|
||||
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
|
||||
{
|
||||
_EnsureStaticInitialization();
|
||||
|
||||
HINSTANCE hInstance = wil::GetModuleInstanceHandle();
|
||||
|
||||
if (RegisterTermClass(hInstance))
|
||||
@@ -197,11 +148,6 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
|
||||
}
|
||||
}
|
||||
|
||||
HwndTerminal::~HwndTerminal()
|
||||
{
|
||||
Teardown();
|
||||
}
|
||||
|
||||
HRESULT HwndTerminal::Initialize()
|
||||
{
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
@@ -216,6 +162,9 @@ HRESULT HwndTerminal::Initialize()
|
||||
RETURN_IF_FAILED(dxEngine->Enable());
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
|
||||
const auto pfn = std::bind(&::Microsoft::Console::Render::Renderer::IsGlyphWideByFont, _renderer.get(), std::placeholders::_1);
|
||||
SetGlyphWidthFallback(pfn);
|
||||
|
||||
_UpdateFont(USER_DEFAULT_SCREEN_DPI);
|
||||
RECT windowRect;
|
||||
GetWindowRect(_hwnd.get(), &windowRect);
|
||||
@@ -232,8 +181,8 @@ HRESULT HwndTerminal::Initialize()
|
||||
_terminal->SetBackgroundCallback([](auto) {});
|
||||
|
||||
_terminal->Create(COORD{ 80, 25 }, 1000, *_renderer);
|
||||
_terminal->SetDefaultBackground(RGB(12, 12, 12));
|
||||
_terminal->SetDefaultForeground(RGB(204, 204, 204));
|
||||
_terminal->SetDefaultBackground(RGB(5, 27, 80));
|
||||
_terminal->SetDefaultForeground(RGB(255, 255, 255));
|
||||
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
@@ -242,33 +191,6 @@ HRESULT HwndTerminal::Initialize()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void HwndTerminal::Teardown() noexcept
|
||||
try
|
||||
{
|
||||
// As a rule, detach resources from the Terminal before shutting them down.
|
||||
// This ensures that teardown is reentrant.
|
||||
|
||||
// Shut down the renderer (and therefore the thread) before we implode
|
||||
if (auto localRenderEngine{ std::exchange(_renderEngine, nullptr) })
|
||||
{
|
||||
if (auto localRenderer{ std::exchange(_renderer, nullptr) })
|
||||
{
|
||||
localRenderer->TriggerTeardown();
|
||||
// renderer is destroyed
|
||||
}
|
||||
// renderEngine is destroyed
|
||||
}
|
||||
|
||||
if (auto localHwnd{ _hwnd.release() })
|
||||
{
|
||||
// If we're being called through WM_DESTROY, we won't get here (hwnd is already released)
|
||||
// If we're not, we may end up in Teardown _again_... but by the time we do, all other
|
||||
// resources have been released and will not be released again.
|
||||
DestroyWindow(localHwnd);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
void HwndTerminal::RegisterScrollCallback(std::function<void(int, int, int)> callback)
|
||||
{
|
||||
_terminal->SetScrollPositionChangedCallback(callback);
|
||||
@@ -545,21 +467,11 @@ try
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
void HwndTerminal::_ClearSelection() noexcept
|
||||
try
|
||||
{
|
||||
auto lock{ _terminal->LockForWriting() };
|
||||
_terminal->ClearSelection();
|
||||
_renderer->TriggerSelection();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
void _stdcall TerminalClearSelection(void* terminal)
|
||||
{
|
||||
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
publicTerminal->_ClearSelection();
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
publicTerminal->_terminal->ClearSelection();
|
||||
}
|
||||
|
||||
bool _stdcall TerminalIsSelectionActive(void* terminal)
|
||||
{
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
@@ -570,10 +482,9 @@ bool _stdcall TerminalIsSelectionActive(void* terminal)
|
||||
// Returns the selected text in the terminal.
|
||||
const wchar_t* _stdcall TerminalGetSelection(void* terminal)
|
||||
{
|
||||
auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
|
||||
|
||||
const auto bufferData = publicTerminal->_terminal->RetrieveSelectedTextFromBuffer(false);
|
||||
publicTerminal->_ClearSelection();
|
||||
|
||||
// convert text: vector<string> --> string
|
||||
std::wstring selectedText;
|
||||
@@ -583,6 +494,8 @@ const wchar_t* _stdcall TerminalGetSelection(void* terminal)
|
||||
}
|
||||
|
||||
auto returnText = wil::make_cotaskmem_string_nothrow(selectedText.c_str());
|
||||
TerminalClearSelection(terminal);
|
||||
|
||||
return returnText.release();
|
||||
}
|
||||
|
||||
@@ -628,21 +541,16 @@ bool HwndTerminal::_CanSendVTMouseInput() const noexcept
|
||||
bool HwndTerminal::_SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept
|
||||
try
|
||||
{
|
||||
til::point cursorPosition{
|
||||
const til::point cursorPosition{
|
||||
GET_X_LPARAM(lParam),
|
||||
GET_Y_LPARAM(lParam),
|
||||
};
|
||||
|
||||
const til::size fontSize{ this->_actualFont.GetSize() };
|
||||
short wheelDelta{ 0 };
|
||||
if (uMsg == WM_MOUSEWHEEL || uMsg == WM_MOUSEHWHEEL)
|
||||
if (uMsg == WM_MOUSEWHEEL)
|
||||
{
|
||||
wheelDelta = HIWORD(wParam);
|
||||
|
||||
// If it's a *WHEEL event, it's in screen coordinates, not window (?!)
|
||||
POINT coordsToTransform = cursorPosition;
|
||||
ScreenToClient(_hwnd.get(), &coordsToTransform);
|
||||
cursorPosition = coordsToTransform;
|
||||
}
|
||||
|
||||
return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta);
|
||||
@@ -653,24 +561,20 @@ catch (...)
|
||||
return false;
|
||||
}
|
||||
|
||||
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept
|
||||
void HwndTerminal::_SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept
|
||||
try
|
||||
{
|
||||
auto modifiers = getControlKeyState();
|
||||
if (WI_IsFlagSet(flags, ENHANCED_KEY))
|
||||
{
|
||||
modifiers |= ControlKeyStates::EnhancedKey;
|
||||
}
|
||||
_terminal->SendKeyEvent(vkey, scanCode, modifiers, keyDown);
|
||||
const auto flags = getControlKeyState();
|
||||
_terminal->SendKeyEvent(vkey, scanCode, flags, keyDown);
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept
|
||||
void HwndTerminal::_SendCharEvent(wchar_t ch, WORD scanCode) noexcept
|
||||
try
|
||||
{
|
||||
if (_terminal->IsSelectionActive())
|
||||
{
|
||||
_ClearSelection();
|
||||
_terminal->ClearSelection();
|
||||
if (ch == UNICODE_ESC)
|
||||
{
|
||||
// ESC should clear any selection before it triggers input.
|
||||
@@ -685,25 +589,21 @@ try
|
||||
return;
|
||||
}
|
||||
|
||||
auto modifiers = getControlKeyState();
|
||||
if (WI_IsFlagSet(flags, ENHANCED_KEY))
|
||||
{
|
||||
modifiers |= ControlKeyStates::EnhancedKey;
|
||||
}
|
||||
_terminal->SendCharEvent(ch, scanCode, modifiers);
|
||||
const auto flags = getControlKeyState();
|
||||
_terminal->SendCharEvent(ch, scanCode, flags);
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown)
|
||||
void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown)
|
||||
{
|
||||
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
publicTerminal->_SendKeyEvent(vkey, scanCode, flags, keyDown);
|
||||
publicTerminal->_SendKeyEvent(vkey, scanCode, keyDown);
|
||||
}
|
||||
|
||||
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags)
|
||||
void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode)
|
||||
{
|
||||
const auto publicTerminal = static_cast<HwndTerminal*>(terminal);
|
||||
publicTerminal->_SendCharEvent(ch, scanCode, flags);
|
||||
publicTerminal->_SendCharEvent(ch, scanCode);
|
||||
}
|
||||
|
||||
void _stdcall DestroyTerminal(void* terminal)
|
||||
@@ -732,7 +632,7 @@ void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR font
|
||||
|
||||
publicTerminal->_terminal->SetCursorStyle(theme.CursorStyle);
|
||||
|
||||
publicTerminal->_desiredFont = { fontFamily, 0, DEFAULT_FONT_WEIGHT, { 0, fontSize }, CP_UTF8 };
|
||||
publicTerminal->_desiredFont = { fontFamily, 0, 10, { 0, fontSize }, CP_UTF8 };
|
||||
publicTerminal->_UpdateFont(newDpi);
|
||||
|
||||
// When the font changes the terminal dimensions need to be recalculated since the available row and column
|
||||
@@ -832,7 +732,7 @@ try
|
||||
{
|
||||
const auto& fontData = _actualFont;
|
||||
int const iFontHeightPoints = fontData.GetUnscaledSize().Y; // this renderer uses points already
|
||||
const COLORREF bgColor = _terminal->GetBackgroundColor(_terminal->GetDefaultBrushColors());
|
||||
const COLORREF bgColor = _terminal->GetAttributeColors(_terminal->GetDefaultBrushColors()).second;
|
||||
|
||||
std::string HTMLToPlaceOnClip = TextBuffer::GenHTML(rows, iFontHeightPoints, fontData.GetFaceName(), bgColor);
|
||||
_CopyToSystemClipboard(HTMLToPlaceOnClip, L"HTML Format");
|
||||
|
||||
@@ -34,8 +34,8 @@ __declspec(dllexport) bool _stdcall TerminalIsSelectionActive(void* terminal);
|
||||
__declspec(dllexport) void _stdcall DestroyTerminal(void* terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
|
||||
__declspec(dllexport) void _stdcall TerminalRegisterWriteCallback(void* terminal, const void __stdcall callback(wchar_t*));
|
||||
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
|
||||
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD flags, WORD scanCode);
|
||||
__declspec(dllexport) void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
|
||||
__declspec(dllexport) void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
|
||||
__declspec(dllexport) void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
__declspec(dllexport) void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
__declspec(dllexport) void _stdcall TerminalSetFocus(void* terminal);
|
||||
@@ -51,10 +51,9 @@ public:
|
||||
HwndTerminal(HwndTerminal&&) = default;
|
||||
HwndTerminal& operator=(const HwndTerminal&) = default;
|
||||
HwndTerminal& operator=(HwndTerminal&&) = default;
|
||||
~HwndTerminal();
|
||||
~HwndTerminal() = default;
|
||||
|
||||
HRESULT Initialize();
|
||||
void Teardown() noexcept;
|
||||
void SendOutput(std::wstring_view data);
|
||||
HRESULT Refresh(const SIZE windowSize, _Out_ COORD* dimensions);
|
||||
void RegisterScrollCallback(std::function<void(int, int, int)> callback);
|
||||
@@ -93,8 +92,8 @@ private:
|
||||
friend void _stdcall TerminalClearSelection(void* terminal);
|
||||
friend const wchar_t* _stdcall TerminalGetSelection(void* terminal);
|
||||
friend bool _stdcall TerminalIsSelectionActive(void* terminal);
|
||||
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, WORD flags, bool keyDown);
|
||||
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode, WORD flags);
|
||||
friend void _stdcall TerminalSendKeyEvent(void* terminal, WORD vkey, WORD scanCode, bool keyDown);
|
||||
friend void _stdcall TerminalSendCharEvent(void* terminal, wchar_t ch, WORD scanCode);
|
||||
friend void _stdcall TerminalSetTheme(void* terminal, TerminalTheme theme, LPCWSTR fontFamily, short fontSize, int newDpi);
|
||||
friend void _stdcall TerminalBlinkCursor(void* terminal);
|
||||
friend void _stdcall TerminalSetCursorVisible(void* terminal, const bool visible);
|
||||
@@ -113,13 +112,11 @@ private:
|
||||
HRESULT _MoveSelection(LPARAM lParam) noexcept;
|
||||
IRawElementProviderSimple* _GetUiaProvider() noexcept;
|
||||
|
||||
void _ClearSelection() noexcept;
|
||||
|
||||
bool _CanSendVTMouseInput() const noexcept;
|
||||
bool _SendMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) noexcept;
|
||||
|
||||
void _SendKeyEvent(WORD vkey, WORD scanCode, WORD flags, bool keyDown) noexcept;
|
||||
void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept;
|
||||
void _SendKeyEvent(WORD vkey, WORD scanCode, bool keyDown) noexcept;
|
||||
void _SendCharEvent(wchar_t ch, WORD scanCode) noexcept;
|
||||
|
||||
// Inherited via IControlAccessibilityInfo
|
||||
COORD GetFontSize() const override;
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
#include "ActionArgs.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include "ActionAndArgs.g.cpp"
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
// This key is reserved to remove a keybinding, instead of mapping it to an action.
|
||||
static constexpr std::string_view UnboundKey{ "unbound" };
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::string_view CopyTextKey{ "copy" };
|
||||
static constexpr std::string_view PasteTextKey{ "paste" };
|
||||
@@ -32,7 +28,19 @@ static constexpr std::string_view SplitPaneKey{ "splitPane" };
|
||||
static constexpr std::string_view ResizePaneKey{ "resizePane" };
|
||||
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
|
||||
static constexpr std::string_view FindKey{ "find" };
|
||||
static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" };
|
||||
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
|
||||
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
|
||||
static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" };
|
||||
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
|
||||
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
|
||||
static constexpr std::string_view RenameTabKey{ "renameTab" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
|
||||
static constexpr std::string_view ActionKey{ "action" };
|
||||
|
||||
// This key is reserved to remove a keybinding, instead of mapping it to an action.
|
||||
static constexpr std::string_view UnboundKey{ "unbound" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
@@ -44,7 +52,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// the actual strings being pointed to. However, since both these strings and
|
||||
// the map are all const for the lifetime of the app, we have nothing to worry
|
||||
// about here.
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionNamesMap{
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
|
||||
{ CopyTextKey, ShortcutAction::CopyText },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
@@ -66,10 +74,17 @@ namespace winrt::TerminalApp::implementation
|
||||
{ ResizePaneKey, ShortcutAction::ResizePane },
|
||||
{ MoveFocusKey, ShortcutAction::MoveFocus },
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
{ ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect },
|
||||
{ ToggleFocusModeKey, ShortcutAction::ToggleFocusMode },
|
||||
{ ToggleFullscreenKey, ShortcutAction::ToggleFullscreen },
|
||||
{ ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop },
|
||||
{ SplitPaneKey, ShortcutAction::SplitPane },
|
||||
{ SetTabColorKey, ShortcutAction::SetTabColor },
|
||||
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
|
||||
{ UnboundKey, ShortcutAction::Invalid },
|
||||
{ FindKey, ShortcutAction::Find }
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
{ RenameTabKey, ShortcutAction::RenameTab },
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
@@ -97,6 +112,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
{ ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson },
|
||||
|
||||
{ ShortcutAction::Invalid, nullptr },
|
||||
};
|
||||
|
||||
@@ -111,8 +130,8 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Try matching the command to one we have. If we can't find the
|
||||
// action name in our list of names, let's just unbind that key.
|
||||
const auto found = ActionAndArgs::ActionNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
const auto found = ActionAndArgs::ActionKeyNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionKeyNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -209,4 +228,58 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring ActionAndArgs::GenerateName() const
|
||||
{
|
||||
// Use a magic static to initialize this map, because we won't be able
|
||||
// to load the resources at _init_, only at runtime.
|
||||
static const auto GeneratedActionNames = []() {
|
||||
return std::unordered_map<ShortcutAction, winrt::hstring>{
|
||||
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
|
||||
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
|
||||
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
|
||||
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
|
||||
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
|
||||
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
|
||||
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
|
||||
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
|
||||
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
|
||||
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
|
||||
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
|
||||
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
|
||||
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
{ ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") },
|
||||
{ ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") },
|
||||
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
|
||||
{ ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") },
|
||||
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
|
||||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
|
||||
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
|
||||
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
|
||||
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
if (_Args)
|
||||
{
|
||||
auto nameFromArgs = _Args.GenerateName();
|
||||
if (!nameFromArgs.empty())
|
||||
{
|
||||
return nameFromArgs;
|
||||
}
|
||||
}
|
||||
|
||||
const auto found = GeneratedActionNames.find(_Action);
|
||||
return found != GeneratedActionNames.end() ? found->second : L"";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,11 +7,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
|
||||
{
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionNamesMap;
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionKeyNamesMap;
|
||||
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
|
||||
ActionAndArgs() = default;
|
||||
hstring GenerateName() const;
|
||||
|
||||
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
|
||||
GETSET_PROPERTY(IActionArgs, Args, nullptr);
|
||||
};
|
||||
|
||||
@@ -15,3 +15,247 @@
|
||||
#include "AdjustFontSizeArgs.g.cpp"
|
||||
#include "SplitPaneArgs.g.cpp"
|
||||
#include "OpenSettingsArgs.g.cpp"
|
||||
#include "SetTabColorArgs.g.cpp"
|
||||
#include "RenameTabArgs.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
winrt::hstring NewTerminalArgs::GenerateName() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
if (!_Profile.empty())
|
||||
{
|
||||
ss << fmt::format(L"profile: {}, ", _Profile);
|
||||
}
|
||||
else if (_ProfileIndex)
|
||||
{
|
||||
ss << fmt::format(L"profile index: {}, ", _ProfileIndex.Value());
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
ss << fmt::format(L"commandline: {}, ", _Commandline);
|
||||
}
|
||||
|
||||
if (!_StartingDirectory.empty())
|
||||
{
|
||||
ss << fmt::format(L"directory: {}, ", _StartingDirectory);
|
||||
}
|
||||
|
||||
if (!_TabTitle.empty())
|
||||
{
|
||||
ss << fmt::format(L"title: {}, ", _TabTitle);
|
||||
}
|
||||
auto s = ss.str();
|
||||
if (s.empty())
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring CopyTextArgs::GenerateName() const
|
||||
{
|
||||
if (_SingleLine)
|
||||
{
|
||||
return RS_(L"CopyTextAsSingleLineCommandKey");
|
||||
}
|
||||
return RS_(L"CopyTextCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring NewTabArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (newTerminalArgsStr.empty())
|
||||
{
|
||||
return RS_(L"NewTabCommandKey");
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, {}", RS_(L"NewTabCommandKey"), newTerminalArgsStr)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring SwitchToTabArgs::GenerateName() const
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, index:{}", RS_(L"SwitchToTabCommandKey"), _TabIndex)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring ResizePaneArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"ResizePaneWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring MoveFocusArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"MoveFocusWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring AdjustFontSizeArgs::GenerateName() const
|
||||
{
|
||||
// If the amount is just 1 (or -1), we'll just return "Increase font
|
||||
// size" (or "Decrease font size"). If the amount delta has a greater
|
||||
// absolute value, we'll include it like"
|
||||
// * Decrease font size, amount: {delta}"
|
||||
if (_Delta < 0)
|
||||
{
|
||||
return _Delta == -1 ? RS_(L"DecreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"DecreaseFontSizeWithAmountCommandKey")),
|
||||
-_Delta)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return _Delta == 1 ? RS_(L"IncreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"IncreaseFontSizeWithAmountCommandKey")),
|
||||
_Delta)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SplitPaneArgs::GenerateName() const
|
||||
{
|
||||
// The string will be similar to the following:
|
||||
// * "Duplicate pane[, split: <direction>][, new terminal arguments...]"
|
||||
// * "Split pane[, split: <direction>][, new terminal arguments...]"
|
||||
//
|
||||
// Direction will only be added to the string if the split direction is
|
||||
// not "auto".
|
||||
// If this is a "duplicate pane" action, then the new terminal arguments
|
||||
// will be omitted (as they're unused)
|
||||
|
||||
std::wstringstream ss;
|
||||
if (_SplitMode == SplitType::Duplicate)
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"DuplicatePaneCommandKey"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"SplitPaneCommandKey"));
|
||||
}
|
||||
ss << L", ";
|
||||
|
||||
// This text is intentionally _not_ localized, to attempt to mirror the
|
||||
// exact syntax that the property would have in JSON.
|
||||
switch (_SplitStyle)
|
||||
{
|
||||
case SplitState::Vertical:
|
||||
ss << L"split: vertical, ";
|
||||
break;
|
||||
case SplitState::Horizontal:
|
||||
ss << L"split: horizontal, ";
|
||||
break;
|
||||
}
|
||||
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (_SplitMode != SplitType::Duplicate && !newTerminalArgsStr.empty())
|
||||
{
|
||||
ss << newTerminalArgsStr.c_str();
|
||||
ss << L", ";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
auto s = ss.str();
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring OpenSettingsArgs::GenerateName() const
|
||||
{
|
||||
switch (_Target)
|
||||
{
|
||||
case SettingsTarget::DefaultsFile:
|
||||
return RS_(L"OpenDefaultSettingsCommandKey");
|
||||
case SettingsTarget::AllFiles:
|
||||
return RS_(L"OpenBothSettingsFilesCommandKey");
|
||||
default:
|
||||
return RS_(L"OpenSettingsCommandKey");
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SetTabColorArgs::GenerateName() const
|
||||
{
|
||||
// "Set tab color to #RRGGBB"
|
||||
// "Reset tab color"
|
||||
if (_TabColor)
|
||||
{
|
||||
til::color c{ _TabColor.Value() };
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"SetTabColorCommandKey")),
|
||||
c.ToHexString(true))
|
||||
};
|
||||
}
|
||||
|
||||
return RS_(L"ResetTabColorCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring RenameTabArgs::GenerateName() const
|
||||
{
|
||||
// "Rename tab to \"{_Title}\""
|
||||
// "Reset tab title"
|
||||
if (!_Title.empty())
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"RenameTabCommandKey")),
|
||||
_Title.c_str())
|
||||
};
|
||||
}
|
||||
return RS_(L"ResetTabNameCommandKey");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
#include "AdjustFontSizeArgs.g.h"
|
||||
#include "SplitPaneArgs.g.h"
|
||||
#include "OpenSettingsArgs.g.h"
|
||||
#include "SetTabColorArgs.g.h"
|
||||
#include "RenameTabArgs.g.h"
|
||||
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
#include "TerminalWarnings.h"
|
||||
|
||||
// Notes on defining ActionArgs and ActionEventArgs:
|
||||
@@ -56,6 +59,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const winrt::TerminalApp::NewTerminalArgs& other)
|
||||
{
|
||||
return other.Commandline() == _Commandline &&
|
||||
@@ -100,6 +105,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view SingleLineKey{ "singleLine" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<CopyTextArgs>();
|
||||
@@ -127,6 +134,8 @@ namespace winrt::TerminalApp::implementation
|
||||
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<NewTabArgs>();
|
||||
@@ -153,6 +162,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view TabIndexKey{ "index" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SwitchToTabArgs>();
|
||||
@@ -217,6 +228,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ResizePaneArgs>();
|
||||
@@ -253,6 +266,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<MoveFocusArgs>();
|
||||
@@ -289,6 +304,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view AdjustFontSizeDelta{ "delta" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<AdjustFontSizeArgs>();
|
||||
@@ -355,13 +372,16 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view SplitModeKey{ "splitMode" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SplitPaneArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SplitStyle == _SplitStyle &&
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs &&
|
||||
(otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) :
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs) &&
|
||||
otherAsUs->_SplitMode == _SplitMode;
|
||||
}
|
||||
return false;
|
||||
@@ -421,6 +441,8 @@ namespace winrt::TerminalApp::implementation
|
||||
static constexpr std::string_view TargetKey{ "target" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<OpenSettingsArgs>();
|
||||
@@ -441,6 +463,74 @@ namespace winrt::TerminalApp::implementation
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
struct SetTabColorArgs : public SetTabColorArgsT<SetTabColorArgs>
|
||||
{
|
||||
SetTabColorArgs() = default;
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
|
||||
|
||||
static constexpr std::string_view ColorKey{ "color" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SetTabColorArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_TabColor == _TabColor;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<SetTabColorArgs>();
|
||||
std::optional<til::color> temp;
|
||||
try
|
||||
{
|
||||
::TerminalApp::JsonUtils::GetOptionalColor(json, ColorKey, temp);
|
||||
if (temp.has_value())
|
||||
{
|
||||
args->_TabColor = static_cast<uint32_t>(temp.value());
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
|
||||
struct RenameTabArgs : public RenameTabArgsT<RenameTabArgs>
|
||||
{
|
||||
RenameTabArgs() = default;
|
||||
GETSET_PROPERTY(winrt::hstring, Title, L"");
|
||||
|
||||
static constexpr std::string_view TitleKey{ "title" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<RenameTabArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_Title == _Title;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
static FromJsonResult FromJson(const Json::Value& json)
|
||||
{
|
||||
// LOAD BEARING: Not using make_self here _will_ break you in the future!
|
||||
auto args = winrt::make_self<RenameTabArgs>();
|
||||
if (auto title{ json[JsonKey(TitleKey)] })
|
||||
{
|
||||
args->_Title = winrt::to_hstring(title.asString());
|
||||
}
|
||||
return { *args, {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace TerminalApp
|
||||
interface IActionArgs
|
||||
{
|
||||
Boolean Equals(IActionArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
interface IActionEventArgs
|
||||
@@ -53,7 +54,9 @@ namespace TerminalApp
|
||||
// ProfileIndex can be null (for "use the default"), so this needs to be
|
||||
// a IReference, so it's nullable
|
||||
Windows.Foundation.IReference<Int32> ProfileIndex { get; };
|
||||
|
||||
Boolean Equals(NewTerminalArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
@@ -102,4 +105,14 @@ namespace TerminalApp
|
||||
{
|
||||
SettingsTarget Target { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SetTabColorArgs : IActionArgs
|
||||
{
|
||||
Windows.Foundation.IReference<UInt32> TabColor { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass RenameTabArgs : IActionArgs
|
||||
{
|
||||
String Title { get; };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
Initialize();
|
||||
|
||||
// Disable XAML's automatic backplating of text when in High Contrast
|
||||
// mode: we want full control of and responsibility for the foreground
|
||||
// and background colors that we draw in XAML.
|
||||
HighContrastAdjustment(::winrt::Windows::UI::Xaml::ApplicationHighContrastAdjustment::None);
|
||||
}
|
||||
|
||||
AppLogic App::Logic()
|
||||
|
||||
@@ -231,10 +231,107 @@ namespace winrt::TerminalApp::implementation
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleRetroEffect(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
const auto termControl = _GetActiveControl();
|
||||
termControl.ToggleRetroEffect();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleFocusMode(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
ToggleFocusMode();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleFullscreen(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
ToggleFullscreen();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleAlwaysOnTop(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
ToggleAlwaysOnTop();
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
// TODO GH#6677: When we add support for commandline mode, first set the
|
||||
// mode that the command palette should be in, before making it visible.
|
||||
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
|
||||
Visibility::Collapsed :
|
||||
Visibility::Visible);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
std::optional<til::color> tabColor;
|
||||
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::SetTabColorArgs>())
|
||||
{
|
||||
if (realArgs.TabColor() != nullptr)
|
||||
{
|
||||
tabColor = realArgs.TabColor().Value();
|
||||
}
|
||||
}
|
||||
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
if (tabColor.has_value())
|
||||
{
|
||||
activeTab->SetTabColor(tabColor.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabColor();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
activeTab->ActivateColorPicker();
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleRenameTab(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
std::optional<winrt::hstring> title;
|
||||
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::RenameTabArgs>())
|
||||
{
|
||||
title = realArgs.Title();
|
||||
}
|
||||
|
||||
auto activeTab = _GetFocusedTab();
|
||||
if (activeTab)
|
||||
{
|
||||
if (title.has_value())
|
||||
{
|
||||
activeTab->SetTabText(title.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabText();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ void AppCommandlineArgs::_buildParser()
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `new-tab` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `nt` subcommand, which is just a shortened version of `new-tab`
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
@@ -202,27 +203,35 @@ void AppCommandlineArgs::_buildParser()
|
||||
void AppCommandlineArgs::_buildNewTabParser()
|
||||
{
|
||||
_newTabCommand.subcommand = _app.add_subcommand("new-tab", RS_A(L"CmdNewTabDesc"));
|
||||
_addNewTerminalArgs(_newTabCommand);
|
||||
_newTabShort.subcommand = _app.add_subcommand("nt", RS_A(L"CmdNTDesc"));
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
_newTabCommand.subcommand->callback([&, this]() {
|
||||
// Build the NewTab action from the values we've parsed on the commandline.
|
||||
auto newTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
newTabAction->Action(ShortcutAction::NewTab);
|
||||
auto args = winrt::make_self<implementation::NewTabArgs>();
|
||||
// _getNewTerminalArgs MUST be called before parsing any other options,
|
||||
// as it might clear those options while finding the commandline
|
||||
args->TerminalArgs(_getNewTerminalArgs(_newTabCommand));
|
||||
newTabAction->Args(*args);
|
||||
_startupActions.push_back(*newTabAction);
|
||||
});
|
||||
auto setupSubcommand = [this](auto& subcommand) {
|
||||
_addNewTerminalArgs(subcommand);
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand.subcommand->callback([&, this]() {
|
||||
// Build the NewTab action from the values we've parsed on the commandline.
|
||||
auto newTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
newTabAction->Action(ShortcutAction::NewTab);
|
||||
auto args = winrt::make_self<implementation::NewTabArgs>();
|
||||
// _getNewTerminalArgs MUST be called before parsing any other options,
|
||||
// as it might clear those options while finding the commandline
|
||||
args->TerminalArgs(_getNewTerminalArgs(subcommand));
|
||||
newTabAction->Args(*args);
|
||||
_startupActions.push_back(*newTabAction);
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_newTabCommand);
|
||||
setupSubcommand(_newTabShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `split-pane` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `sp` subcommand, which is just a shortened version of `split-pane`
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
@@ -230,49 +239,57 @@ void AppCommandlineArgs::_buildNewTabParser()
|
||||
void AppCommandlineArgs::_buildSplitPaneParser()
|
||||
{
|
||||
_newPaneCommand.subcommand = _app.add_subcommand("split-pane", RS_A(L"CmdSplitPaneDesc"));
|
||||
_addNewTerminalArgs(_newPaneCommand);
|
||||
_horizontalOption = _newPaneCommand.subcommand->add_flag("-H,--horizontal",
|
||||
_splitHorizontal,
|
||||
RS_A(L"CmdSplitPaneHorizontalArgDesc"));
|
||||
_verticalOption = _newPaneCommand.subcommand->add_flag("-V,--vertical",
|
||||
_splitVertical,
|
||||
RS_A(L"CmdSplitPaneVerticalArgDesc"));
|
||||
_verticalOption->excludes(_horizontalOption);
|
||||
_newPaneShort.subcommand = _app.add_subcommand("sp", RS_A(L"CmdSPDesc"));
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
_newPaneCommand.subcommand->callback([&, this]() {
|
||||
// Build the SplitPane action from the values we've parsed on the commandline.
|
||||
auto splitPaneActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
|
||||
splitPaneActionAndArgs->Action(ShortcutAction::SplitPane);
|
||||
auto args = winrt::make_self<implementation::SplitPaneArgs>();
|
||||
// _getNewTerminalArgs MUST be called before parsing any other options,
|
||||
// as it might clear those options while finding the commandline
|
||||
args->TerminalArgs(_getNewTerminalArgs(_newPaneCommand));
|
||||
args->SplitStyle(SplitState::Automatic);
|
||||
// Make sure to use the `Option`s here to check if they were set -
|
||||
// _getNewTerminalArgs might reset them while parsing a commandline
|
||||
if ((*_horizontalOption || *_verticalOption))
|
||||
{
|
||||
if (_splitHorizontal)
|
||||
{
|
||||
args->SplitStyle(SplitState::Horizontal);
|
||||
}
|
||||
else if (_splitVertical)
|
||||
{
|
||||
args->SplitStyle(SplitState::Vertical);
|
||||
}
|
||||
}
|
||||
auto setupSubcommand = [this](auto& subcommand) {
|
||||
_addNewTerminalArgs(subcommand);
|
||||
subcommand._horizontalOption = subcommand.subcommand->add_flag("-H,--horizontal",
|
||||
_splitHorizontal,
|
||||
RS_A(L"CmdSplitPaneHorizontalArgDesc"));
|
||||
subcommand._verticalOption = subcommand.subcommand->add_flag("-V,--vertical",
|
||||
_splitVertical,
|
||||
RS_A(L"CmdSplitPaneVerticalArgDesc"));
|
||||
subcommand._verticalOption->excludes(subcommand._horizontalOption);
|
||||
|
||||
splitPaneActionAndArgs->Args(*args);
|
||||
_startupActions.push_back(*splitPaneActionAndArgs);
|
||||
});
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand.subcommand->callback([&, this]() {
|
||||
// Build the SplitPane action from the values we've parsed on the commandline.
|
||||
auto splitPaneActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
|
||||
splitPaneActionAndArgs->Action(ShortcutAction::SplitPane);
|
||||
auto args = winrt::make_self<implementation::SplitPaneArgs>();
|
||||
// _getNewTerminalArgs MUST be called before parsing any other options,
|
||||
// as it might clear those options while finding the commandline
|
||||
args->TerminalArgs(_getNewTerminalArgs(subcommand));
|
||||
args->SplitStyle(SplitState::Automatic);
|
||||
// Make sure to use the `Option`s here to check if they were set -
|
||||
// _getNewTerminalArgs might reset them while parsing a commandline
|
||||
if ((*subcommand._horizontalOption || *subcommand._verticalOption))
|
||||
{
|
||||
if (_splitHorizontal)
|
||||
{
|
||||
args->SplitStyle(SplitState::Horizontal);
|
||||
}
|
||||
else if (_splitVertical)
|
||||
{
|
||||
args->SplitStyle(SplitState::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
splitPaneActionAndArgs->Args(*args);
|
||||
_startupActions.push_back(*splitPaneActionAndArgs);
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_newPaneCommand);
|
||||
setupSubcommand(_newPaneShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `new-tab` subcommand and related options to the commandline parser.
|
||||
// - Adds the `focus-tab` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `ft` subcommand, which is just a shortened version of `focus-tab`
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
@@ -280,39 +297,48 @@ void AppCommandlineArgs::_buildSplitPaneParser()
|
||||
void AppCommandlineArgs::_buildFocusTabParser()
|
||||
{
|
||||
_focusTabCommand = _app.add_subcommand("focus-tab", RS_A(L"CmdFocusTabDesc"));
|
||||
auto* indexOpt = _focusTabCommand->add_option("-t,--target", _focusTabIndex, RS_A(L"CmdFocusTabTargetArgDesc"));
|
||||
auto* nextOpt = _focusTabCommand->add_flag("-n,--next",
|
||||
_focusNextTab,
|
||||
RS_A(L"CmdFocusTabNextArgDesc"));
|
||||
auto* prevOpt = _focusTabCommand->add_flag("-p,--previous",
|
||||
_focusPrevTab,
|
||||
RS_A(L"CmdFocusTabPrevArgDesc"));
|
||||
nextOpt->excludes(prevOpt);
|
||||
indexOpt->excludes(prevOpt);
|
||||
indexOpt->excludes(nextOpt);
|
||||
_focusTabShort = _app.add_subcommand("ft", RS_A(L"CmdFTDesc"));
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
_focusTabCommand->callback([&, this]() {
|
||||
// Build the action from the values we've parsed on the commandline.
|
||||
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
auto setupSubcommand = [this](auto* subcommand) {
|
||||
auto* indexOpt = subcommand->add_option("-t,--target",
|
||||
_focusTabIndex,
|
||||
RS_A(L"CmdFocusTabTargetArgDesc"));
|
||||
auto* nextOpt = subcommand->add_flag("-n,--next",
|
||||
_focusNextTab,
|
||||
RS_A(L"CmdFocusTabNextArgDesc"));
|
||||
auto* prevOpt = subcommand->add_flag("-p,--previous",
|
||||
_focusPrevTab,
|
||||
RS_A(L"CmdFocusTabPrevArgDesc"));
|
||||
nextOpt->excludes(prevOpt);
|
||||
indexOpt->excludes(prevOpt);
|
||||
indexOpt->excludes(nextOpt);
|
||||
|
||||
if (_focusTabIndex >= 0)
|
||||
{
|
||||
focusTabAction->Action(ShortcutAction::SwitchToTab);
|
||||
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(_focusTabIndex);
|
||||
focusTabAction->Args(*args);
|
||||
_startupActions.push_back(*focusTabAction);
|
||||
}
|
||||
else if (_focusNextTab || _focusPrevTab)
|
||||
{
|
||||
focusTabAction->Action(_focusNextTab ? ShortcutAction::NextTab : ShortcutAction::PrevTab);
|
||||
_startupActions.push_back(*focusTabAction);
|
||||
}
|
||||
});
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand->callback([&, this]() {
|
||||
// Build the action from the values we've parsed on the commandline.
|
||||
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
|
||||
|
||||
if (_focusTabIndex >= 0)
|
||||
{
|
||||
focusTabAction->Action(ShortcutAction::SwitchToTab);
|
||||
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
|
||||
args->TabIndex(_focusTabIndex);
|
||||
focusTabAction->Args(*args);
|
||||
_startupActions.push_back(*focusTabAction);
|
||||
}
|
||||
else if (_focusNextTab || _focusPrevTab)
|
||||
{
|
||||
focusTabAction->Action(_focusNextTab ? ShortcutAction::NextTab : ShortcutAction::PrevTab);
|
||||
_startupActions.push_back(*focusTabAction);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_focusTabCommand);
|
||||
setupSubcommand(_focusTabShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -409,7 +435,10 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT
|
||||
bool AppCommandlineArgs::_noCommandsProvided()
|
||||
{
|
||||
return !(*_newTabCommand.subcommand ||
|
||||
*_newTabShort.subcommand ||
|
||||
*_focusTabCommand ||
|
||||
*_focusTabShort ||
|
||||
*_newPaneShort.subcommand ||
|
||||
*_newPaneCommand.subcommand);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,15 +56,21 @@ private:
|
||||
CLI::Option* titleOption;
|
||||
};
|
||||
|
||||
struct NewPaneSubcommand : public NewTerminalSubcommand
|
||||
{
|
||||
CLI::Option* _horizontalOption;
|
||||
CLI::Option* _verticalOption;
|
||||
};
|
||||
|
||||
// --- Subcommands ---
|
||||
NewTerminalSubcommand _newTabCommand;
|
||||
NewTerminalSubcommand _newPaneCommand;
|
||||
NewTerminalSubcommand _newTabShort;
|
||||
NewPaneSubcommand _newPaneCommand;
|
||||
NewPaneSubcommand _newPaneShort;
|
||||
CLI::App* _focusTabCommand;
|
||||
CLI::App* _focusTabShort;
|
||||
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
|
||||
|
||||
CLI::Option* _horizontalOption;
|
||||
CLI::Option* _verticalOption;
|
||||
|
||||
std::string _profileName;
|
||||
std::string _startingDirectory;
|
||||
std::string _startingTitle;
|
||||
|
||||
@@ -55,8 +55,11 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
for (auto& kv : _keyShortcuts)
|
||||
{
|
||||
if (kv.second.Action() == actionAndArgs.Action() &&
|
||||
kv.second.Args().Equals(actionAndArgs.Args()))
|
||||
const auto action = kv.second.Action();
|
||||
const auto args = kv.second.Args();
|
||||
const auto actionMatched = action == actionAndArgs.Action();
|
||||
const auto argsMatched = args ? args.Equals(actionAndArgs.Args()) : args == actionAndArgs.Args();
|
||||
if (actionMatched && argsMatched)
|
||||
{
|
||||
return kv.first;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
||||
|
||||
// Iterate over all the possible actions in the names list, and see if
|
||||
// it has a binding.
|
||||
for (auto& actionName : ActionAndArgs::ActionNamesMap)
|
||||
for (auto& actionName : ActionAndArgs::ActionKeyNamesMap)
|
||||
{
|
||||
const auto searchedForName = actionName.first;
|
||||
const auto searchedForAction = actionName.second;
|
||||
@@ -85,7 +85,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
||||
// - Deserialize an AppKeyBindings from the key mappings that are in the array
|
||||
// `json`. The json array should contain an array of objects with both a
|
||||
// `command` string and a `keys` array, where `command` is one of the names
|
||||
// listed in `ActionAndArgs::ActionNamesMap`, and `keys` is an array of
|
||||
// listed in `ActionAndArgs::ActionKeyNamesMap`, and `keys` is an array of
|
||||
// keypresses. Currently, the array should contain a single string, which can
|
||||
// be deserialized into a KeyChord.
|
||||
// - Applies the deserialized keybindings to the provided `bindings` object. If
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using namespace winrt::Windows::ApplicationModel;
|
||||
using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
@@ -231,7 +232,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// this as a MTA, before the app is Create()'d
|
||||
WINRT_ASSERT(_loadedInitialSettings);
|
||||
|
||||
_root->ShowDialog({ this, &AppLogic::_ShowDialog });
|
||||
_root->DialogPresenter(*this);
|
||||
|
||||
// In UWP mode, we cannot handle taking over the title bar for tabs,
|
||||
// so this setting is overridden to false no matter what the preference is.
|
||||
@@ -277,9 +278,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens.
|
||||
// Arguments:
|
||||
// sender: unused
|
||||
// dialog: the dialog object that is going to show up
|
||||
fire_and_forget AppLogic::_ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog)
|
||||
// - dialog: the dialog object that is going to show up
|
||||
// Return value:
|
||||
// - an IAsyncOperation with the dialog result
|
||||
winrt::Windows::Foundation::IAsyncOperation<ContentDialogResult> AppLogic::ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog)
|
||||
{
|
||||
// DON'T release this lock in a wil::scope_exit. The scope_exit will get
|
||||
// called when we await, which is not what we want.
|
||||
@@ -287,7 +289,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (!lock)
|
||||
{
|
||||
// Another dialog is visible.
|
||||
return;
|
||||
co_return ContentDialogResult::None;
|
||||
}
|
||||
|
||||
// IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs.
|
||||
@@ -321,7 +323,7 @@ namespace winrt::TerminalApp::implementation
|
||||
auto loadedRevoker{ dialog.Loaded(winrt::auto_revoke, themingLambda) }; // if it's not yet in the tree
|
||||
|
||||
// Display the dialog.
|
||||
co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
|
||||
co_return co_await dialog.ShowAsync(Controls::ContentDialogPlacement::Popup);
|
||||
|
||||
// After the dialog is dismissed, the dialog lock (held by `lock`) will
|
||||
// be released so another can be shown
|
||||
@@ -333,7 +335,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// as the title and first content of the dialog, then also displays a
|
||||
// message for whatever exception was found while validating the settings.
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
// when this is called, nothing happens. See ShowDialog for details
|
||||
// Arguments:
|
||||
// - titleKey: The key to use to lookup the title text from our resources.
|
||||
// - contentKey: The key to use to lookup the content text from our resources.
|
||||
@@ -378,7 +380,7 @@ namespace winrt::TerminalApp::implementation
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(Controls::ContentDialogButton::Close);
|
||||
|
||||
_ShowDialog(nullptr, dialog);
|
||||
ShowDialog(dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -386,7 +388,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// settings. Displays messages for whatever warnings were found while
|
||||
// validating the settings.
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
// when this is called, nothing happens. See ShowDialog for details
|
||||
void AppLogic::_ShowLoadWarningsDialog()
|
||||
{
|
||||
auto title = RS_(L"SettingsValidateErrorTitle");
|
||||
@@ -437,7 +439,7 @@ namespace winrt::TerminalApp::implementation
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(Controls::ContentDialogButton::Close);
|
||||
|
||||
_ShowDialog(nullptr, dialog);
|
||||
ShowDialog(dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1119,6 +1121,21 @@ namespace winrt::TerminalApp::implementation
|
||||
return RS_(L"ApplicationVersionUnknown");
|
||||
}
|
||||
|
||||
bool AppLogic::FocusMode() const
|
||||
{
|
||||
return _root ? _root->FocusMode() : false;
|
||||
}
|
||||
|
||||
bool AppLogic::Fullscreen() const
|
||||
{
|
||||
return _root ? _root->Fullscreen() : false;
|
||||
}
|
||||
|
||||
bool AppLogic::AlwaysOnTop() const
|
||||
{
|
||||
return _root ? _root->AlwaysOnTop() : false;
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
||||
@@ -33,6 +33,9 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::hstring ApplicationDisplayName() const;
|
||||
winrt::hstring ApplicationVersion() const;
|
||||
bool FocusMode() const;
|
||||
bool Fullscreen() const;
|
||||
bool AlwaysOnTop() const;
|
||||
|
||||
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
|
||||
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
|
||||
@@ -49,6 +52,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(RequestedThemeChanged, _requestedThemeChangedHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
|
||||
|
||||
@@ -79,7 +84,6 @@ namespace winrt::TerminalApp::implementation
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
int _ParseArgs(winrt::array_view<const hstring>& args);
|
||||
|
||||
fire_and_forget _ShowDialog(const winrt::Windows::Foundation::IInspectable& sender, winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
|
||||
void _ShowLoadWarningsDialog();
|
||||
|
||||
@@ -103,7 +107,9 @@ namespace winrt::TerminalApp::implementation
|
||||
FORWARDED_TYPED_EVENT(SetTitleBarContent, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement, _root, SetTitleBarContent);
|
||||
FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged);
|
||||
FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed);
|
||||
FORWARDED_TYPED_EVENT(ToggleFullscreen, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs, _root, ToggleFullscreen);
|
||||
FORWARDED_TYPED_EVENT(FocusModeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FocusModeChanged);
|
||||
FORWARDED_TYPED_EVENT(FullscreenChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, FullscreenChanged);
|
||||
FORWARDED_TYPED_EVENT(AlwaysOnTopChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, _root, AlwaysOnTopChanged);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace TerminalApp
|
||||
FullscreenMode,
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass AppLogic : IDirectKeyListener
|
||||
[default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter
|
||||
{
|
||||
AppLogic();
|
||||
|
||||
@@ -41,6 +41,10 @@ namespace TerminalApp
|
||||
String ApplicationDisplayName { get; };
|
||||
String ApplicationVersion { get; };
|
||||
|
||||
Boolean FocusMode { get; };
|
||||
Boolean Fullscreen { get; };
|
||||
Boolean AlwaysOnTop { get; };
|
||||
|
||||
Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi);
|
||||
Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY);
|
||||
Windows.UI.Xaml.ElementTheme GetRequestedTheme();
|
||||
@@ -50,10 +54,16 @@ namespace TerminalApp
|
||||
void TitlebarClicked();
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
Windows.Foundation.IAsyncOperation<Windows.UI.Xaml.Controls.ContentDialogResult> ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog);
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.ElementTheme> RequestedThemeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> FocusModeChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> FullscreenChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> AlwaysOnTopChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ const Profile* CascadiaSettings::FindProfile(GUID profileGuid) const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - an iterable collection of all of our Profiles.
|
||||
std::basic_string_view<Profile> CascadiaSettings::GetProfiles() const noexcept
|
||||
gsl::span<const Profile> CascadiaSettings::GetProfiles() const noexcept
|
||||
{
|
||||
return { &_profiles[0], _profiles.size() };
|
||||
}
|
||||
@@ -226,12 +226,9 @@ void CascadiaSettings::_ValidateProfilesHaveGuid()
|
||||
void CascadiaSettings::_ResolveDefaultProfile()
|
||||
{
|
||||
const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
|
||||
if (unparsedDefaultProfile)
|
||||
{
|
||||
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(*unparsedDefaultProfile) };
|
||||
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
|
||||
GlobalSettings().DefaultProfile(defaultProfileGuid);
|
||||
}
|
||||
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
|
||||
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
|
||||
GlobalSettings().DefaultProfile(defaultProfileGuid);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -721,3 +718,30 @@ std::string CascadiaSettings::_ApplyFirstRunChangesToSettingsTemplate(std::strin
|
||||
|
||||
return finalSettings;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Lookup the color scheme for a given profile. If the profile doesn't exist,
|
||||
// or the scheme name listed in the profile doesn't correspond to a scheme,
|
||||
// this will return `nullptr`.
|
||||
// Arguments:
|
||||
// - profileGuid: the GUID of the profile to find the scheme for.
|
||||
// Return Value:
|
||||
// - a non-owning pointer to the scheme.
|
||||
const ColorScheme* CascadiaSettings::GetColorSchemeForProfile(const GUID profileGuid) const
|
||||
{
|
||||
auto* profile = FindProfile(profileGuid);
|
||||
if (!profile)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
auto schemeName = profile->GetSchemeName().has_value() ? profile->GetSchemeName().value() : L"\0";
|
||||
auto scheme = _globals.GetColorSchemes().find(schemeName);
|
||||
if (scheme != _globals.GetColorSchemes().end())
|
||||
{
|
||||
return &scheme->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
|
||||
GlobalAppSettings& GlobalSettings();
|
||||
|
||||
std::basic_string_view<Profile> GetProfiles() const noexcept;
|
||||
gsl::span<const Profile> GetProfiles() const noexcept;
|
||||
|
||||
winrt::TerminalApp::AppKeyBindings GetKeybindings() const noexcept;
|
||||
|
||||
@@ -70,6 +70,7 @@ public:
|
||||
static std::filesystem::path GetDefaultSettingsPath();
|
||||
|
||||
const Profile* FindProfile(GUID profileGuid) const noexcept;
|
||||
const ColorScheme* GetColorSchemeForProfile(const GUID profileGuid) const;
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings>& GetWarnings();
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
|
||||
// GH 3588, we need this below to know if the user chose something that wasn't our default.
|
||||
// Collect it up here in case it gets modified by any of the other layers between now and when
|
||||
// the user's preferences are loaded and layered.
|
||||
const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().DefaultProfile();
|
||||
const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().UnparsedDefaultProfile();
|
||||
|
||||
std::optional<std::string> fileData = _ReadUserSettings();
|
||||
const bool foundFile = fileData.has_value();
|
||||
@@ -141,11 +141,12 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadAll()
|
||||
// is a lot of computation we can skip if no one cares.
|
||||
if (TraceLoggingProviderEnabled(g_hTerminalAppProvider, 0, MICROSOFT_KEYWORD_MEASURES))
|
||||
{
|
||||
const auto hardcodedDefaultGuidAsGuid = Utils::GuidFromString(hardcodedDefaultGuid);
|
||||
const auto guid = resultPtr->GlobalSettings().DefaultProfile();
|
||||
|
||||
// Compare to the defaults.json one that we set on install.
|
||||
// If it's different, log what the user chose.
|
||||
if (hardcodedDefaultGuid != guid)
|
||||
if (hardcodedDefaultGuidAsGuid != guid)
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
@@ -228,7 +229,6 @@ std::unique_ptr<CascadiaSettings> CascadiaSettings::LoadDefaults()
|
||||
// them from a file (and the potential that could fail)
|
||||
resultPtr->_ParseJsonString(DefaultJson, true);
|
||||
resultPtr->LayerJson(resultPtr->_defaultSettings);
|
||||
resultPtr->_ResolveDefaultProfile();
|
||||
|
||||
return resultPtr;
|
||||
}
|
||||
|
||||
191
src/cascadia/TerminalApp/Command.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Command.h"
|
||||
#include "Command.g.cpp"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::TerminalApp;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconPathKey{ "iconPath" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view ArgsKey{ "args" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Function Description:
|
||||
// - attempt to get the name of this command from the provided json object.
|
||||
// * If the "name" property is a string, return that value.
|
||||
// * If the "name" property is an object, attempt to lookup the string
|
||||
// resource specified by the "key" property, to support localizable
|
||||
// command names.
|
||||
// Arguments:
|
||||
// - json: The Json::Value representing the command object we should get the name for.
|
||||
// Return Value:
|
||||
// - the empty string if we couldn't find a name, otherwise the command's name.
|
||||
static winrt::hstring _nameFromJson(const Json::Value& json)
|
||||
{
|
||||
if (const auto name{ json[JsonKey(NameKey)] })
|
||||
{
|
||||
if (name.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (const auto keyJson{ name[JsonKey("key")] })
|
||||
{
|
||||
// Make sure the key is present before we try
|
||||
// loading it. Otherwise we'll crash
|
||||
const auto resourceKey = GetWstringFromJson(keyJson);
|
||||
if (HasLibraryResourceWithName(resourceKey))
|
||||
{
|
||||
return GetLibraryResourceString(resourceKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else if (name.isString())
|
||||
{
|
||||
auto nameStr = name.asString();
|
||||
return winrt::to_hstring(nameStr);
|
||||
}
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the name for the command specified in `json`. If there is no "name"
|
||||
// property in the provided json object, then instead generate a name for
|
||||
// the provided ActionAndArgs.
|
||||
// Arguments:
|
||||
// - json: json for the command to generate a name for.
|
||||
// - actionAndArgs: An ActionAndArgs object to use to generate a name for,
|
||||
// if the json object doesn't contain a "name".
|
||||
// Return Value:
|
||||
// - The "name" from the json, or the generated name from ActionAndArgs::GenerateName
|
||||
static winrt::hstring _nameFromJsonOrAction(const Json::Value& json,
|
||||
winrt::com_ptr<ActionAndArgs> actionAndArgs)
|
||||
{
|
||||
auto manualName = _nameFromJson(json);
|
||||
if (!manualName.empty())
|
||||
{
|
||||
return manualName;
|
||||
}
|
||||
if (!actionAndArgs)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
return actionAndArgs->GenerateName();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize a Command from the `json` object. The json object should
|
||||
// contain a "name" and "action", and optionally an "icon".
|
||||
// * "name": string|object - the name of the command to display in the
|
||||
// command palette. If this is an object, look for the "key" property,
|
||||
// and try to load the string from our resources instead.
|
||||
// * "action": string|object - A ShortcutAction, either as a name or as an
|
||||
// ActionAndArgs serialization. See ActionAndArgs::FromJson for details.
|
||||
// If this is null, we'll remove this command from the list of commands.
|
||||
// Arguments:
|
||||
// - json: the Json::Value to deserialize into a Command
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - the newly constructed Command object.
|
||||
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
|
||||
// the binding quite right. Additionally, do we want it to be an image,
|
||||
// or a FontIcon? I've had difficulty binding either/or.
|
||||
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (result->_Name.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempt to parse all the json objects in `json` into new Command
|
||||
// objects, and add them to the map of commands.
|
||||
// - If any parsed command has
|
||||
// the same Name as an existing command in commands, the new one will
|
||||
// layer on top of the existing one.
|
||||
// Arguments:
|
||||
// - commands: a map of Name->Command which new commands should be layered upon.
|
||||
// - json: A Json::Value containing an array of serialized commands
|
||||
// Return Value:
|
||||
// - A vector containing any warnings detected while parsing
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json)
|
||||
{
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
|
||||
|
||||
for (const auto& value : json)
|
||||
{
|
||||
if (value.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = Command::FromJson(value, warnings);
|
||||
if (result)
|
||||
{
|
||||
// Override commands with the same name
|
||||
commands.insert_or_assign(result->Name(), *result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there wasn't a parsed command, then try to get the
|
||||
// name from the json blob. If that name currently
|
||||
// exists in our list of commands, we should remove it.
|
||||
const auto name = _nameFromJson(value);
|
||||
if (!name.empty())
|
||||
{
|
||||
commands.erase(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
}
|
||||
45
src/cascadia/TerminalApp/Command.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- Command.h
|
||||
|
||||
Abstract:
|
||||
- A command represents a single entry in the Command Palette. This is an object
|
||||
that has a user facing "name" to display to the user, and an associated action
|
||||
which can be dispatched.
|
||||
|
||||
- For more information, see GH#2046, #5400, #5674, and #6635
|
||||
|
||||
Author(s):
|
||||
- Mike Griese - June 2020
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "Command.g.h"
|
||||
#include "TerminalWarnings.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command() = default;
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Command);
|
||||
}
|
||||
16
src/cascadia/TerminalApp/Command.idl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Command();
|
||||
|
||||
String Name;
|
||||
ActionAndArgs Action;
|
||||
String KeyChordText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#include "pch.h"
|
||||
#include "CommandKeyChordVisibilityConverter.h"
|
||||
#include "CommandKeyChordVisibilityConverter.g.cpp"
|
||||
|
||||
using namespace winrt::Windows;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Method Description:
|
||||
// - Attempt to convert something into another type. For the
|
||||
// CommandKeyChordVisibilityConverter, we're gonna check if `value` is a
|
||||
// string, and try and convert it into a Visibility value. If the input
|
||||
// param wasn't a string, or was the empty string, we'll return
|
||||
// Visibility::Collapsed. Otherwise, we'll return Visible.
|
||||
|
||||
// Arguments:
|
||||
// - value: the input object to attempt to convert into a Visibility.
|
||||
// Return Value:
|
||||
// - Visible if the object was a string and wasn't the empty string.
|
||||
Foundation::IInspectable CommandKeyChordVisibilityConverter::Convert(Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
const auto& name = winrt::unbox_value_or<hstring>(value, L"");
|
||||
return winrt::box_value(name.empty() ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
// unused for one-way bindings
|
||||
Foundation::IInspectable CommandKeyChordVisibilityConverter::ConvertBack(Foundation::IInspectable const& /* value */,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommandKeyChordVisibilityConverter.g.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct CommandKeyChordVisibilityConverter : CommandKeyChordVisibilityConverterT<CommandKeyChordVisibilityConverter>
|
||||
{
|
||||
CommandKeyChordVisibilityConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
|
||||
Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CommandKeyChordVisibilityConverter);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
// See https://docs.microsoft.com/en-us/windows/uwp/data-binding/data-binding-quickstart
|
||||
|
||||
// We use the default attribute to declare IValueConverter as the default
|
||||
// interface. In the listing, CommandKeyChordVisibilityConverter has only a
|
||||
// constructor, and no methods, so no default interface is generated for it.
|
||||
// The default attribute is optimal if you won't be adding instance members
|
||||
// to CommandKeyChordVisibilityConverter, because no QueryInterface will be
|
||||
// required to call the IValueConverter methods
|
||||
runtimeclass CommandKeyChordVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
CommandKeyChordVisibilityConverter();
|
||||
};
|
||||
|
||||
}
|
||||
458
src/cascadia/TerminalApp/CommandPalette.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
|
||||
#include "CommandPalette.g.cpp"
|
||||
#include <winrt/Microsoft.Terminal.Settings.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
CommandPalette::CommandPalette()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
// Hook up the shadow on the command palette to the backdrop that
|
||||
// will actually show it. This needs to be done at runtime, and only
|
||||
// if the shadow actually exists. ThemeShadow isn't supported below
|
||||
// version 18362.
|
||||
CommandPaletteShadow().Receivers().Append(_shadowBackdrop());
|
||||
// "raise" the command palette up by 16 units, so it will cast a shadow.
|
||||
_backdrop().Translation({ 0, 0, 16 });
|
||||
}
|
||||
|
||||
// Whatever is hosting us will enable us by setting our visibility to
|
||||
// "Visible". When that happens, set focus to our search box.
|
||||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteOpened",
|
||||
TraceLoggingDescription("Event emitted when the Command Palette is opened"),
|
||||
TraceLoggingWideString(L"Action", "Mode", "which mode the palette was opened in"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Raise an event to return control to the Terminal.
|
||||
_dismissPalette();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the focus up or down the list of commands. If we're at the top,
|
||||
// we'll loop around to the bottom, and vice-versa.
|
||||
// Arguments:
|
||||
// - moveDown: if true, we're attempting to move to the next item in the
|
||||
// list. Otherwise, we're attempting to move to the previous.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_selectNextItem(const bool moveDown)
|
||||
{
|
||||
const auto selected = _filteredActionsView().SelectedIndex();
|
||||
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
||||
// Wraparound math. By adding numItems and then calculating modulo numItems,
|
||||
// we clamp the values to the range [0, numItems) while still supporting moving
|
||||
// upward from 0 to numItems - 1.
|
||||
const auto newIndex = ((numItems + selected + (moveDown ? 1 : -1)) % numItems);
|
||||
_filteredActionsView().SelectedIndex(newIndex);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process keystrokes in the input box. This is used for moving focus up
|
||||
// and down the list of commands in Action mode, and for executing
|
||||
// commands in both Action mode and Commandline mode.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
_selectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Down)
|
||||
{
|
||||
// Action Mode: Move focus to the previous item in the list.
|
||||
_selectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Enter)
|
||||
{
|
||||
// Action Mode: Dispatch the action of the selected command.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
_dispatchCommand(selectedItem.try_as<Command>());
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Escape)
|
||||
{
|
||||
// Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string.
|
||||
if (_searchBox().Text().empty())
|
||||
{
|
||||
_dismissPalette();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is triggered when someone clicks anywhere in the bounds of
|
||||
// the window that's _not_ the command palette UI. When that happens,
|
||||
// we'll want to dismiss the palette.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
|
||||
{
|
||||
_dismissPalette();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is only triggered when someone clicks in the space right
|
||||
// next to the text box in the command palette. We _don't_ want that click
|
||||
// to light dismiss the palette, so we'll mark it handled here.
|
||||
// Arguments:
|
||||
// - e: the PointerRoutedEventArgs that we want to mark as handled
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_backdropPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
|
||||
{
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is called when the user clicks on an individual item from
|
||||
// the list. We'll get the item that was clicked and dispatch the command
|
||||
// that the user clicked on.
|
||||
// Arguments:
|
||||
// - e: an ItemClickEventArgs who's ClickedItem() will be the command that was clicked on.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
|
||||
{
|
||||
_dispatchCommand(e.ClickedItem().try_as<TerminalApp::Command>());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper method for retrieving the action from a command the user
|
||||
// selected, and dispatching that command. Also fires a tracelogging event
|
||||
// indicating that the user successfully found the action they were
|
||||
// looking for.
|
||||
// Arguments:
|
||||
// - command: the Command to dispatch. This might be null.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_dispatchCommand(const TerminalApp::Command& command)
|
||||
{
|
||||
if (command)
|
||||
{
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_close();
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteDispatchedAction",
|
||||
TraceLoggingDescription("Event emitted when the user selects an action in the Command Palette"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper method for closing the command palette, when the user has _not_
|
||||
// selected an action. Also fires a tracelogging event indicating that the
|
||||
// user closed the palette without running a command.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_dismissPalette()
|
||||
{
|
||||
_close();
|
||||
|
||||
TraceLoggingWrite(
|
||||
g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider
|
||||
"CommandPaletteDismissed",
|
||||
TraceLoggingDescription("Event emitted when the user dismisses the Command Palette without selecting an action"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for when the text in the input box changes. In Action
|
||||
// Mode, we'll update the list of displayed commands, and select the first one.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
_updateFilteredActions();
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetActions(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
{
|
||||
_allActions = actions;
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs)
|
||||
{
|
||||
std::wstring_view leftName{ lhs.Name() };
|
||||
std::wstring_view rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
// This is a helper struct to aid in sorting Commands by a given weighting.
|
||||
struct WeightedCommand
|
||||
{
|
||||
TerminalApp::Command command;
|
||||
int weight;
|
||||
|
||||
bool operator<(const WeightedCommand& other) const
|
||||
{
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
if (weight == other.weight)
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
}
|
||||
return weight < other.weight;
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Update our list of filtered actions to reflect the current contents of
|
||||
// the input box. For more details on which commands will be displayed,
|
||||
// see `_getWeight`.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_updateFilteredActions()
|
||||
{
|
||||
_filteredActions.Clear();
|
||||
auto searchText = _searchBox().Text();
|
||||
const bool addAll = searchText.empty();
|
||||
|
||||
// If there's no filter text, then just add all the commands in order to the list.
|
||||
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
|
||||
// by the rest of the commands.
|
||||
if (addAll)
|
||||
{
|
||||
// Add all the commands, but make sure they're sorted alphabetically.
|
||||
std::vector<TerminalApp::Command> sortedCommands;
|
||||
sortedCommands.reserve(_allActions.Size());
|
||||
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
sortedCommands.push_back(action);
|
||||
}
|
||||
std::sort(sortedCommands.begin(),
|
||||
sortedCommands.end(),
|
||||
_compareCommandNames);
|
||||
|
||||
for (auto action : sortedCommands)
|
||||
{
|
||||
_filteredActions.Append(action);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, there was some filter text.
|
||||
// Show these actions in a weighted order.
|
||||
// - Matching the first character of a word, then the first char of a
|
||||
// subsequent word seems better than just "the order they appear in
|
||||
// the list".
|
||||
// - TODO GH#6647:"Recently used commands" ordering also seems valuable.
|
||||
// * This could be done by weighting the recently used commands
|
||||
// higher the more recently they were used, then weighting all
|
||||
// the unused commands as 1
|
||||
|
||||
// Use a priority queue to order commands so that "better" matches
|
||||
// appear first in the list. The ordering will be determined by the
|
||||
// match weight produced by _getWeight.
|
||||
std::priority_queue<WeightedCommand> heap;
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
|
||||
if (weight > 0)
|
||||
{
|
||||
WeightedCommand wc;
|
||||
wc.command = action;
|
||||
wc.weight = weight;
|
||||
heap.push(wc);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, all the commands in heap are matches. We've also
|
||||
// sorted commands with the same weight alphabetically.
|
||||
// Remove everything in-order from the queue, and add to the list of
|
||||
// filtered actions.
|
||||
while (!heap.empty())
|
||||
{
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
_filteredActions.Append(top.command);
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Calculates a "weighting" by which should be used to order a command
|
||||
// name relative to other names, given a specific search string.
|
||||
// Currently, this is based off of two factors:
|
||||
// * The weight is incremented once for each matched character of the
|
||||
// search text.
|
||||
// * If a matching character from the search text was found at the start
|
||||
// of a word in the name, then we increment the weight again.
|
||||
// * For example, for a search string "sp", we want "Split Pane" to
|
||||
// appear in the list before "Close Pane"
|
||||
// * Consecutive matches will be weighted higher than matches with
|
||||
// characters in between the search characters.
|
||||
// - This will return 0 if the command should not be shown. If all the
|
||||
// characters of search text appear in order in `name`, then this function
|
||||
// will return a positive number. There can be any number of characters
|
||||
// separating consecutive characters in searchText.
|
||||
// * For example:
|
||||
// "name": "New Tab"
|
||||
// "name": "Close Tab"
|
||||
// "name": "Close Pane"
|
||||
// "name": "[-] Split Horizontal"
|
||||
// "name": "[ | ] Split Vertical"
|
||||
// "name": "Next Tab"
|
||||
// "name": "Prev Tab"
|
||||
// "name": "Open Settings"
|
||||
// "name": "Open Media Controls"
|
||||
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
|
||||
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
|
||||
// **Tab**".
|
||||
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
|
||||
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
|
||||
// Controls".
|
||||
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
|
||||
// "Split", then the **V** in "Vertical").
|
||||
// Arguments:
|
||||
// - searchText: the string of text to search for in `name`
|
||||
// - name: the name to check
|
||||
// Return Value:
|
||||
// - the relative weight of this match
|
||||
int CommandPalette::_getWeight(const winrt::hstring& searchText,
|
||||
const winrt::hstring& name)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
bool lastWasSpace = true;
|
||||
|
||||
auto it = name.cbegin();
|
||||
|
||||
for (auto searchChar : searchText)
|
||||
{
|
||||
searchChar = std::towlower(searchChar);
|
||||
// Advance the iterator to the next character that we're looking
|
||||
// for.
|
||||
|
||||
bool lastWasMatch = true;
|
||||
while (true)
|
||||
{
|
||||
// If we are at the end of the name string, we haven't found
|
||||
// it.
|
||||
if (it == name.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// found it
|
||||
if (std::towlower(*it) == searchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasSpace = *it == L' ';
|
||||
++it;
|
||||
lastWasMatch = false;
|
||||
}
|
||||
|
||||
// Advance the iterator by one character so that we don't
|
||||
// end up on the same character in the next iteration.
|
||||
++it;
|
||||
|
||||
totalWeight += 1;
|
||||
totalWeight += lastWasSpace ? 1 : 0;
|
||||
totalWeight += (lastWasMatch) ? 1 : 0;
|
||||
}
|
||||
|
||||
return totalWeight;
|
||||
}
|
||||
|
||||
void CommandPalette::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
|
||||
{
|
||||
_dispatch = dispatch;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dismiss the command palette. This will:
|
||||
// * select all the current text in the input box
|
||||
// * set our visibility to Hidden
|
||||
// * raise our Closed event, so the page can return focus to the active Terminal
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_close()
|
||||
{
|
||||
Visibility(Visibility::Collapsed);
|
||||
|
||||
// Clear the text box each time we close the dialog. This is consistent with VsCode.
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
}
|
||||
52
src/cascadia/TerminalApp/CommandPalette.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommandPalette.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct CommandPalette : CommandPaletteT<CommandPalette>
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
void SetActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
|
||||
|
||||
void _selectNextItem(const bool moveDown);
|
||||
|
||||
void _updateFilteredActions();
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
|
||||
void _dispatchCommand(const TerminalApp::Command& command);
|
||||
|
||||
void _dismissPalette();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CommandPalette);
|
||||
}
|
||||
18
src/cascadia/TerminalApp/CommandPalette.idl
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../Command.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetActions(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
}
|
||||
}
|
||||
237
src/cascadia/TerminalApp/CommandPalette.xaml
Normal file
@@ -0,0 +1,237 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
x:Class="TerminalApp.CommandPalette"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- ThemeShadow is only on 18362. This "Windows10version1903" bit
|
||||
adds it conditionally -->
|
||||
<Windows10version1903:ThemeShadow x:Name="CommandPaletteShadow" />
|
||||
|
||||
<!-- This creates an instance of our CommandKeyChordVisibilityConverter we can reference below -->
|
||||
<local:CommandKeyChordVisibilityConverter x:Key="CommandKeyChordVisibilityConverter"/>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#333333" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#B5B5B5"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
<!-- KeyChordText styles -->
|
||||
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#CCCCCC" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#636363"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#DADADA"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
<!-- KeyChordText styles -->
|
||||
<Style x:Key="KeyChordBorderStyle" TargetType="Border">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="1" />
|
||||
<Setter Property="Background" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemColorWindowColor}" />
|
||||
</Style>
|
||||
|
||||
<!-- KeyChordText styles (use XAML defaults for High Contrast theme) -->
|
||||
<Style x:Key="KeyChordBorderStyle" TargetType="Border"/>
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="8*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
|
||||
order to receive pointer events, an element needs to be _not_ transparent.
|
||||
However, we want to be able to eat all the clicks outside the immediate
|
||||
bounds of the command palette, and we don't want a semi-transparent overlay
|
||||
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
|
||||
the entire page, then it can be mostly transparent, and cause the root grid
|
||||
to receive clicks _anywhere_ in its bounds. -->
|
||||
|
||||
<Grid
|
||||
x:Name="_shadowBackdrop"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="_backdrop"
|
||||
Style="{ThemeResource CommandPaletteBackground}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
PointerPressed="_backdropPointerPressed"
|
||||
Margin="8"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
x:Uid="CommandPalette_SearchBox"
|
||||
x:Name="_searchBox"
|
||||
Margin="8"
|
||||
IsSpellCheckEnabled="False"
|
||||
TextChanged="_filterTextChanged"
|
||||
KeyDown="_keyDownHandler"
|
||||
Text="">
|
||||
</TextBox>
|
||||
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
x:Uid="CommandPalette_NoMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1">
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
x:Name="_filteredActionsView"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
SelectionMode="Single"
|
||||
CanReorderItems="False"
|
||||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:Command">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" >
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- TODO GH#6644: Add Icon to command palette entries, in column 0 -->
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind KeyChordText,
|
||||
Mode=OneWay,
|
||||
Converter={StaticResource CommandKeyChordVisibilityConverter}}"
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "../../inc/DefaultSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
@@ -17,12 +16,12 @@ using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
static constexpr std::string_view KeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view BindingsKey{ "bindings" };
|
||||
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
|
||||
static constexpr std::string_view AlwaysShowTabsKey{ "alwaysShowTabs" };
|
||||
static constexpr std::string_view InitialRowsKey{ "initialRows" };
|
||||
static constexpr std::string_view InitialColsKey{ "initialCols" };
|
||||
static constexpr std::string_view RowsToScrollKey{ "rowsToScroll" };
|
||||
static constexpr std::string_view InitialPositionKey{ "initialPosition" };
|
||||
static constexpr std::string_view ShowTitleInTitlebarKey{ "showTerminalTitleInTitlebar" };
|
||||
static constexpr std::string_view ThemeKey{ "theme" };
|
||||
@@ -31,10 +30,13 @@ static constexpr std::string_view ShowTabsInTitlebarKey{ "showTabsInTitlebar" };
|
||||
static constexpr std::string_view WordDelimitersKey{ "wordDelimiters" };
|
||||
static constexpr std::string_view CopyOnSelectKey{ "copyOnSelect" };
|
||||
static constexpr std::string_view CopyFormattingKey{ "copyFormatting" };
|
||||
static constexpr std::string_view WarnAboutLargePasteKey{ "largePasteWarning" };
|
||||
static constexpr std::string_view WarnAboutMultiLinePasteKey{ "multiLinePasteWarning" };
|
||||
static constexpr std::string_view LaunchModeKey{ "launchMode" };
|
||||
static constexpr std::string_view ConfirmCloseAllKey{ "confirmCloseAllTabs" };
|
||||
static constexpr std::string_view SnapToGridOnResizeKey{ "snapToGridOnResize" };
|
||||
static constexpr std::string_view EnableStartupTaskKey{ "startOnUserLogin" };
|
||||
static constexpr std::string_view AlwaysOnTopKey{ "alwaysOnTop" };
|
||||
|
||||
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
|
||||
|
||||
@@ -71,7 +73,6 @@ GlobalAppSettings::GlobalAppSettings() :
|
||||
_defaultProfile{},
|
||||
_InitialRows{ DEFAULT_ROWS },
|
||||
_InitialCols{ DEFAULT_COLS },
|
||||
_RowsToScroll{ DEFAULT_ROWSTOSCROLL },
|
||||
_WordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_DebugFeaturesEnabled{ debugFeaturesDefault }
|
||||
{
|
||||
@@ -104,9 +105,9 @@ GUID GlobalAppSettings::DefaultProfile() const
|
||||
return _defaultProfile;
|
||||
}
|
||||
|
||||
std::optional<std::wstring> GlobalAppSettings::UnparsedDefaultProfile() const
|
||||
std::wstring GlobalAppSettings::UnparsedDefaultProfile() const
|
||||
{
|
||||
return _unparsedDefaultProfile;
|
||||
return _unparsedDefaultProfile.value();
|
||||
}
|
||||
|
||||
AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept
|
||||
@@ -125,7 +126,6 @@ void GlobalAppSettings::ApplyToSettings(TerminalSettings& settings) const noexce
|
||||
settings.KeyBindings(GetKeybindings());
|
||||
settings.InitialRows(_InitialRows);
|
||||
settings.InitialCols(_InitialCols);
|
||||
settings.RowsToScroll(_RowsToScroll);
|
||||
|
||||
settings.WordDelimiters(_WordDelimiters);
|
||||
settings.CopyOnSelect(_CopyOnSelect);
|
||||
@@ -162,19 +162,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
|
||||
JsonUtils::GetInt(json, InitialColsKey, _InitialCols);
|
||||
|
||||
if (auto rowsToScroll{ json[JsonKey(RowsToScrollKey)] })
|
||||
{
|
||||
//if it's not an int we fall back to setting it to 0, which implies using the system setting. This will be the case if it's set to "system"
|
||||
if (rowsToScroll.isInt())
|
||||
{
|
||||
_RowsToScroll = rowsToScroll.asInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
_RowsToScroll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto initialPosition{ json[JsonKey(InitialPositionKey)] })
|
||||
{
|
||||
_ParseInitialPosition(initialPosition.asString(), _InitialPosition);
|
||||
@@ -190,6 +177,10 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
|
||||
JsonUtils::GetBool(json, CopyFormattingKey, _CopyFormatting);
|
||||
|
||||
JsonUtils::GetBool(json, WarnAboutLargePasteKey, _WarnAboutLargePaste);
|
||||
|
||||
JsonUtils::GetBool(json, WarnAboutMultiLinePasteKey, _WarnAboutMultiLinePaste);
|
||||
|
||||
if (auto launchMode{ json[JsonKey(LaunchModeKey)] })
|
||||
{
|
||||
_LaunchMode = _ParseLaunchMode(GetWstringFromJson(launchMode));
|
||||
@@ -205,18 +196,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
_TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
|
||||
}
|
||||
|
||||
if (auto keybindings{ json[JsonKey(KeybindingsKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(keybindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
|
||||
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
|
||||
|
||||
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
|
||||
@@ -228,6 +207,33 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
|
||||
|
||||
JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
|
||||
|
||||
JsonUtils::GetBool(json, AlwaysOnTopKey, _AlwaysOnTop);
|
||||
|
||||
// This is a helper lambda to get the keybindings and commands out of both
|
||||
// and array of objects. We'll use this twice, once on the legacy
|
||||
// `keybindings` key, and again on the newer `bindings` key.
|
||||
auto parseBindings = [this, &json](auto jsonKey) {
|
||||
if (auto bindings{ json[JsonKey(jsonKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(bindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
|
||||
// Now parse the array again, but this time as a list of commands.
|
||||
warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings);
|
||||
// It's possible that the user provided commands have some warnings
|
||||
// in them, similar to the keybindings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
};
|
||||
parseBindings(LegacyKeybindingsKey);
|
||||
parseBindings(BindingsKey);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -365,3 +371,8 @@ std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindings
|
||||
{
|
||||
return _keybindingsWarnings;
|
||||
}
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ Author(s):
|
||||
#pragma once
|
||||
#include "AppKeyBindings.h"
|
||||
#include "ColorScheme.h"
|
||||
#include "Command.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
@@ -54,11 +55,13 @@ public:
|
||||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
|
||||
|
||||
// These are implemented manually to handle the string/GUID exchange
|
||||
// by higher layers in the app.
|
||||
void DefaultProfile(const GUID defaultProfile) noexcept;
|
||||
GUID DefaultProfile() const;
|
||||
std::optional<std::wstring> UnparsedDefaultProfile() const;
|
||||
std::wstring UnparsedDefaultProfile() const;
|
||||
|
||||
GETSET_PROPERTY(int32_t, InitialRows); // default value set in constructor
|
||||
GETSET_PROPERTY(int32_t, InitialCols); // default value set in constructor
|
||||
@@ -67,11 +70,12 @@ public:
|
||||
GETSET_PROPERTY(bool, ConfirmCloseAllTabs, true);
|
||||
GETSET_PROPERTY(winrt::Windows::UI::Xaml::ElementTheme, Theme, winrt::Windows::UI::Xaml::ElementTheme::Default);
|
||||
GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal);
|
||||
GETSET_PROPERTY(int, RowsToScroll); // default value set in constructor
|
||||
GETSET_PROPERTY(bool, ShowTabsInTitlebar, true);
|
||||
GETSET_PROPERTY(std::wstring, WordDelimiters); // default value set in constructor
|
||||
GETSET_PROPERTY(bool, CopyOnSelect, false);
|
||||
GETSET_PROPERTY(bool, CopyFormatting, false);
|
||||
GETSET_PROPERTY(bool, WarnAboutLargePaste, true);
|
||||
GETSET_PROPERTY(bool, WarnAboutMultiLinePaste, true);
|
||||
GETSET_PROPERTY(LaunchPosition, InitialPosition);
|
||||
GETSET_PROPERTY(winrt::TerminalApp::LaunchMode, LaunchMode, winrt::TerminalApp::LaunchMode::DefaultMode);
|
||||
GETSET_PROPERTY(bool, SnapToGridOnResize, true);
|
||||
@@ -79,8 +83,8 @@ public:
|
||||
GETSET_PROPERTY(bool, SoftwareRendering, false);
|
||||
GETSET_PROPERTY(bool, ForceVTInput, false);
|
||||
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
|
||||
|
||||
GETSET_PROPERTY(bool, StartOnUserLogin, false);
|
||||
GETSET_PROPERTY(bool, AlwaysOnTop, false);
|
||||
|
||||
private:
|
||||
std::optional<std::wstring> _unparsedDefaultProfile;
|
||||
@@ -90,6 +94,7 @@ private:
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
|
||||
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
|
||||
|
||||
490
src/cascadia/TerminalApp/JsonUtilsNew.h
Normal file
@@ -0,0 +1,490 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- JsonUtils.h
|
||||
|
||||
Abstract:
|
||||
- Helpers for the TerminalApp project
|
||||
Author(s):
|
||||
- Mike Griese - August 2019
|
||||
- Dustin Howett - January 2020
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <json.h>
|
||||
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
// If we don't use winrt, nobody will include the ConversionTraits for winrt stuff.
|
||||
// If nobody includes it, these forward declarations will suffice.
|
||||
struct guid;
|
||||
struct hstring;
|
||||
namespace Windows::Foundation
|
||||
{
|
||||
template<typename T>
|
||||
struct IReference;
|
||||
}
|
||||
}
|
||||
|
||||
namespace TerminalApp::JsonUtils
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
// Function Description:
|
||||
// - Returns a string_view to a Json::Value's internal string storage,
|
||||
// hopefully without copying it.
|
||||
__declspec(noinline) inline const std::string_view GetStringView(const Json::Value& json)
|
||||
{
|
||||
const char* begin{ nullptr };
|
||||
const char* end{ nullptr };
|
||||
json.getString(&begin, &end);
|
||||
const std::string_view zeroCopyString{ begin, gsl::narrow_cast<size_t>(end - begin) };
|
||||
return zeroCopyString;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct DeduceOptional
|
||||
{
|
||||
using Type = typename std::decay<T>::type;
|
||||
static constexpr bool IsOptional = false;
|
||||
};
|
||||
|
||||
template<typename TOpt>
|
||||
struct DeduceOptional<std::optional<TOpt>>
|
||||
{
|
||||
using Type = typename std::decay<TOpt>::type;
|
||||
static constexpr bool IsOptional = true;
|
||||
};
|
||||
|
||||
template<typename TOpt>
|
||||
struct DeduceOptional<::winrt::Windows::Foundation::IReference<TOpt>>
|
||||
{
|
||||
using Type = typename std::decay<TOpt>::type;
|
||||
static constexpr bool IsOptional = true;
|
||||
};
|
||||
}
|
||||
|
||||
// These exceptions cannot use localized messages, as we do not have
|
||||
// guaranteed access to the resource loader.
|
||||
class TypeMismatchException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
TypeMismatchException() :
|
||||
runtime_error("unexpected data type") {}
|
||||
};
|
||||
|
||||
class KeyedException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
KeyedException(const std::string_view key, std::exception_ptr exception) :
|
||||
runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()),
|
||||
_key{ key },
|
||||
_innerException{ std::move(exception) } {}
|
||||
|
||||
std::string GetKey() const
|
||||
{
|
||||
return _key;
|
||||
}
|
||||
|
||||
[[noreturn]] void RethrowInner() const
|
||||
{
|
||||
std::rethrow_exception(_innerException);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _key;
|
||||
std::exception_ptr _innerException;
|
||||
};
|
||||
|
||||
class UnexpectedValueException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
UnexpectedValueException(const std::string_view value) :
|
||||
runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()),
|
||||
_value{ value } {}
|
||||
|
||||
std::string GetValue() const
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _value;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ConversionTrait
|
||||
{
|
||||
// Forward-declare these so the linker can pick up specializations from elsewhere!
|
||||
T FromJson(const Json::Value&);
|
||||
bool CanConvert(const Json::Value& json);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::string>
|
||||
{
|
||||
std::string FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asString();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<std::wstring>
|
||||
{
|
||||
std::wstring FromJson(const Json::Value& json)
|
||||
{
|
||||
return til::u8u16(Detail::GetStringView(json));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WINRT_BASE_H
|
||||
template<>
|
||||
struct ConversionTrait<winrt::hstring> : public ConversionTrait<std::wstring>
|
||||
{
|
||||
// Leverage the wstring converter's validation
|
||||
winrt::hstring FromJson(const Json::Value& json)
|
||||
{
|
||||
return winrt::hstring{ til::u8u16(Detail::GetStringView(json)) };
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<bool>
|
||||
{
|
||||
bool FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asBool();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isBool();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<int>
|
||||
{
|
||||
int FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asInt();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isInt();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<unsigned int>
|
||||
{
|
||||
unsigned int FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asUInt();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isUInt();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<float>
|
||||
{
|
||||
float FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asFloat();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isNumeric();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<double>
|
||||
{
|
||||
double FromJson(const Json::Value& json)
|
||||
{
|
||||
return json.asDouble();
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isNumeric();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<GUID>
|
||||
{
|
||||
GUID FromJson(const Json::Value& json)
|
||||
{
|
||||
return ::Microsoft::Console::Utils::GuidFromString(til::u8u16(Detail::GetStringView(json)));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
if (!json.isString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto string{ Detail::GetStringView(json) };
|
||||
return string.length() == 38 && string.front() == '{' && string.back() == '}';
|
||||
}
|
||||
};
|
||||
|
||||
// (GUID and winrt::guid are mutually convertible!)
|
||||
template<>
|
||||
struct ConversionTrait<winrt::guid> : public ConversionTrait<GUID>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ConversionTrait<til::color>
|
||||
{
|
||||
til::color FromJson(const Json::Value& json)
|
||||
{
|
||||
return ::Microsoft::Console::Utils::ColorFromHexString(Detail::GetStringView(json));
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
if (!json.isString())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto string{ Detail::GetStringView(json) };
|
||||
return (string.length() == 7 || string.length() == 4) && string.front() == '#';
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename TBase>
|
||||
struct EnumMapper
|
||||
{
|
||||
using BaseEnumMapper = EnumMapper<T, TBase>;
|
||||
using pair_type = std::pair<std::string_view, T>;
|
||||
T FromJson(const Json::Value& json)
|
||||
{
|
||||
const auto name{ Detail::GetStringView(json) };
|
||||
for (const auto& pair : TBase::mappings)
|
||||
{
|
||||
if (pair.first == name)
|
||||
{
|
||||
return pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
throw UnexpectedValueException{ name };
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return json.isString();
|
||||
}
|
||||
};
|
||||
|
||||
// FlagMapper is EnumMapper, but it works for bitfields.
|
||||
// It supports a string (single flag) or an array of strings.
|
||||
// Does an O(n*m) search; meant for small search spaces!
|
||||
//
|
||||
// Cleverly leverage EnumMapper to do the heavy lifting.
|
||||
template<typename T, typename TBase>
|
||||
struct FlagMapper : public EnumMapper<T, TBase>
|
||||
{
|
||||
private:
|
||||
// Hide BaseEnumMapper so FlagMapper's consumers cannot see
|
||||
// it.
|
||||
using BaseEnumMapper = EnumMapper<T, TBase>::BaseEnumMapper;
|
||||
|
||||
public:
|
||||
using BaseFlagMapper = FlagMapper<T, TBase>;
|
||||
static constexpr T AllSet{ static_cast<T>(~0u) };
|
||||
static constexpr T AllClear{ static_cast<T>(0u) };
|
||||
|
||||
T FromJson(const Json::Value& json)
|
||||
{
|
||||
if (json.isString())
|
||||
{
|
||||
return BaseEnumMapper::FromJson(json);
|
||||
}
|
||||
else if (json.isArray())
|
||||
{
|
||||
unsigned int seen{ 0 };
|
||||
T value{};
|
||||
for (const auto& element : json)
|
||||
{
|
||||
const auto newFlag{ BaseEnumMapper::FromJson(element) };
|
||||
if (++seen > 1 &&
|
||||
((newFlag == AllClear && value != AllClear) ||
|
||||
(value == AllClear && newFlag != AllClear)))
|
||||
{
|
||||
// attempt to combine AllClear (explicitly) with anything else
|
||||
throw UnexpectedValueException{ element.asString() };
|
||||
}
|
||||
value |= newFlag;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// We'll only get here if CanConvert has failed us.
|
||||
return AllClear;
|
||||
}
|
||||
|
||||
bool CanConvert(const Json::Value& json)
|
||||
{
|
||||
return BaseEnumMapper::CanConvert(json) || json.isArray();
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Helper that will populate a reference with a value converted from a json object.
|
||||
// Arguments:
|
||||
// - json: the json object to convert
|
||||
// - target: the value to populate with the converted result
|
||||
// Return Value:
|
||||
// - a boolean indicating whether the value existed (in this case, was non-null)
|
||||
//
|
||||
// GetValue, type-deduced, manual converter
|
||||
template<typename T, typename Converter>
|
||||
bool GetValue(const Json::Value& json, T& target, Converter&& conv)
|
||||
{
|
||||
if constexpr (Detail::DeduceOptional<T>::IsOptional)
|
||||
{
|
||||
// FOR OPTION TYPES
|
||||
// - If the json object is set to `null`, then
|
||||
// we'll instead set the target back to the empty optional.
|
||||
if (json.isNull())
|
||||
{
|
||||
target = T{}; // zero-construct an empty optional
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (json)
|
||||
{
|
||||
if (!conv.CanConvert(json))
|
||||
{
|
||||
throw TypeMismatchException{};
|
||||
}
|
||||
|
||||
target = conv.FromJson(json);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// GetValue, forced return type, manual converter
|
||||
template<typename T, typename Converter>
|
||||
std::decay_t<T> GetValue(const Json::Value& json, Converter&& conv)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValue(json, local, std::forward<Converter>(conv));
|
||||
return local; // returns zero-initialized or value
|
||||
}
|
||||
|
||||
// GetValueForKey, type-deduced, manual converter
|
||||
template<typename T, typename Converter>
|
||||
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target, Converter&& conv)
|
||||
{
|
||||
if (auto found{ json.find(&*key.cbegin(), (&*key.cbegin()) + key.size()) })
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetValue(*found, target, std::forward<Converter>(conv));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Wrap any caught exceptions in one that preserves context.
|
||||
throw KeyedException(key, std::current_exception());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// GetValueForKey, forced return type, manual converter
|
||||
template<typename T, typename Converter>
|
||||
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key, Converter&& conv)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValueForKey(json, key, local, std::forward<Converter>(conv));
|
||||
return local; // returns zero-initialized?
|
||||
}
|
||||
|
||||
// GetValue, type-deduced, with automatic converter
|
||||
template<typename T>
|
||||
bool GetValue(const Json::Value& json, T& target)
|
||||
{
|
||||
return GetValue(json, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// GetValue, forced return type, with automatic converter
|
||||
template<typename T>
|
||||
std::decay_t<T> GetValue(const Json::Value& json)
|
||||
{
|
||||
std::decay_t<T> local{};
|
||||
GetValue(json, local, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
return local; // returns zero-initialized or value
|
||||
}
|
||||
|
||||
// GetValueForKey, type-deduced, with automatic converter
|
||||
template<typename T>
|
||||
bool GetValueForKey(const Json::Value& json, std::string_view key, T& target)
|
||||
{
|
||||
return GetValueForKey(json, key, target, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// GetValueForKey, forced return type, with automatic converter
|
||||
template<typename T>
|
||||
std::decay_t<T> GetValueForKey(const Json::Value& json, std::string_view key)
|
||||
{
|
||||
return GetValueForKey<T>(json, key, ConversionTrait<typename Detail::DeduceOptional<T>::Type>{});
|
||||
}
|
||||
|
||||
// Get multiple values for keys (json, k, &v, k, &v, k, &v, ...).
|
||||
// Uses the default converter for each v.
|
||||
// Careful: this can cause a template explosion.
|
||||
constexpr void GetValuesForKeys(const Json::Value& /*json*/) {}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
void GetValuesForKeys(const Json::Value& json, std::string_view key1, T&& val1, Args&&... args)
|
||||
{
|
||||
GetValueForKey(json, key1, val1);
|
||||
GetValuesForKeys(json, std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
#define JSON_ENUM_MAPPER(...) \
|
||||
template<> \
|
||||
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
|
||||
public ::TerminalApp::JsonUtils::EnumMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
|
||||
|
||||
#define JSON_FLAG_MAPPER(...) \
|
||||
template<> \
|
||||
struct ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__> : \
|
||||
public ::TerminalApp::JsonUtils::FlagMapper<__VA_ARGS__, ::TerminalApp::JsonUtils::ConversionTrait<__VA_ARGS__>>
|
||||
|
||||
#define JSON_MAPPINGS(Count) \
|
||||
static constexpr std::array<pair_type, Count> mappings
|
||||