diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt
index 17b73e780d6..fdb6a2dcd05 100644
--- a/.github/actions/spell-check/dictionary/apis.txt
+++ b/.github/actions/spell-check/dictionary/apis.txt
@@ -8,11 +8,12 @@ EXPCMDSTATE
fullkbd
href
IAsync
-IBox
IBind
+IBox
IClass
IComparable
ICustom
+IDialog
IDirect
IExplorer
IMap
@@ -27,6 +28,7 @@ NOAGGREGATION
NOREDIRECTIONBITMAP
oaidl
ocidl
+PAGESCROLL
RETURNCMD
rfind
roundf
diff --git a/.github/actions/spell-check/dictionary/dictionary.txt b/.github/actions/spell-check/dictionary/dictionary.txt
index d3590ea587f..c01a75b1927 100644
--- a/.github/actions/spell-check/dictionary/dictionary.txt
+++ b/.github/actions/spell-check/dictionary/dictionary.txt
@@ -284908,6 +284908,7 @@ overliing
overliking
overlimit
overline
+overlined
overling
overlinger
overlinked
diff --git a/.github/actions/spell-check/dictionary/microsoft.txt b/.github/actions/spell-check/dictionary/microsoft.txt
index 960efdbd7d5..5e5d2ee3164 100644
--- a/.github/actions/spell-check/dictionary/microsoft.txt
+++ b/.github/actions/spell-check/dictionary/microsoft.txt
@@ -1,4 +1,5 @@
ACLs
+backplating
DACL
DACLs
LKG
diff --git a/.github/actions/spell-check/expect/alphabet.txt b/.github/actions/spell-check/expect/alphabet.txt
index 3644d8e5922..6d395ae7a5d 100644
--- a/.github/actions/spell-check/expect/alphabet.txt
+++ b/.github/actions/spell-check/expect/alphabet.txt
@@ -8,6 +8,7 @@ abcdefghijklmnop
ABCDEFGHIJKLMNOPQRST
abcdefghijklmnopqrstuvwxyz
ABE
+BBGGRR
BBBBBBBBBBBBBBDDDD
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt
index 7fe9b08c3e8..c4ab573558d 100644
--- a/.github/actions/spell-check/expect/expect.txt
+++ b/.github/actions/spell-check/expect/expect.txt
@@ -202,6 +202,7 @@ capslock
CARETBLINKINGENABLED
CARRIAGERETURN
cascadia
+cassert
catid
carlos
zamora
@@ -270,12 +271,14 @@ clipbrd
CLIPCHILDREN
CLIPSIBLINGS
cliutils
+clocale
closetest
cloudconsole
cls
CLSCTX
clsid
CLUSTERMAP
+cmath
cmatrix
cmder
CMDEXT
@@ -419,12 +422,16 @@ Csr
csrmsg
CSRSS
csrutil
+cstdarg
+cstddef
cstdio
+cstdlib
cstr
cstring
cstyle
CSwitch
CText
+ctime
ctl
ctlseqs
Ctlv
@@ -590,6 +597,7 @@ devops
Dext
df
DFactory
+DFMT
dh
dialogbox
diffing
@@ -1143,6 +1151,7 @@ IUI
IUia
IUnknown
ivalid
+IValue
IVector
IWait
iwch
@@ -1255,6 +1264,7 @@ Loremipsumdolorsitamet
lowercased
loword
lparam
+lparen
LPBYTE
LPCCH
lpch
@@ -1614,6 +1624,7 @@ OSCCT
OSCFG
OSCRCC
OSCSCC
+OSCSCB
OSCWT
OSDEPENDSROOT
osfhandle
@@ -1979,6 +1990,7 @@ roadmap
robomac
roundtrip
ROWSTOSCROLL
+rparen
RRF
RRRGGGBB
rtcore
@@ -2125,6 +2137,7 @@ SIGDN
SINGLEFLAG
SINGLETHREADED
siup
+SIZEBOX
sizeof
SIZESCROLL
SKIPFONT
diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt
index 3833627a51d..413709e1202 100644
--- a/.github/actions/spell-check/patterns/patterns.txt
+++ b/.github/actions/spell-check/patterns/patterns.txt
@@ -14,5 +14,8 @@ Scro\&ll
# selectionInput.cpp
:\\windows\\syste\b
TestUtils::VerifyExpectedString\(tb, L"[^"]+"
-hostSm\.ProcessString\(L"[^"]+"
+(?:hostSm|mach)\.ProcessString\(L"[^"]+"
\b([A-Za-z])\1{3,}\b
+Base64::s_(?:En|De)code\(L"[^"]+"
+VERIFY_ARE_EQUAL\(L"[^"]+"
+L"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\+/"
diff --git a/OpenConsole.sln b/OpenConsole.sln
index b4d94c467fd..4d0d2e61481 100644
--- a/OpenConsole.sln
+++ b/OpenConsole.sln
@@ -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}
diff --git a/build/scripts/Test-WindowsTerminalPackage.ps1 b/build/scripts/Test-WindowsTerminalPackage.ps1
index 5958a69f8b5..88cb14bbf97 100644
--- a/build/scripts/Test-WindowsTerminalPackage.ps1
+++ b/build/scripts/Test-WindowsTerminalPackage.ps1
@@ -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
}
diff --git a/custom.props b/custom.props
index a44cb494594..272e30a61fa 100644
--- a/custom.props
+++ b/custom.props
@@ -5,7 +5,7 @@
true
2020
1
- 1
+ 2
Windows Terminal
diff --git a/dep/gsl b/dep/gsl
index 7e99e76c976..0f6dbc9e291 160000
--- a/dep/gsl
+++ b/dep/gsl
@@ -1 +1 @@
-Subproject commit 7e99e76c9761d0d0b0848b91f8648830670ee872
+Subproject commit 0f6dbc9e2915ef5c16830f3fa3565738de2a9230
diff --git a/doc/building.md b/doc/building.md
index b99c139c333..27c1e7d806e 100644
--- a/doc/building.md
+++ b/doc/building.md
@@ -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:
diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md
index ffca902b73f..9c58edc89f4 100644
--- a/doc/cascadia/SettingsSchema.md
+++ b/doc/cascadia/SettingsSchema.md
@@ -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 Ctrl + T. |
| `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 Ctrl + T 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. |
diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json
index 66a912a19bf..7232d65f133 100644
--- a/doc/cascadia/profiles.schema.json
+++ b/doc/cascadia/profiles.schema.json
@@ -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,14 @@
"scrollUpPage",
"splitPane",
"switchToTab",
+ "toggleFocusMode",
"toggleFullscreen",
+ "toggleRetroEffect",
"find",
+ "setTabColor",
+ "openTabColorPicker",
+ "renameTab",
+ "commandPalette",
"unbound"
],
"type": "string"
@@ -257,6 +263,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 +294,7 @@
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
{ "$ref": "#/definitions/OpenSettingsAction" },
+ { "$ref": "#/definitions/SetTabColorAction" },
{ "type": "null" }
]
},
@@ -316,6 +339,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"
@@ -364,10 +397,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.",
diff --git a/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md b/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md
index 7f95ccc979a..b8bcca7cf5a 100644
--- a/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md
+++ b/doc/specs/#4993 - Keyboard Selection/Keyboard-Selection.md
@@ -1,7 +1,7 @@
---
author: Carlos Zamora @carlos-zamora
created on: 2019-08-30
-last updated: 2020-06-23
+last updated: 2020-06-26
issue id: 715
---
@@ -49,17 +49,25 @@ The Terminal Core will need to expose a `MoveSelectionEndpoint()` function that
- `enum Direction`: the direction that the selection anchor will attempt to move to. Possible values include `Up`, `Down`, `Left`, and `Right`.
- `enum SelectionExpansionMode`: the selection expansion mode that the selection anchor will adhere to. Possible values include `Cell`, `Word`, `Viewport`, `Buffer`.
+#### Moving by Cell
For `SelectionExpansionMode = Cell`, the selection anchor will be updated according to the buffer's output pattern. For **horizontal movements**, the selection anchor will attempt to move left or right. If a viewport boundary is hit, the anchor will wrap appropriately (i.e.: hitting the left boundary moves it to the last cell of the line above it).
For **vertical movements**, the selection anchor will attempt to move up or down. If a **viewport boundary** is hit and there is a scroll buffer, the anchor will move and scroll accordingly by a line. If a **buffer boundary** is hit, the anchor will not move. In this case, however, the event will still be considered handled.
-For `SelectionExpansionMode = Word`, the selection anchor will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection anchor will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the anchor. As before, if a boundary is hit, the anchor will wrap appropriately.
+**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the anchor.
+
+#### Moving by Word
+For `SelectionExpansionMode = Word`, the selection anchor will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection anchor will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the anchor. As before, if a boundary is hit, the anchor will wrap appropriately. See [Future Considerations](#FutureConsiderations) for how this will interact with line wrapping.
For **vertical movements**, the movement is a little more complicated than before. The selection will still respond to buffer and viewport boundaries as before. If the user is trying to move up, the selection anchor will attempt to move up by one line, then selection will be expanded leftwards. Alternatively, if the user is trying to move down, the selection anchor will attempt to move down by one line, then the selection will be expanded rightwards.
+#### Moving by Viewport
For `SelectionExpansionMode = Viewport`, the selection anchor will be updated according to the viewport's height. Horizontal movements will be updated according to the viewport's width, thus resulting in the anchor being moved to the left/right boundary of the viewport.
-**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the anchor.
+#### Moving by Buffer
+
+For `SelectionExpansionMode = Buffer`, the selection anchor will be moved to the beginning or end of all the text within the buffer. If moving up or left, set the position to 0,0 (the origin of the buffer). If moving down or right, set the position to the last character in the buffer.
+
**NOTE**: In all cases, horizontal movements attempting to move past the left/right viewport boundaries result in a wrap. Vertical movements attempting to move past the top/bottom viewport boundaries will scroll such that the selection is at the edge of the screen. Vertical movements attempting to move past the top/bottom buffer boundaries will be clamped to be within buffer boundaries.
@@ -128,9 +136,9 @@ Thanks to Keybinding Args, there will only be 2 new commands that need to be add
| Action | Keybinding Args | Description |
|--|--|--|
| `moveSelectionPoint` | | If a selection exists, moves the last selection anchor.
-| | `Enum direction { up, down, left, right}` | The direction the selection will be moved in. |
-| | `Enum expansionMode { cell, word, viewport, buffer }` | The context for which to move the selection anchor to. (defaults to `cell`)
-| `selectEntireBuffer` | | Select the entire text buffer.
+| | `Enum direction { up, down, left, right }` | The direction the selection will be moved in. |
+| | `Enum expandBy { cell, word, page, all }` | The context for which to move the selection anchor to. (defaults to `cell`)
+| `selectAll` | | Select the entire text buffer.
| `markMode` | | Cycle the selection point targeted by `moveSelectionPoint`. If no selection exists, a selection is created at the cursor. |
| `toggleBlockSelection` | | Transform the existing selection between a block selection and a line selection. |
@@ -144,26 +152,27 @@ By default, the following keybindings will be set:
{ "command": { "action": "moveSelectionPoint", "direction": "right" }, "keys": "shift+right" },
// Word Selection
-{ "command": { "action": "moveSelectionPoint", "direction": "left", "expansionMode": "word" }, "keys": "ctrl+shift+left" },
-{ "command": { "action": "moveSelectionPoint", "direction": "right", "expansionMode": "word" }, "keys": "ctrl+shift+right" },
+{ "command": { "action": "moveSelectionPoint", "direction": "left", "expandBy": "word" }, "keys": "ctrl+shift+left" },
+{ "command": { "action": "moveSelectionPoint", "direction": "right", "expandBy": "word" }, "keys": "ctrl+shift+right" },
// Viewport Selection
-{ "command": { "action": "moveSelectionPoint", "direction": "left", "expansionMode": "viewport" }, "keys": "shift+home" },
-{ "command": { "action": "moveSelectionPoint", "direction": "right", "expansionMode": "viewport" }, "keys": "shift+end" },
-{ "command": { "action": "moveSelectionPoint", "direction": "up", "expansionMode": "viewport" }, "keys": "shift+pgup" },
-{ "command": { "action": "moveSelectionPoint", "direction": "down", "expansionMode": "viewport" }, "keys": "shift+pgdn" },
+{ "command": { "action": "moveSelectionPoint", "direction": "left", "expandBy": "page" }, "keys": "shift+home" },
+{ "command": { "action": "moveSelectionPoint", "direction": "right", "expandBy": "page" }, "keys": "shift+end" },
+{ "command": { "action": "moveSelectionPoint", "direction": "up", "expandBy": "page" }, "keys": "shift+pgup" },
+{ "command": { "action": "moveSelectionPoint", "direction": "down", "expandBy": "page" }, "keys": "shift+pgdn" },
// Buffer Corner Selection
-{ "command": { "action": "moveSelectionPoint", "direction": "up", "expansionMode": "buffer" }, "keys": "ctrl+shift+home" },
-{ "command": { "action": "moveSelectionPoint", "direction": "down", "expansionMode": "buffer" }, "keys": "ctrl+shift+end" },
+{ "command": { "action": "moveSelectionPoint", "direction": "up", "expandBy": "all" }, "keys": "ctrl+shift+home" },
+{ "command": { "action": "moveSelectionPoint", "direction": "down", "expandBy": "all" }, "keys": "ctrl+shift+end" },
// Select All
-{ "command": "selectEntireBuffer", "keys": "ctrl+shift+a" },
+{ "command": "selectAll", "keys": "ctrl+shift+a" },
// Mark Mode
{ "command": "markMode", "keys": "ctrl+shift+m" },
{ "command": "toggleBlockSelection", "keys": "alt+shift+m" },
```
+These are in accordance with ConHost's keyboard selection model.
## Capabilities
@@ -195,7 +204,14 @@ The settings model makes all of these features easy to disable, if the user wish
## Future considerations
-None
+### Grapheme Clusters
+When grapheme cluster support is inevitably added to the Text Buffer, moving by "cell" is expected to move by "character" or "cluster". This is similar to how wide glyphs are handled today. Either all of it is selected, or none of it.
+
+### Word Selection Wrap
+At the time of writing this spec, expanding or moving by word is interrupted by the beginning or end of the line, regardless of the wrap flag being set. In the future, selection and the accessibility models will respect the wrap flag on the text buffer.
+
+### Contextual Keybindings
+This feature introduces a large number of keybindings that only work if a selection is active. Currently, key bindings cannot be bound to a context, so if a user binds `moveSelectionPoint` to `shift+up` and there is no selection, `shift+up` is sent directly to the Terminal. In the future, a `context` key could be added to new bindings to get around this problem. That way, users could bind other actions to `shift+up` to run specifically when a selection is not active.
## Resources
diff --git a/doc/specs/#980 - SnapOnOutput.md b/doc/specs/#980 - SnapOnOutput.md
new file mode 100644
index 00000000000..3d48cf45e9c
--- /dev/null
+++ b/doc/specs/#980 - SnapOnOutput.md
@@ -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 ScrollLock key. Researching the use of the ScrollLock key has shown that programs rarely use this key. In most apps, pressing the ScrollLock key does not actually prevent scrolling the application. Additionally, finding a way to bing the `scrollLock` action to the ScrollLock key would be difficult. A physical keyboard may not necessarily have a ScrollLock 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, ctrl+s and ctrl+q 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, ctrl+s does a 'Forward Search History' instead.
+- Additionally, there is a Pause 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)
diff --git a/oss/fmt/cgmanifest.json b/oss/fmt/cgmanifest.json
index a8148d03abc..1a8c5d01b14 100644
--- a/oss/fmt/cgmanifest.json
+++ b/oss/fmt/cgmanifest.json
@@ -4,7 +4,7 @@
"type": "git",
"git": {
"repositoryUrl": "https://github.com/fmtlib/fmt",
- "commitHash": "9bdd1596cef1b57b9556f8bef32dc4a32322ef3e"
+ "commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753"
}
}
}
diff --git a/oss/fmt/include/fmt/chrono.h b/oss/fmt/include/fmt/chrono.h
index 421d464ad8a..e70b8053a61 100644
--- a/oss/fmt/include/fmt/chrono.h
+++ b/oss/fmt/include/fmt/chrono.h
@@ -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(T::max())) {
+ if (from > static_cast((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(T::max())) {
+ if (from > static_cast((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(from);
}
// not within range.
@@ -195,12 +195,13 @@ To safe_duration_cast(std::chrono::duration from,
}
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
- const auto max1 = internal::max_value() / Factor::num;
+ const auto max1 = detail::max_value() / Factor::num;
if (count > max1) {
ec = 1;
return {};
}
- const auto min1 = std::numeric_limits::min() / Factor::num;
+ const auto min1 =
+ (std::numeric_limits::min)() / Factor::num;
if (count < min1) {
ec = 1;
return {};
@@ -269,7 +270,7 @@ To safe_duration_cast(std::chrono::duration from,
// multiply with Factor::num without overflow or underflow
if (Factor::num != 1) {
- constexpr auto max1 = internal::max_value() /
+ constexpr auto max1 = detail::max_value() /
static_cast(Factor::num);
if (count > max1) {
ec = 1;
@@ -306,12 +307,12 @@ To safe_duration_cast(std::chrono::duration 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 struct formatter {
template
@@ -405,7 +406,7 @@ template struct formatter {
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 struct formatter {
template
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
basic_memory_buffer 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 struct formatter {
// 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 struct formatter {
basic_memory_buffer tm_format;
};
-namespace internal {
+namespace detail {
template 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::value ? fp_f : format,
val);
}
+template
+OutputIt copy_unit(string_view unit, OutputIt out, Char) {
+ return std::copy(unit.begin(), unit.end(), out);
+}
+
+template
+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
OutputIt format_duration_unit(OutputIt out) {
- if (const char* unit = get_units()) {
- string_view s(unit);
- if (const_check(std::is_same())) {
- 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())
+ 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 n =
to_unsigned(to_nonnegative_int(value, max_value()));
- 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(out, n, num_digits);
+ out = format_decimal(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(out);
}
};
-} // namespace internal
+} // namespace detail
template
struct formatter, Char> {
private:
basic_format_specs specs;
int precision;
- using arg_ref_type = internal::arg_ref;
+ using arg_ref_type = detail::arg_ref;
arg_ref_type width_ref;
arg_ref_type precision_ref;
mutable basic_string_view format_str;
@@ -1032,7 +1038,7 @@ struct formatter, 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, 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::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, Char> {
-> decltype(ctx.begin()) {
auto range = do_parse(ctx);
format_str = basic_string_view(
- &*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, Char> {
// is not specified.
basic_memory_buffer buf;
auto out = std::back_inserter(buf);
- using range = internal::output_range;
- internal::basic_writer w(range(ctx.out()));
- internal::handle_dynamic_spec(specs.width,
- width_ref, ctx);
- internal::handle_dynamic_spec(
- precision, precision_ref, ctx);
+ detail::handle_dynamic_spec(specs.width, width_ref,
+ ctx);
+ detail::handle_dynamic_spec(precision,
+ precision_ref, ctx);
if (begin == end || *begin == '}') {
- out = internal::format_duration_value(out, d.count(), precision);
- internal::format_duration_unit(out);
+ out = detail::format_duration_value(out, d.count(), precision);
+ detail::format_duration_unit(out);
} else {
- internal::chrono_formatter f(
+ detail::chrono_formatter 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(buf.data(), buf.size()), specs);
}
};
diff --git a/oss/fmt/include/fmt/color.h b/oss/fmt/include/fmt/color.h
index 96d9ab6b438..b65f892afc0 100644
--- a/oss/fmt/include/fmt/color.h
+++ b/oss/fmt/include/fmt/color.h
@@ -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(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 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('\x1b');
buffer[index++] = static_cast('[');
@@ -398,7 +398,7 @@ template struct ansi_color_escape {
if (em_bits & static_cast(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('\x1b');
@@ -429,14 +429,14 @@ template struct ansi_color_escape {
template
FMT_CONSTEXPR ansi_color_escape make_foreground_color(
- internal::color_type foreground) FMT_NOEXCEPT {
- return ansi_color_escape(foreground, internal::data::foreground_color);
+ detail::color_type foreground) FMT_NOEXCEPT {
+ return ansi_color_escape(foreground, detail::data::foreground_color);
}
template
FMT_CONSTEXPR ansi_color_escape make_background_color(
- internal::color_type background) FMT_NOEXCEPT {
- return ansi_color_escape(background, internal::data::background_color);
+ detail::color_type background) FMT_NOEXCEPT {
+ return ansi_color_escape(background, detail::data::background_color);
}
template
@@ -455,11 +455,11 @@ inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
}
template 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(FILE* stream) FMT_NOEXCEPT {
- fputs(internal::data::wreset_color, stream);
+ fputs(detail::data::wreset_color, stream);
}
template
@@ -476,33 +476,31 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts,
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
- auto emphasis = internal::make_emphasis(ts.get_emphasis());
+ auto emphasis = detail::make_emphasis(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
- auto foreground =
- internal::make_foreground_color(ts.get_foreground());
+ auto foreground = detail::make_foreground_color(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
- auto background =
- internal::make_background_color(ts.get_background());
+ auto background = detail::make_background_color(ts.get_background());
buf.append(background.begin(), background.end());
}
- internal::vformat_to(buf, format_str, args);
- if (has_style) internal::reset_color(buf);
+ detail::vformat_to(buf, format_str, args);
+ if (has_style) detail::reset_color(buf);
}
-} // namespace internal
+} // namespace detail
template >
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args> args) {
basic_memory_buffer 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 ::value)>
+ FMT_ENABLE_IF(detail::is_string::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
- internal::check_format_string(format_str);
+ detail::check_format_string(format_str);
using context = buffer_context>;
format_arg_store as{args...};
vprint(f, ts, format_str, basic_format_args(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 ::value)>
+ FMT_ENABLE_IF(detail::is_string::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 vformat(
const text_style& ts, const S& format_str,
basic_format_args>> args) {
basic_memory_buffer 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 >
inline std::basic_string format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
- internal::make_args_checked(format_str, args...));
+ detail::make_args_checked(format_str, args...));
}
FMT_END_NAMESPACE
diff --git a/oss/fmt/include/fmt/compile.h b/oss/fmt/include/fmt/compile.h
index e4b12f349e9..d7e6449ebbf 100644
--- a/oss/fmt/include/fmt/compile.h
+++ b/oss/fmt/include/fmt/compile.h
@@ -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
+struct is_compiled_string : std::is_base_of {};
+
+/**
+ \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
+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 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) { ++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) {
+ 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 id) {
+ FMT_CONSTEXPR int on_arg_id(basic_string_view 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> handler(
@@ -160,23 +191,24 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view format_str,
format_string_compiler(format_str, handler));
}
-template
+template
void format_arg(
- basic_format_parse_context& parse_ctx,
+ basic_format_parse_context& parse_ctx,
Context& ctx, Id arg_id) {
- ctx.advance_to(
- visit_format_arg(arg_formatter(ctx, &parse_ctx), ctx.arg(arg_id)));
+ ctx.advance_to(visit_format_arg(
+ arg_formatter(ctx, &parse_ctx),
+ ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
-template
-auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args)
- -> typename Context::iterator {
+template
+auto vformat_to(OutputIt out, CompiledFormat& cf,
+ basic_format_args args) -> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context 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 args)
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
- internal::format_arg(parse_ctx, ctx, value.arg_index);
+ detail::format_arg(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(parse_ctx, ctx, value.str);
+ detail::format_arg(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 args)
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
- visit_format_arg(arg_formatter(ctx, nullptr, &specs), arg));
+ visit_format_arg(arg_formatter(
+ ctx, nullptr, &specs),
+ arg));
break;
}
}
@@ -240,7 +274,7 @@ struct basic_compiled_format {};
template
struct compiled_format_base : basic_compiled_format {
using char_type = char_t;
- using parts_container = std::vector>;
+ using parts_container = std::vector>;
parts_container compiled_parts;
@@ -305,7 +339,7 @@ struct compiled_format_base::value>>
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_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 {
private:
basic_string_view format_str_;
- template
- friend auto cf::vformat_to(Range out, CompiledFormat& cf,
+ template
+ friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args args) ->
typename Context::iterator;
@@ -359,8 +393,7 @@ template struct text {
template
OutputIt format(OutputIt out, const Args&...) const {
- // TODO: reserve
- return copy_str(data.begin(), data.end(), out);
+ return write(out, data);
}
};
@@ -373,33 +406,6 @@ constexpr text make_text(basic_string_view s, size_t pos,
return {{&s[pos], size}};
}
-template , 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
-OutputIt format_default(OutputIt out, double value) {
- writer w(out);
- w.write(value);
- return w.out();
-}
-
-template
-OutputIt format_default(OutputIt out, Char value) {
- *out++ = value;
- return out;
-}
-
-template
-OutputIt format_default(OutputIt out, const Char* value) {
- auto length = std::char_traits::length(value);
- return copy_str(value, value + length, out);
-}
-
// A replacement field that refers to argument N.
template struct field {
using char_type = Char;
@@ -408,13 +414,30 @@ template 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(args...);
- return format_default(out, arg);
+ return write(out, arg);
}
};
template
struct is_compiled_format> : std::true_type {};
+// A replacement field that refers to argument N and has format specifiers.
+template struct spec_field {
+ using char_type = Char;
+ mutable formatter fmt;
+
+ template
+ OutputIt format(OutputIt out, const Args&... args) const {
+ // This ensures that the argument type is convertile to `const T&`.
+ const T& arg = get(args...);
+ basic_format_context ctx(out, {});
+ return fmt.format(arg, ctx);
+ }
+};
+
+template
+struct is_compiled_format> : std::true_type {};
+
template struct concat {
L lhs;
R rhs;
@@ -450,7 +473,8 @@ constexpr auto compile_format_string(S format_str);
template
constexpr auto parse_tail(T head, S format_str) {
- if constexpr (POS != to_string_view(format_str).size()) {
+ if constexpr (POS !=
+ basic_string_view(format_str).size()) {
constexpr auto tail = compile_format_string(format_str);
if constexpr (std::is_same,
unknown_format>())
@@ -462,6 +486,21 @@ constexpr auto parse_tail(T head, S format_str) {
}
}
+template struct parse_specs_result {
+ formatter fmt;
+ size_t end;
+};
+
+template
+constexpr parse_specs_result parse_specs(basic_string_view str,
+ size_t pos) {
+ str.remove_prefix(pos);
+ auto ctx = basic_format_parse_context(str);
+ auto f = formatter();
+ 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
@@ -475,12 +514,13 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') {
using type = get_type;
- if constexpr (std::is_same::value) {
- return parse_tail(field(),
- format_str);
- } else {
- return unknown_format();
- }
+ return parse_tail(field(),
+ format_str);
+ } else if constexpr (str[POS + 1] == ':') {
+ using type = get_type;
+ constexpr auto result = parse_specs(str, POS + 2);
+ return parse_tail(
+ spec_field{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 ::value)>
+ FMT_ENABLE_IF(is_compile_string::value ||
+ detail::is_compiled_string::value)>
constexpr auto compile(S format_str) {
constexpr basic_string_view 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, 0, 0>(
+ detail::compile_format_string, 0, 0>(
format_str);
if constexpr (std::is_same,
- internal::unknown_format>()) {
- return internal::compiled_format(to_string_view(format_str));
+ detail::unknown_format>()) {
+ return detail::compiled_format(to_string_view(format_str));
} else {
return result;
}
}
}
+#else
+template ::value)>
+constexpr auto compile(S format_str) -> detail::compiled_format {
+ return detail::compiled_format(to_string_view(format_str));
+}
+#endif // __cpp_if_constexpr
+
+// Compiles the format string which must be a string literal.
+template
+auto compile(const Char (&format_str)[N])
+ -> detail::compiled_format {
+ return detail::compiled_format(
+ basic_string_view(format_str, N - 1));
+}
+} // namespace detail
+
+// DEPRECATED! use FMT_COMPILE instead.
+template
+FMT_DEPRECATED auto compile(const Args&... args)
+ -> decltype(detail::compile(args...)) {
+ return detail::compile(args...);
+}
+
+#if FMT_USE_CONSTEXPR
+# ifdef __cpp_if_constexpr
template ::value)>
-std::basic_string format(const CompiledFormat& cf, const Args&... args) {
+ FMT_ENABLE_IF(detail::is_compiled_format::value)>
+FMT_INLINE std::basic_string format(const CompiledFormat& cf,
+ const Args&... args) {
basic_memory_buffer buffer;
- cf.format(std::back_inserter(buffer), args...);
+ detail::buffer& base = buffer;
+ cf.format(std::back_inserter(base), args...);
return to_string(buffer);
}
template ::value)>
+ FMT_ENABLE_IF(detail::is_compiled_format::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
return cf.format(out, args...);
}
-# else
-template ::value)>
-constexpr auto compile(S format_str) -> internal::compiled_format {
- return internal::compiled_format(to_string_view(format_str));
-}
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
-// Compiles the format string which must be a string literal.
-template
-auto compile(const Char (&format_str)[N])
- -> internal::compiled_format {
- return internal::compiled_format(
- basic_string_view(format_str, N - 1));
-}
-
template ::value)>
std::basic_string format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer buffer;
- using range = buffer_range;
using context = buffer_context;
- internal::cf::vformat_to(range(buffer), cf,
- make_format_args(args...));
+ detail::buffer& base = buffer;
+ detail::cf::vformat_to(std::back_inserter(base), cf,
+ make_format_args(args...));
return to_string(buffer);
}
+template ::value)>
+FMT_INLINE std::basic_string format(const S&,
+ Args&&... args) {
+ constexpr basic_string_view str = S();
+ if (str.size() == 2 && str[0] == '{' && str[1] == '}')
+ return fmt::to_string(detail::first(args...));
+ constexpr auto compiled = detail::compile(S());
+ return format(compiled, std::forward