diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt index 19f5fb487..b17163a05 100644 --- a/.github/actions/spelling/allow/apis.txt +++ b/.github/actions/spelling/allow/apis.txt @@ -9,6 +9,7 @@ BUILDBRANCH BUILDMSG BUILDNUMBER BYPOSITION +BYCOMMAND charconv CLASSNOTAVAILABLE cmdletbinding @@ -85,6 +86,7 @@ LSHIFT MENUCOMMAND MENUDATA MENUINFO +MENUITEMINFOW memicmp mptt mov diff --git a/.github/actions/spelling/allow/math.txt b/.github/actions/spelling/allow/math.txt index 1482aedab..bf8960f00 100644 --- a/.github/actions/spelling/allow/math.txt +++ b/.github/actions/spelling/allow/math.txt @@ -1,3 +1,11 @@ +atan +CPrime +HBar +HPrime isnan +LPrime +LStep powf +RSub sqrtf +ULP diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index a96139e3c..a96131468 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -1,5 +1,6 @@ ACLs ADMINS +advapi altform altforms appendwttlogging @@ -15,6 +16,7 @@ CPLs cpptools cppvsdbg CPRs +cryptbase DACL DACLs diffs @@ -46,6 +48,7 @@ powershell propkey pscustomobject QWORD +regedit robocopy SACLs sdkddkver diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 81bfde277..8d751a187 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -67,6 +67,7 @@ SUMS$ ^src/terminal/parser/ft_fuzzer/run\.bat$ ^src/terminal/parser/ft_fuzzer/VTCommandFuzzer\.cpp$ ^src/terminal/parser/ft_fuzzwrapper/run\.bat$ +^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ ^src/tools/lnkd/lnkd\.bat$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index c12609802..c18d880e4 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -189,13 +189,12 @@ cacafire callee capslock CARETBLINKINGENABLED +carlos CARRIAGERETURN cascadia cassert castsi catid -carlos -zamora cazamor CBash cbegin @@ -669,6 +668,7 @@ dwriteglyphrundescriptionclustermap dxgi dxgidwm dxinterop +dxsm dxttbmp eachother eae @@ -938,6 +938,7 @@ GTP guc gui guidatom +guiddef GValue GWL GWLP @@ -1232,6 +1233,7 @@ KLF KLMNO KLMNOPQRST KLMNOPQRSTQQQQQ +KPRIORITY KVM langid LANGUAGELIST @@ -1534,6 +1536,7 @@ NOCOLOR NOCOMM NOCONTEXTHELP NOCOPYBITS +nodefaultlib nodiscard NODUP noexcept @@ -1642,6 +1645,7 @@ onecoreuapuuid onecoreuuid ONECOREWINDOWS onehalf +oneseq ONLCR openbash opencode @@ -1708,6 +1712,7 @@ pcch PCCHAR PCCONSOLE PCD +pcg pch PCHAR PCIDLIST @@ -1803,6 +1808,7 @@ POSX POSXSCROLL POSYSCROLL ppci +PPEB ppf ppguid ppidl @@ -2022,6 +2028,7 @@ Rike RIPMSG RIS RMENU +rng roadmap robomac roundtrip @@ -2227,6 +2234,7 @@ STARTWPARMSW Statusline stdafx STDAPI +stdc stdcall stdcpp stderr @@ -2724,6 +2732,7 @@ wixproj wline wlinestream wmain +wmemory WMSZ wnd WNDALLOC @@ -2850,6 +2859,7 @@ YSize YSubstantial YVIRTUALSCREEN YWalk +zamora ZCmd ZCtrl zsh diff --git a/.github/actions/spelling/expect/web.txt b/.github/actions/spelling/expect/web.txt index 3072b0075..826edf1af 100644 --- a/.github/actions/spelling/expect/web.txt +++ b/.github/actions/spelling/expect/web.txt @@ -1,5 +1,7 @@ http www +easyrgb +php ecma rapidtables WCAG diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 580fc04ed..da6f3a476 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -125,7 +125,7 @@ Team members will be happy to help review specs and guide them to completion. ### Help Wanted -Once the team have approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted). +Once the team has approved an issue/spec, development can proceed. If no developers are immediately available, the spec can be parked ready for a developer to get started. Parked specs' issues will be labeled "Help Wanted". To find a list of development opportunities waiting for developer involvement, visit the Issues and filter on [the Help-Wanted label](https://github.com/microsoft/terminal/labels/Help%20Wanted). --- diff --git a/NOTICE.md b/NOTICE.md index 44e0f6662..5c153f6ab 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -117,7 +117,6 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ``` ## dynamic_bitset @@ -148,7 +147,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ``` ## \{fmt\} @@ -215,7 +213,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ``` @@ -249,7 +246,71 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` +## PCG Random Number Generation + +**Source**: [https://github.com/imneme/pcg-cpp](https://github.com/imneme/pcg-cpp) + +### License + +``` +Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## ConEmu +**Source**: [https://github.com/Maximus5/ConEmu](https://github.com/Maximus5/ConEmu) + +### License + +``` +BSD 3-Clause License + +Copyright (c) 2009-2017, Maximus5 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` # Microsoft Open Source diff --git a/README.md b/README.md index ca416b82c..18523b63b 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ resources useful and interesting: * Windows Terminal Launch: [Build 2019 Session](https://www.youtube.com/watch?v=KMudkRcwjCw) * Run As Radio: [Show 645 - Windows Terminal with Richard - Turner](http://www.runasradio.com/Shows/Show/645) + Turner](https://www.runasradio.com/Shows/Show/645) * Azure Devops Podcast: [Episode 54 - Kayla Cinnamon and Rich Turner on DevOps on the Windows Terminal](http://azuredevopspodcast.clear-measure.com/kayla-cinnamon-and-rich-turner-on-devops-on-the-windows-terminal-team-episode-54) diff --git a/custom.props b/custom.props index 95b29b746..23f7ff5aa 100644 --- a/custom.props +++ b/custom.props @@ -5,7 +5,7 @@ true 2021 1 - 12 + 13 Windows Terminal diff --git a/doc/EXCEPTIONS.md b/doc/EXCEPTIONS.md index 48a6017ed..d6e90988f 100644 --- a/doc/EXCEPTIONS.md +++ b/doc/EXCEPTIONS.md @@ -4,7 +4,7 @@ Introducing exceptions to an existing non-exception-based codebase can be perilous. The console was originally written in C at a time when C++ was relatively unused in the Windows operating system. As part of our project to modernize the Windows console, we converted to use C++, but still had an aversion to using exception-based error handling in -our code for fear that it introduce unexpected failures. However, the STL and other libraries like it are so useful that +our code for fear that it might introduce unexpected failures. However, the STL and other libraries like it are so useful that sometimes it's significantly simpler to use them. Given that, we have a set of rules that we follow when considering exception use. diff --git a/doc/Niksa.md b/doc/Niksa.md index e543191a6..c4c7e4798 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -189,7 +189,7 @@ I think there might be a bit of a misunderstanding here - there are two differen * shell applications, like `cmd.exe`, `powershell`, `zsh`, etc. These are text-only applications that emit streams of characters. They don't care at all about how they're eventually rendered to the user. These are also sometimes referred to as "commandline client" applications. * terminal applications, like the Windows Terminal, gnome-terminal, xterm, iterm2, hyper. These are graphical applications that can be used to render the output of commandline clients. -On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will creates a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal. +On Windows, if you just run `cmd.exe` directly, the OS will create an instance of `conhost.exe` as the _terminal_ for `cmd.exe`. The same thing happens for `powershell.exe`, the system will create a new conhost window for any client that's not already connected to a terminal of some sort. This has lead to an enormous amount of confusion for people thinking that a conhost window is actually a "`cmd` window". `cmd` can't have a window, it's just a commandline application. Its window is always some other terminal. Any terminal can run any commandline client application. So you can use the Windows Terminal to run whatever shell you want. I use mine for both `cmd` and `powershell`, and also WSL: diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 4f55d576b..874667793 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -188,6 +188,10 @@ ], "type": "string" }, + "adjustIndistinguishableColors": { + "description": "When set to true, we will (when necessary) adjust the foreground color to make it more visible, based on the background color.", + "type": "boolean" + }, "experimental.retroTerminalEffect": { "description": "When set to true, enable retro terminal effects when unfocused. This is an experimental feature, and its continued existence is not guaranteed.", "type": "boolean" @@ -455,7 +459,7 @@ }, "suppressApplicationTitle": { "type": "boolean", - "default": "false", + "default": false, "description": "When set to true, tabTitle overrides the default title of the tab and any title change messages from the application will be suppressed. When set to false, tabTitle behaves as normal" }, "colorScheme": { @@ -502,7 +506,7 @@ "properties": { "action": { "type": "string", - "pattern": "adjustFontSize" + "const": "adjustFontSize" }, "delta": { "type": "integer", @@ -526,7 +530,7 @@ "properties": { "action": { "type": "string", - "pattern": "copy" + "const": "copy" }, "singleLine": { "type": "boolean", @@ -562,7 +566,7 @@ "properties": { "action": { "type": "string", - "pattern": "newTab" + "const": "newTab" } } } @@ -578,7 +582,7 @@ "properties": { "action": { "type": "string", - "pattern": "switchToTab" + "const": "switchToTab" }, "index": { "type": "integer", @@ -602,7 +606,7 @@ "properties": { "action": { "type": "string", - "pattern": "movePane" + "const": "movePane" }, "index": { "type": "integer", @@ -626,7 +630,7 @@ "properties": { "action": { "type": "string", - "pattern": "moveFocus" + "const": "moveFocus" }, "direction": { "$ref": "#/$defs/FocusDirection", @@ -650,7 +654,7 @@ "properties": { "action": { "type": "string", - "pattern": "swapPane" + "const": "swapPane" }, "direction": { "$ref": "#/$defs/FocusDirection", @@ -674,7 +678,7 @@ "properties": { "action": { "type": "string", - "pattern": "resizePane" + "const": "resizePane" }, "direction": { "$ref": "#/$defs/ResizeDirection", @@ -698,7 +702,7 @@ "properties": { "action": { "type": "string", - "pattern": "sendInput" + "const": "sendInput" }, "input": { "type": "string", @@ -725,7 +729,7 @@ "properties": { "action": { "type": "string", - "pattern": "splitPane" + "const": "splitPane" }, "split": { "$ref": "#/$defs/SplitDirection", @@ -757,7 +761,7 @@ "properties": { "action": { "type": "string", - "pattern": "openSettings" + "const": "openSettings" }, "target": { "type": "string", @@ -784,7 +788,7 @@ "properties": { "action": { "type": "string", - "pattern": "setTabColor" + "const": "setTabColor" }, "color": { "$ref": "#/$defs/Color", @@ -805,7 +809,7 @@ "properties": { "action": { "type": "string", - "pattern": "setColorScheme" + "const": "setColorScheme" }, "colorScheme": { "type": "string", @@ -829,7 +833,7 @@ "properties": { "action": { "type": "string", - "pattern": "wt" + "const": "wt" }, "commandline": { "type": "string", @@ -853,7 +857,7 @@ "properties": { "action": { "type": "string", - "pattern": "closeOtherTabs" + "const": "closeOtherTabs" }, "index": { "oneOf": [ @@ -881,7 +885,7 @@ "properties": { "action": { "type": "string", - "pattern": "closeTabsAfter" + "const": "closeTabsAfter" }, "index": { "oneOf": [ @@ -909,7 +913,7 @@ "properties": { "action": { "type": "string", - "pattern": "closeTab" + "const": "closeTab" }, "index": { "oneOf": [ @@ -937,7 +941,7 @@ "properties": { "action": { "type": "string", - "pattern": "scrollUp" + "const": "scrollUp" }, "rowsToScroll": { "type": [ @@ -961,7 +965,7 @@ "properties": { "action": { "type": "string", - "pattern": "scrollDown" + "const": "scrollDown" }, "rowsToScroll": { "type": [ @@ -985,7 +989,7 @@ "properties": { "action": { "type": "string", - "pattern": "moveTab" + "const": "moveTab" }, "direction": { "$ref": "#/$defs/MoveTabDirection", @@ -1008,7 +1012,7 @@ "properties": { "action": { "type": "string", - "pattern": "multipleActions" + "const": "multipleActions" }, "actions": { "$ref": "#/$defs/ShortcutAction", @@ -1033,7 +1037,7 @@ "properties": { "action": { "type": "string", - "pattern": "commandPalette" + "const": "commandPalette" }, "launchMode": { "$ref": "#/$defs/CommandPaletteLaunchMode", @@ -1054,7 +1058,7 @@ "properties": { "action": { "type": "string", - "pattern": "findMatch" + "const": "findMatch" }, "direction": { "$ref": "#/$defs/FindMatchDirection", @@ -1081,7 +1085,7 @@ "properties": { "action": { "type": "string", - "pattern": "newWindow" + "const": "newWindow" } } } @@ -1097,7 +1101,7 @@ "properties": { "action": { "type": "string", - "pattern": "prevTab" + "const": "prevTab" }, "tabSwitcherMode": { "$ref": "#/$defs/SwitchToAdjacentTabArgs", @@ -1118,7 +1122,7 @@ "properties": { "action": { "type": "string", - "pattern": "nextTab" + "const": "nextTab" }, "tabSwitcherMode": { "$ref": "#/$defs/SwitchToAdjacentTabArgs", @@ -1139,7 +1143,7 @@ "properties": { "action": { "type": "string", - "pattern": "renameTab" + "const": "renameTab" }, "title": { "type": "string", @@ -1160,7 +1164,7 @@ "properties": { "action": { "type": "string", - "pattern": "renameWindow" + "const": "renameWindow" }, "name": { "type": "string", @@ -1181,7 +1185,7 @@ "properties": { "action": { "type": "string", - "pattern": "focusPane" + "const": "focusPane" }, "id": { "type": "integer", @@ -1203,7 +1207,7 @@ "properties": { "action": { "type": "string", - "pattern": "globalSummon" + "const": "globalSummon" }, "desktop": { "type": "string", @@ -1254,7 +1258,7 @@ "properties": { "action": { "type": "string", - "pattern": "quakeMode" + "const": "quakeMode" } } } @@ -1329,6 +1333,12 @@ { "$ref": "#/$defs/MoveTabAction" }, + { + "$ref": "#/$defs/MultipleActionsAction" + }, + { + "$ref": "#/$defs/CommandPaletteAction" + }, { "$ref": "#/$defs/FindMatchAction" }, @@ -1468,6 +1478,11 @@ "description": "When set to true, trailing white-spaces will be removed from text in rectangular (block) selection while copied to your clipboard. When set to false, the white-spaces will be preserved.", "type": "boolean" }, + "trimPaste": { + "default": true, + "description": "When enabled, the Terminal will automatically trim trailing whitespace characters when pasting text", + "type": "boolean" + }, "experimental.detectURLs": { "default": true, "description": "When set to true, URLs will be detected by the Terminal. This will cause URLs to underline on hover and be clickable by pressing Ctrl.", @@ -1571,22 +1586,22 @@ "deprecated": true }, "minimizeToNotificationArea": { - "default": "false", + "default": false, "description": "When set to true, minimizing a Terminal window will no longer appear in the taskbar. Instead, a Terminal icon will appear in the notification area through which the user can access their windows.", "type": "boolean" }, "alwaysShowNotificationIcon": { - "default": "false", + "default": false, "description": "When set to true, the Terminal's notification icon will always be shown in the notification area.", "type": "boolean" }, "showAdminShield": { - "default": "true", + "default": true, "description": "When set to true, the Terminal's tab row will display a shield icon when the Terminal is running with administrator privileges", "type": "boolean" }, "useAcrylicInTabRow": { - "default": "false", + "default": false, "description": "When set to true, the tab row will have an acrylic background with 50% opacity.", "type": "boolean" }, @@ -1973,6 +1988,10 @@ } ] }, + "adjustIndistinguishableColors": { + "description": "When set to true, we will (when necessary) adjust the foreground color to make it more visible, based on the background color.", + "type": "boolean" + }, "scrollbarState": { "default": "visible", "description": "Defines the visibility of the scrollbar.", diff --git a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md index 37b1fe4cb..c7dbefeab 100644 --- a/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md +++ b/doc/specs/#885 - Terminal Settings Model/#885 - Terminal Settings Model.md @@ -268,7 +268,7 @@ Today, if the deserialization of `CascadiaSettings` encounters any errors, an ex To get around this issue, when `CascadiaSettings` encounters a serialization error, it must internally record any pertinent information for that error, and return the simple `CascadiaSettings` as if nothing happened. The consumer must then call `CascadiaSettings::GetErrors()` and `CascadiaSettings::GetWarnings()` to properly - understand whether an error ocurred and how to present that to the user. + understand whether an error occurred and how to present that to the user. #### TerminalApp: Loading and Reloading Changes diff --git a/doc/terminal-v2-roadmap.md b/doc/terminal-v2-roadmap.md index 8f4cab235..9db0aa652 100644 --- a/doc/terminal-v2-roadmap.md +++ b/doc/terminal-v2-roadmap.md @@ -2,7 +2,7 @@ ## Overview -This document outlines the roadmap towards delivering Windows Terminal 2.0 by Winter 2021. +This document outlines the roadmap towards delivering Windows Terminal 2.0. ## Milestones @@ -31,9 +31,9 @@ Below is the schedule for when milestones will be included in release builds of | 2021-05-31 | [1.9] in Windows Terminal Preview
[1.8] in Windows Terminal | [Windows Terminal Preview 1.9 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-9-release/) | | 2021-07-14 | [1.10] in Windows Terminal Preview
[1.9] in Windows Terminal | [Windows Terminal Preview 1.10 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-10-release/) | | 2021-08-31 | [1.11] in Windows Terminal Preview
[1.10] in Windows Terminal | [Windows Terminal Preview 1.11 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-11-release/) | -| 2021-10-31 | 1.12 in Windows Terminal Preview
1.11 in Windows Terminal | | -| 2021-11-30 | 2.0 RC in Windows Terminal Preview
2.0 RC in Windows Terminal | | -| 2021-12-31 | [2.0] in Windows Terminal Preview
[2.0] in Windows Terminal | | +| 2021-10-20 | [1.12] in Windows Terminal Preview
[1.11] in Windows Terminal | [Windows Terminal Preview 1.12 Release](https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-12-release/) | +| | 2.0 RC in Windows Terminal Preview
2.0 RC in Windows Terminal | | +| | [2.0] in Windows Terminal Preview
[2.0] in Windows Terminal | | ## Issue Triage & Prioritization @@ -49,28 +49,32 @@ The following are a list of the key scenarios we're aiming to deliver for Termin > πŸ‘‰ Note: There are many other features that don't fit within 2.0, but will be re-assessed and prioritized for 3.0, the plan for which will be published in 2021. -| Priority\* | Scenario | Description/Notes | -| ---------- | -------- | ----------------- | -| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.

Issue: [#1564]
Specs: [#6720], [#6904]
Implementation: [#7283], [#7370], [#8048] | -| 0 | Command palette | A popup menu to list possible actions and commands.

Issues: [#5400], [#2046]
Spec: [#2193]
Implementation: [#6635] | -| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.

Issue: [#1256], [#5000]
Spec: [#2080], [#7240] | -| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.

Issue: [#574]
Implementation: [#7251] | -| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal

Issue: [#492]
Spec: [#2080], [#7414] | -| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme

Issue: [#3327]
Spec: [#5772] | -| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)

Issue: [#5000], [#632]
Spec: [#8455] | -| 1 | Open tab in existing window | Open new tabs in existing Terminal windows

Issue: [#5000], [#4472]
Spec: [#8135] | -| 1 | Traditional opacity | Have a transparent background without the acrylic blur.

Issue: [#603]
**Current State**: Blocked on WinUI 3.0 | -| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.

Issue: [#980]
Spec: [#2529]
Implementation: [#6062] | -| 2 | Infinite scrollback | Have an infinite history for the text buffer.

Issue: [#1410] | -| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.

Issue: [#1000] | -| 2 | Theme marketplace | Marketplace for creation and distribution of themes.
Dependent on overall theming | -| 2 | Jump list | Show profiles from task bar (on right click)/start menu.

Issue: [#576]
Implementation: [#7515] | -| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).

Issue: [#756] | -| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.

Issue: [#1060]
Implementation: [#6100] | -| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.

Issues: [#961], [#960], [#766] | -| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.

Issue: [#653] | -| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. | -| 3 | Pointer bindings | Provide settings that can be bound to the mouse.

Issue: [#1553] | +| Priority\* | Scenario | Description/Notes | State | +| ---------- | -------- | ----------------- | ----- | +| 0 | Settings UI | A user interface that connects to settings.json. This provides a way for people to edit their settings without having to edit a JSON file.

Issue: [#1564]
Specs: [#6720], [#6904]
Implementation: [#7283], [#7370], [#8048] | βœ”οΈ | +| 0 | Command palette | A popup menu to list possible actions and commands.

Issues: [#5400], [#2046]
Spec: [#2193]
Implementation: [#6635] | βœ”οΈ | +| 1 | Tab tear-off | The ability to tear a tab out of the current window and spawn a new window or attach it to a separate window.

Issue: [#1256], [#5000]
Spec: [#2080], [#7240] | πŸ“ | +| 1 | Clickable links | Hyperlinking any links that appear in the text buffer. When clicking on the link, the link will open in your default browser.

Issue: [#574]
Implementation: [#7251] | βœ”οΈ | +| 1 | Default terminal | If a command-line application is spawned, it should open in Windows Terminal (if installed) or your preferred terminal

Issue: [#492]
Spec: [#2080], [#7414] | βœ”οΈ | +| 1 | Overall theme support | Tab coloring, title bar coloring, pane border coloring, pane border width, definition of what makes a theme

Issue: [#3327]
Spec: [#5772] | 🦢 | +| 1 | Open profile elevated | Configure profiles to always open elevated (if Terminal was run unelevated)

Issue: [#5000], [#632]
Spec: [#8455] | πŸ“ | +| 1 | Open tab in existing window | Open new tabs in existing Terminal windows

Issue: [#5000], [#4472]
Spec: [#8135] | βœ”οΈ | +| 1 | Traditional opacity | Have a transparent background without the acrylic blur.

Issue: [#603] | βœ”οΈ | +| 2 | SnapOnOutput, scroll lock | Pause output or scrolling on click.

Issue: [#980]
Spec: [#2529]
Implementation: [#6062] | βœ”οΈ | +| 2 | Infinite scrollback | Have an infinite history for the text buffer.

Issue: [#1410] | 🦢 | +| 2 | Pane management | All issues listed out in the original issue. Some features include pane resizing with mouse, pane zooming, and opening a pane by prompting which profile to use.

Issue: [#1000] | πŸ“ | +| 2 | Theme marketplace | Marketplace for creation and distribution of themes.
Dependent on overall theming | 🦢 | +| 2 | Jump list | Show profiles from task bar (on right click)/start menu.

Issue: [#576]
Implementation: [#7515] | βœ”οΈ | +| 2 | Open with multiple tabs | A setting that allows Windows Terminal to launch with a specific tab configuration (not using only command line arguments).

Issue: [#756] | βœ”οΈ | +| 3 | Open in Windows Terminal | Functionality to right click on a file or folder and select Open in Windows Terminal.

Issue: [#1060]
Implementation: [#6100] | βœ”οΈ | +| 3 | Session restoration | Launch Windows Terminal and the previous session is restored with the proper tab and pane configuration and starting directories.

Issues: [#961], [#960], [#766] | βœ”οΈ | +| 3 | Quake mode | Provide a quick launch terminal that appears and disappears when a hotkey is pressed.

Issue: [#653] | βœ”οΈ | +| 3 | Settings migration infrastructure | Migrate people's settings without breaking them. Hand-in-hand with settings UI. | 🦢 | +| 3 | Pointer bindings | Provide settings that can be bound to the mouse.

Issue: [#1553] | 🦢 | + +* πŸ“: The feature is currently in progress +* βœ”οΈ: The feature is complete and shipped in a Preview build +* 🦢: The feature is at risk of being punted to a future release cycle (beyond 2.0) Feature Notes: @@ -91,6 +95,8 @@ Feature Notes: [1.9]: https://github.com/microsoft/terminal/milestone/34 [1.10]: https://github.com/microsoft/terminal/milestone/35 [1.11]: https://github.com/microsoft/terminal/milestone/36 +[1.12]: https://github.com/microsoft/terminal/milestone/38 +[1.13]: https://github.com/microsoft/terminal/milestone/39 [2.0]: https://github.com/microsoft/terminal/milestone/22 [#1564]: https://github.com/microsoft/terminal/issues/1564 [#6720]: https://github.com/microsoft/terminal/pull/6720 diff --git a/oss/pcg/LICENSE-APACHE.txt b/oss/pcg/LICENSE-APACHE.txt new file mode 100644 index 000000000..c0ee81299 --- /dev/null +++ b/oss/pcg/LICENSE-APACHE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/oss/pcg/LICENSE-MIT.txt b/oss/pcg/LICENSE-MIT.txt new file mode 100644 index 000000000..51428f73c --- /dev/null +++ b/oss/pcg/LICENSE-MIT.txt @@ -0,0 +1,19 @@ +Copyright (c) 2014-2017 Melissa O'Neill and PCG Project contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/oss/pcg/cgmanifest.json b/oss/pcg/cgmanifest.json new file mode 100644 index 000000000..ef64ef4d6 --- /dev/null +++ b/oss/pcg/cgmanifest.json @@ -0,0 +1,14 @@ +{ + "Registrations": [ + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/imneme/pcg-cpp", + "commitHash": "ffd522e7188bef30a00c74dc7eb9de5faff90092" + } + } + } + ], + "Version": 1 +} diff --git a/oss/pcg/include/pcg_random.hpp b/oss/pcg/include/pcg_random.hpp new file mode 100644 index 000000000..bd85f4b0d --- /dev/null +++ b/oss/pcg/include/pcg_random.hpp @@ -0,0 +1,82 @@ +// PCG Random Number Generation for C++ +// +// Copyright 2014-2019 Melissa O'Neill , +// and the PCG Project contributors. +// +// SPDX-License-Identifier: (Apache-2.0 OR MIT) +// +// Licensed under the Apache License, Version 2.0 (provided in +// LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) +// or under the MIT license (provided in LICENSE-MIT.txt and at +// http://opensource.org/licenses/MIT), at your option. This file may not +// be copied, modified, or distributed except according to those terms. +// +// Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either +// express or implied. See your chosen license for details. +// +// For additional information about the PCG random number generation scheme, +// visit http://www.pcg-random.org/. +// +// ----------------------------------------------------------------------------- +// +// Leonard Hecker : +// The following contents are an extract of pcg_engines::oneseq_dxsm_64_32 +// reduced down to the bare essentials, while retaining base functionality. + +namespace pcg_engines { + class oneseq_dxsm_64_32 { + using xtype = uint32_t; + using itype = uint64_t; + + itype state_; + + static constexpr uint64_t multiplier() { + return 6364136223846793005ULL; + } + + static constexpr uint64_t increment() { + return 1442695040888963407ULL; + } + + static itype bump(itype state) { + return state * multiplier() + increment(); + } + + itype base_generate0() { + itype old_state = state_; + state_ = bump(state_); + return old_state; + } + + public: + explicit oneseq_dxsm_64_32(itype state = 0xcafef00dd15ea5e5ULL) : state_(bump(state + increment())) { + } + + // Returns a value in the interval [0, UINT32_MAX]. + xtype operator()() { + constexpr auto xtypebits = uint8_t(sizeof(xtype) * 8); + constexpr auto itypebits = uint8_t(sizeof(itype) * 8); + + auto internal = base_generate0(); + auto hi = xtype(internal >> (itypebits - xtypebits)); + auto lo = xtype(internal); + + lo |= 1; + hi ^= hi >> (xtypebits / 2); + hi *= xtype(multiplier()); + hi ^= hi >> (3 * (xtypebits / 4)); + hi *= lo; + return hi; + } + + // Returns a value in the interval [0, upper_bound). + xtype operator()(xtype upper_bound) { + uint32_t threshold = (UINT64_MAX + uint32_t(1) - upper_bound) % upper_bound; + for (;;) { + auto r = operator()(); + if (r >= threshold) + return r % upper_bound; + } + } + }; +} diff --git a/src/Terminal.wprp b/src/Terminal.wprp index 80a6e730b..129d83ca0 100644 --- a/src/Terminal.wprp +++ b/src/Terminal.wprp @@ -13,6 +13,15 @@ + + + + + + + + + @@ -32,5 +41,27 @@ + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index c44724846..b99c3121d 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -12,8 +12,30 @@ static_assert(alignof(TextAttribute) == 2); // Ensure that we can memcpy() and memmove() the struct for performance. static_assert(std::is_trivially_copyable_v); -BYTE TextAttribute::s_legacyDefaultForeground = 7; -BYTE TextAttribute::s_legacyDefaultBackground = 0; +namespace +{ + constexpr std::array s_initLegacyColorMap(const BYTE defaultIndex) + { + std::array legacyColorMap; + for (auto i = 0u; i < legacyColorMap.size(); i++) + { + const auto legacyIndex = TextColor::TransposeLegacyIndex(i); + gsl::at(legacyColorMap, i) = i == defaultIndex ? TextColor{} : TextColor{ legacyIndex, true }; + } + return legacyColorMap; + } + + BYTE s_legacyDefaultForeground = 7; + BYTE s_legacyDefaultBackground = 0; + BYTE s_ansiDefaultForeground = 7; + BYTE s_ansiDefaultBackground = 0; +} + +// These maps allow for an efficient conversion from a legacy attribute index +// to a TextColor with the corresponding ANSI index, also taking into account +// the legacy index values that need to be converted to a default TextColor. +std::array TextAttribute::s_legacyForegroundColorMap = s_initLegacyColorMap(7); +std::array TextAttribute::s_legacyBackgroundColorMap = s_initLegacyColorMap(0); // Routine Description: // - Sets the legacy attributes which map to and from the default colors. @@ -23,8 +45,22 @@ BYTE TextAttribute::s_legacyDefaultBackground = 0; // - None void TextAttribute::SetLegacyDefaultAttributes(const WORD defaultAttributes) noexcept { + // First we reset the current default color map entries to what they should + // be for a regular translation from a legacy index to an ANSI TextColor. + gsl::at(s_legacyForegroundColorMap, s_legacyDefaultForeground) = TextColor{ s_ansiDefaultForeground, true }; + gsl::at(s_legacyBackgroundColorMap, s_legacyDefaultBackground) = TextColor{ s_ansiDefaultBackground, true }; + + // Then we save the new default attribute values and their corresponding + // ANSI translations. We use the latter values to more efficiently handle + // the "VT Quirk" conversion below. s_legacyDefaultForeground = defaultAttributes & FG_ATTRS; s_legacyDefaultBackground = (defaultAttributes & BG_ATTRS) >> 4; + s_ansiDefaultForeground = TextColor::TransposeLegacyIndex(s_legacyDefaultForeground); + s_ansiDefaultBackground = TextColor::TransposeLegacyIndex(s_legacyDefaultBackground); + + // Finally we set the new default color map entries. + gsl::at(s_legacyForegroundColorMap, s_legacyDefaultForeground) = TextColor{}; + gsl::at(s_legacyBackgroundColorMap, s_legacyDefaultBackground) = TextColor{}; } // Routine Description: @@ -55,13 +91,13 @@ TextAttribute TextAttribute::StripErroneousVT16VersionsOfLegacyDefaults(const Te 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)) + attribute.IsBold() == WI_IsFlagSet(s_ansiDefaultForeground, FOREGROUND_INTENSITY) && + fg.GetIndex() == (s_ansiDefaultForeground & ~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) + if (bg.IsIndex16() && bg.GetIndex() == s_ansiDefaultBackground) { copy.SetDefaultBackground(); } diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 00f8a2266..e973a9cd3 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -41,8 +41,8 @@ public: explicit constexpr TextAttribute(const WORD wLegacyAttr) noexcept : _wAttrLegacy{ gsl::narrow_cast(wLegacyAttr & META_ATTRS) }, - _foreground{ s_LegacyIndexOrDefault(wLegacyAttr & FG_ATTRS, s_legacyDefaultForeground) }, - _background{ s_LegacyIndexOrDefault((wLegacyAttr & BG_ATTRS) >> 4, s_legacyDefaultBackground) }, + _foreground{ gsl::at(s_legacyForegroundColorMap, wLegacyAttr & FG_ATTRS) }, + _background{ gsl::at(s_legacyBackgroundColorMap, (wLegacyAttr & BG_ATTRS) >> 4) }, _extendedAttrs{ ExtendedAttributes::Normal }, _hyperlinkId{ 0 } { @@ -167,13 +167,8 @@ public: } private: - 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; + static std::array s_legacyForegroundColorMap; + static std::array s_legacyBackgroundColorMap; uint16_t _wAttrLegacy; // sizeof: 2, alignof: 2 uint16_t _hyperlinkId; // sizeof: 2, alignof: 2 diff --git a/src/buffer/out/TextColor.cpp b/src/buffer/out/TextColor.cpp index dcd0a8df3..88bc8657f 100644 --- a/src/buffer/out/TextColor.cpp +++ b/src/buffer/out/TextColor.cpp @@ -4,6 +4,8 @@ #include "precomp.h" #include "TextColor.h" +#include + // clang-format off // A table mapping 8-bit RGB colors, in the form RRRGGGBB, @@ -30,7 +32,7 @@ constexpr std::array CompressedRgbToIndex16 = { // A table mapping indexed colors from the 256-color palette, // down to one of the 16 colors in the legacy palette. constexpr std::array Index256ToIndex16 = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15, 0, 1, 1, 1, 9, 9, 2, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 11, 11, 3, 3, 10, 10, 11, 11, 11, 11, 10, 10, 10, 10, 11, 11, 5, 5, 5, 5, 1, 1, 8, 8, 1, 1, 9, 9, @@ -186,7 +188,7 @@ COLORREF TextColor::GetColor(const std::array& colorTable, const // the result will be something like 0b00100000. // 5. Use BitScanForward (bsf) to find the index of the most significant 1 bit. const auto haystack = _mm256_loadu_si256(reinterpret_cast(colorTable.data())); // 1. - const auto needle = _mm256_set1_epi32(__builtin_bit_cast(int, defaultColor)); // 2. + const auto needle = _mm256_set1_epi32(til::bit_cast(defaultColor)); // 2. const auto result = _mm256_cmpeq_epi32(haystack, needle); // 3. const auto mask = _mm256_movemask_ps(_mm256_castsi256_ps(result)); // 4. unsigned long index; @@ -203,7 +205,7 @@ COLORREF TextColor::GetColor(const std::array& colorTable, const // --> the index returned by _BitScanForward must be divided by 2. const auto haystack1 = _mm_loadu_si128(reinterpret_cast(colorTable.data() + 0)); const auto haystack2 = _mm_loadu_si128(reinterpret_cast(colorTable.data() + 4)); - const auto needle = _mm_set1_epi32(__builtin_bit_cast(int, defaultColor)); + const auto needle = _mm_set1_epi32(til::bit_cast(defaultColor)); const auto result1 = _mm_cmpeq_epi32(haystack1, needle); const auto result2 = _mm_cmpeq_epi32(haystack2, needle); const auto result = _mm_packs_epi32(result1, result2); // 3.5 @@ -250,11 +252,7 @@ BYTE TextColor::GetLegacyIndex(const BYTE defaultIndex) const noexcept { return defaultIndex; } - else if (IsIndex16()) - { - return GetIndex(); - } - else if (IsIndex256()) + else if (IsIndex16() || IsIndex256()) { return til::at(Index256ToIndex16, GetIndex()); } diff --git a/src/buffer/out/TextColor.h b/src/buffer/out/TextColor.h index ad984702f..35b62ff65 100644 --- a/src/buffer/out/TextColor.h +++ b/src/buffer/out/TextColor.h @@ -48,6 +48,23 @@ enum class ColorType : BYTE struct TextColor { public: + static constexpr BYTE DARK_BLACK = 0; + static constexpr BYTE DARK_RED = 1; + static constexpr BYTE DARK_GREEN = 2; + static constexpr BYTE DARK_YELLOW = 3; + static constexpr BYTE DARK_BLUE = 4; + static constexpr BYTE DARK_MAGENTA = 5; + static constexpr BYTE DARK_CYAN = 6; + static constexpr BYTE DARK_WHITE = 7; + static constexpr BYTE BRIGHT_BLACK = 8; + static constexpr BYTE BRIGHT_RED = 9; + static constexpr BYTE BRIGHT_GREEN = 10; + static constexpr BYTE BRIGHT_YELLOW = 11; + static constexpr BYTE BRIGHT_BLUE = 12; + static constexpr BYTE BRIGHT_MAGENTA = 13; + static constexpr BYTE BRIGHT_CYAN = 14; + static constexpr BYTE BRIGHT_WHITE = 15; + constexpr TextColor() noexcept : _meta{ ColorType::IsDefault }, _red{ 0 }, @@ -96,6 +113,16 @@ public: COLORREF GetRGB() const noexcept; + static constexpr BYTE TransposeLegacyIndex(const size_t index) + { + // When converting a 16-color index in the legacy Windows order to or + // from an ANSI-compatible order, we need to swap the bits in positions + // 0 and 2. We do this by XORing the index with 00000101, but only if + // one (but not both) of those bit positions is set. + const auto oneBitSet = (index ^ (index >> 2)) & 1; + return gsl::narrow_cast(index ^ oneBitSet ^ (oneBitSet << 2)); + } + private: union { diff --git a/src/buffer/out/ut_textbuffer/TextAttributeTests.cpp b/src/buffer/out/ut_textbuffer/TextAttributeTests.cpp index 65aba47f7..1b61955bb 100644 --- a/src/buffer/out/ut_textbuffer/TextAttributeTests.cpp +++ b/src/buffer/out/ut_textbuffer/TextAttributeTests.cpp @@ -237,7 +237,7 @@ void TextAttributeTests::TestRoundtripDefaultColors() Log::Comment(L"Foreground legacy default index should map to default text color."); legacyAttribute = fgLegacyDefault | BACKGROUND_GREEN; textAttribute.SetDefaultForeground(); - textAttribute.SetIndexedBackground256(BACKGROUND_GREEN >> 4); + textAttribute.SetIndexedBackground256(TextColor::DARK_GREEN); VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute }); Log::Comment(L"Default foreground text color should map back to legacy default index."); @@ -245,7 +245,7 @@ void TextAttributeTests::TestRoundtripDefaultColors() Log::Comment(L"Background legacy default index should map to default text color."); legacyAttribute = FOREGROUND_GREEN | bgLegacyDefault; - textAttribute.SetIndexedForeground256(FOREGROUND_GREEN); + textAttribute.SetIndexedForeground256(TextColor::DARK_GREEN); textAttribute.SetDefaultBackground(); VERIFY_ARE_EQUAL(textAttribute, TextAttribute{ legacyAttribute }); @@ -288,7 +288,7 @@ void TextAttributeTests::TestBoldAsBright() VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true)); VERIFY_ARE_EQUAL(std::make_pair(_defaultFg, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false)); - attr.SetIndexedForeground(0); + attr.SetIndexedForeground(TextColor::DARK_BLACK); VERIFY_IS_TRUE(attr.IsBold()); Log::Comment(L"Foreground should be bright black when bold is bright is enabled"); @@ -297,7 +297,7 @@ void TextAttributeTests::TestBoldAsBright() Log::Comment(L"Foreground should be dark black when bold is bright is disabled"); VERIFY_ARE_EQUAL(std::make_pair(darkBlack, _defaultBg), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false)); - attr.SetIndexedBackground(2); + attr.SetIndexedBackground(TextColor::DARK_GREEN); VERIFY_IS_TRUE(attr.IsBold()); Log::Comment(L"background should be unaffected by 'bold is bright'"); @@ -312,7 +312,7 @@ void TextAttributeTests::TestBoldAsBright() Log::Comment(L"When set to a bright color, and bold, 'bold is bright' changes nothing"); attr.SetBold(true); - attr.SetIndexedForeground(8); + attr.SetIndexedForeground(TextColor::BRIGHT_BLACK); VERIFY_IS_TRUE(attr.IsBold()); VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, true)); VERIFY_ARE_EQUAL(std::make_pair(brightBlack, darkGreen), attr.CalculateRgbColors(_colorTable, _defaultFg, _defaultBg, false, false, false)); diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index db9cfe825..b73a6453b 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -74,7 +74,7 @@ Enabled="false" DisplayName="ms-resource:AppName" /> - + - + diff --git a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp b/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp index 85a9dd34a..682bee5f2 100644 --- a/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ColorSchemeTests.cpp @@ -80,7 +80,7 @@ namespace SettingsModelLocalTests std::array expectedCampbellTable; const auto campbellSpan = gsl::make_span(expectedCampbellTable); - Utils::InitializeCampbellColorTable(campbellSpan); + Utils::InitializeColorTable(campbellSpan); Utils::SetColorTableAlpha(campbellSpan, 0); for (size_t i = 0; i < expectedCampbellTable.size(); i++) diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index 7f6638954..c20a9a853 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -7,7 +7,9 @@ #include "../TerminalSettingsModel/CascadiaSettings.h" #include "JsonTestClass.h" #include "TestUtils.h" + #include +#include using namespace Microsoft::Console; using namespace WEX::Logging; @@ -70,6 +72,7 @@ namespace SettingsModelLocalTests TEST_METHOD(TestCloneInheritanceTree); TEST_METHOD(TestValidDefaults); TEST_METHOD(TestInheritedCommand); + TEST_METHOD(LoadFragmentsWithMultipleUpdates); private: static winrt::com_ptr createSettings(const std::string_view& userJSON) @@ -1092,7 +1095,7 @@ namespace SettingsModelLocalTests }, { "name": "profile1", - "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "source": "Terminal.App.UnitTest.1", "historySize": 2222 }, @@ -1979,4 +1982,34 @@ namespace SettingsModelLocalTests VERIFY_IS_NULL(actualKeyChord); } } + + // This test ensures GH#11597 doesn't regress. + void DeserializationTests::LoadFragmentsWithMultipleUpdates() + { + static constexpr std::wstring_view fragmentSource{ L"fragment" }; + static constexpr std::string_view fragmentJson{ R"({ + "profiles": [ + { + "updates": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", + "cursorShape": "filledBox" + }, + { + "updates": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", + "cursorShape": "filledBox" + }, + { + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe" + } + ] + })" }; + + implementation::SettingsLoader loader{ std::string_view{}, DefaultJson }; + loader.MergeInboxIntoUserSettings(); + loader.MergeFragmentIntoUserSettings(winrt::hstring{ fragmentSource }, fragmentJson); + loader.FinalizeLayering(); + + VERIFY_IS_FALSE(loader.duplicateProfile); + VERIFY_ARE_EQUAL(3u, loader.userSettings.profiles.size()); + } } diff --git a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp index 54c405592..2c5138f6d 100644 --- a/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ProfileTests.cpp @@ -272,20 +272,30 @@ namespace SettingsModelLocalTests void ProfileTests::DuplicateProfileTest() { static constexpr std::string_view userProfiles{ R"({ - "profiles": [ - { - "name": "profile0", - "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", - "backgroundImage": "file:///some/path", - "hidden": false, - } - ] + "profiles": { + "defaults": { + "font": { + "size": 123 + } + }, + "list": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "backgroundImage": "file:///some/path", + "hidden": false, + } + ] + } })" }; const auto settings = winrt::make_self(userProfiles); const auto profile = settings->AllProfiles().GetAt(0); const auto duplicatedProfile = settings->DuplicateProfile(profile); + // GH#11392: Ensure duplicated profiles properly inherit the base layer, even for nested objects. + VERIFY_ARE_EQUAL(123, duplicatedProfile.FontInfo().FontSize()); + duplicatedProfile.Guid(profile.Guid()); duplicatedProfile.Name(profile.Name()); @@ -300,6 +310,17 @@ namespace SettingsModelLocalTests // the GUID generated for a dynamic profile (with a source) is different // than that of a profile without a source. + static constexpr std::string_view inboxSettings{ R"({ + "profiles": [ + { + "name" : "profile0", + "source": "Terminal.App.UnitTest.0" + }, + { + "name" : "profile1" + } + ] + })" }; static constexpr std::string_view userSettings{ R"({ "profiles": [ { @@ -312,9 +333,9 @@ namespace SettingsModelLocalTests ] })" }; - const auto settings = winrt::make_self(userSettings, DefaultJson); + const auto settings = winrt::make_self(userSettings, inboxSettings); - VERIFY_ARE_EQUAL(4u, settings->AllProfiles().Size()); + VERIFY_ARE_EQUAL(3u, settings->AllProfiles().Size()); VERIFY_ARE_EQUAL(L"profile0", settings->AllProfiles().GetAt(0).Name()); VERIFY_IS_TRUE(settings->AllProfiles().GetAt(0).HasGuid()); diff --git a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp index e538967f9..ff2aefdb6 100644 --- a/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/SerializationTests.cpp @@ -53,7 +53,7 @@ namespace SettingsModelLocalTests // Return Value: // - the JsonObject representing this instance template - void RoundtripTest(const std::string& jsonString) + void RoundtripTest(const std::string_view& jsonString) { const auto json{ VerifyParseSucceeded(jsonString) }; const auto settings{ T::FromJson(json) }; @@ -69,7 +69,7 @@ namespace SettingsModelLocalTests void SerializationTests::GlobalSettings() { - const std::string globalsString{ R"( + static constexpr std::string_view globalsString{ R"( { "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", @@ -97,6 +97,7 @@ namespace SettingsModelLocalTests "confirmCloseAllTabs": true, "largePasteWarning": true, "multiLinePasteWarning": true, + "trimPaste": true, "experimental.input.forceVT": false, "experimental.rendering.forceFullRepaint": false, @@ -105,7 +106,7 @@ namespace SettingsModelLocalTests "actions": [] })" }; - const std::string smallGlobalsString{ R"( + static constexpr std::string_view smallGlobalsString{ R"( { "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", "actions": [] @@ -117,7 +118,7 @@ namespace SettingsModelLocalTests void SerializationTests::Profile() { - const std::string profileString{ R"( + static constexpr std::string_view profileString{ R"( { "name": "Windows PowerShell", "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", @@ -152,7 +153,7 @@ namespace SettingsModelLocalTests "selectionBackground": "#CCAABB", "useAcrylic": false, - "acrylicOpacity": 0.5, + "opacity": 50, "backgroundImage": "made_you_look.jpeg", "backgroundImageStretchMode": "uniformToFill", @@ -167,7 +168,7 @@ namespace SettingsModelLocalTests "experimental.retroTerminalEffect": false })" }; - const std::string smallProfileString{ R"( + static constexpr std::string_view smallProfileString{ R"( { "name": "Custom Profile" })" }; @@ -175,7 +176,7 @@ namespace SettingsModelLocalTests // Setting "tabColor" to null tests two things: // - null should count as an explicit user-set value, not falling back to the parent's value // - null should be acceptable even though we're working with colors - const std::string weirdProfileString{ R"( + static constexpr std::string_view weirdProfileString{ R"( { "guid" : "{8b039d4d-77ca-5a83-88e1-dfc8e895a127}", "name": "Weird Profile", @@ -192,7 +193,7 @@ namespace SettingsModelLocalTests void SerializationTests::ColorScheme() { - const std::string schemeString{ R"({ + static constexpr std::string_view schemeString{ R"({ "name": "Campbell", "cursorColor": "#FFFFFF", @@ -225,56 +226,56 @@ namespace SettingsModelLocalTests void SerializationTests::Actions() { // simple command - const std::string actionsString1{ R"([ + static constexpr std::string_view actionsString1{ R"([ { "command": "paste" } ])" }; // complex command - const std::string actionsString2A{ R"([ + static constexpr std::string_view actionsString2A{ R"([ { "command": { "action": "setTabColor" } } ])" }; - const std::string actionsString2B{ R"([ + static constexpr std::string_view actionsString2B{ R"([ { "command": { "action": "setTabColor", "color": "#112233" } } ])" }; - const std::string actionsString2C{ R"([ + static constexpr std::string_view actionsString2C{ R"([ { "command": { "action": "copy" } }, { "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } } ])" }; // simple command with key chords - const std::string actionsString3{ R"([ + static constexpr std::string_view actionsString3{ R"([ { "command": "toggleAlwaysOnTop", "keys": "ctrl+a" }, { "command": "toggleAlwaysOnTop", "keys": "ctrl+b" } ])" }; // complex command with key chords - const std::string actionsString4A{ R"([ + static constexpr std::string_view actionsString4A{ R"([ { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" }, { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" } ])" }; - const std::string actionsString4B{ R"([ + static constexpr std::string_view actionsString4B{ R"([ { "command": { "action": "findMatch", "direction": "next" }, "keys": "ctrl+shift+s" }, { "command": { "action": "findMatch", "direction": "prev" }, "keys": "ctrl+shift+r" } ])" }; // command with name and icon and multiple key chords - const std::string actionsString5{ R"([ + static constexpr std::string_view actionsString5{ R"([ { "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" }, { "command": "scrollToTop", "keys": "ctrl+f" } ])" }; // complex command with new terminal args - const std::string actionsString6{ R"([ + static constexpr std::string_view actionsString6{ R"([ { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" }, ])" }; // complex command with meaningful null arg - const std::string actionsString7{ R"([ + static constexpr std::string_view actionsString7{ R"([ { "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" } ])" }; // nested command - const std::string actionsString8{ R"([ + static constexpr std::string_view actionsString8{ R"([ { "name": "Change font size...", "commands": [ @@ -286,7 +287,7 @@ namespace SettingsModelLocalTests ])" }; // iterable command - const std::string actionsString9A{ R"([ + static constexpr std::string_view actionsString9A{ R"([ { "name": "New tab", "commands": [ @@ -299,7 +300,7 @@ namespace SettingsModelLocalTests ] } ])" }; - const std::string actionsString9B{ R"([ + static constexpr std::string_view actionsString9B{ R"([ { "commands": [ @@ -315,7 +316,7 @@ namespace SettingsModelLocalTests "name": "Send Input ..." } ])" }; - const std::string actionsString9C{ R""([ + static constexpr std::string_view actionsString9C{ R""([ { "commands": [ @@ -338,7 +339,7 @@ namespace SettingsModelLocalTests "name": "Send Input (Evil) ..." } ])"" }; - const std::string actionsString9D{ R""([ + static constexpr std::string_view actionsString9D{ R""([ { "command": { @@ -352,7 +353,7 @@ namespace SettingsModelLocalTests ])"" }; // unbound command - const std::string actionsString10{ R"([ + static constexpr std::string_view actionsString10{ R"([ { "command": "unbound", "keys": "ctrl+c" } ])" }; @@ -395,7 +396,7 @@ namespace SettingsModelLocalTests void SerializationTests::CascadiaSettings() { - const std::string settingsString{ R"({ + static constexpr std::string_view settingsString{ R"({ "$help" : "https://aka.ms/terminal-documentation", "$schema" : "https://aka.ms/terminal-profiles-schema", "defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}", @@ -465,7 +466,7 @@ namespace SettingsModelLocalTests void SerializationTests::LegacyFontSettings() { - const std::string profileString{ R"( + static constexpr std::string_view profileString{ R"( { "name": "Profile with legacy font settings", @@ -474,7 +475,7 @@ namespace SettingsModelLocalTests "fontWeight": "normal" })" }; - const std::string expectedOutput{ R"( + static constexpr std::string_view expectedOutput{ R"( { "name": "Profile with legacy font settings", diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index 0fa507a9d..d830a65ef 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -3,6 +3,8 @@ #include "pch.h" +#include + #include "../TerminalSettingsModel/CascadiaSettings.h" #include "../TerminalSettingsModel/TerminalSettings.h" #include "TestUtils.h" @@ -34,14 +36,12 @@ namespace SettingsModelLocalTests END_TEST_CLASS() TEST_METHOD(TryCreateWinRTType); - TEST_METHOD(TestTerminalArgsForBinding); - + TEST_METHOD(CommandLineToArgvW); + TEST_METHOD(GetProfileForArgsWithCommandline); TEST_METHOD(MakeSettingsForProfile); TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist); - TEST_METHOD(TestLayerProfileOnColorScheme); - TEST_METHOD(TestCommandlineToTitlePromotion); TEST_CLASS_SETUP(ClassSetup) @@ -60,6 +60,139 @@ namespace SettingsModelLocalTests VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); } + // CascadiaSettings::_normalizeCommandLine abuses some aspects from CommandLineToArgvW + // to simplify the implementation. It assumes that all arguments returned by + // CommandLineToArgvW are returned back to back in memory as "arg1\0arg2\0arg3\0...". + // This test ensures CommandLineToArgvW doesn't change just to be sure. + void TerminalSettingsTests::CommandLineToArgvW() + { + pcg_engines::oneseq_dxsm_64_32 rng{ til::gen_random() }; + + const auto expectedArgc = static_cast(rng(16) + 1); + std::wstring expectedArgv; + std::wstring input; + + // We generate up to 16 arguments. Each argument is up to 64 chars long, is quoted + // (2 chars, only applies to the input) and separated by a whitespace (1 char). + expectedArgv.reserve(expectedArgc * 65); + input.reserve(expectedArgc * 67); + + for (int i = 0; i < expectedArgc; ++i) + { + const bool useQuotes = static_cast(rng(2)); + const auto count = static_cast(rng(64)); + const auto ch = static_cast(rng('z' - 'a' + 1) + 'a'); + + if (i != 0) + { + expectedArgv.push_back(L'\0'); + input.push_back(L' '); + } + + if (useQuotes) + { + input.push_back(L'"'); + } + + expectedArgv.append(count, ch); + input.append(count, ch); + + if (useQuotes) + { + input.push_back(L'"'); + } + } + + int argc; + wil::unique_hlocal_ptr argv{ ::CommandLineToArgvW(input.c_str(), &argc) }; + VERIFY_ARE_EQUAL(expectedArgc, argc); + VERIFY_IS_NOT_NULL(argv); + + const auto lastArg = argv[argc - 1]; + const auto beg = argv[0]; + const auto end = lastArg + wcslen(lastArg); + VERIFY_IS_GREATER_THAN(end, beg); + VERIFY_ARE_EQUAL(expectedArgv.size(), static_cast(end - beg)); + VERIFY_ARE_EQUAL(0, memcmp(beg, expectedArgv.data(), expectedArgv.size())); + } + + void TerminalSettingsTests::GetProfileForArgsWithCommandline() + { + // I'm exclusively using cmd.exe as I know exactly where it resides at. + static constexpr std::string_view settingsJson{ R"({ + "profiles": { + "defaults": { + "historySize": 123 + }, + "list": [ + { + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "commandline": "%SystemRoot%\\System32\\cmd.exe" + }, + { + "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A" + }, + { + "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A /B" + }, + { + "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", + "commandline": "cmd.exe /A /C", + "connectionType": "{9a9977a7-1fe0-49c0-b6c0-13a0cd1c98a1}" + } + ] + } + })" }; + + const auto settings = winrt::make_self(settingsJson); + + struct TestCase + { + std::wstring_view input; + int expected; + }; + + static constexpr std::array testCases{ + // Base test. + TestCase{ L"cmd.exe", 0 }, + // SearchPathW() normalization + case insensitive matching. + TestCase{ L"cmd.exe /a", 1 }, + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A", 1 }, + // Test that we don't pick the equally long but different "/A /B" variant. + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 }, + // Test that we don't pick the shorter "/A" variant, + // but do pick the shorter "/A /B" variant for longer inputs. + TestCase{ L"cmd.exe /A /B", 2 }, + TestCase{ L"cmd.exe /A /B /C", 2 }, + // Ignore profiles with a connection type, like the Azure cloud shell. + // Instead it should pick any other prefix. + TestCase{ L"C:\\Windows\\System32\\cmd.exe /A /C", 1 }, + // Return base layer profile for missing profiles. + TestCase{ L"C:\\Windows\\regedit.exe", -1 }, + }; + + for (const auto& testCase : testCases) + { + NewTerminalArgs args; + args.Commandline(testCase.input); + + const auto profile = settings->GetProfileForArgs(args); + VERIFY_IS_NOT_NULL(profile); + + if (testCase.expected < 0) + { + VERIFY_ARE_EQUAL(123, profile.HistorySize()); + } + else + { + GUID expectedGUID{ 0x6239a42c, static_cast(0x1111 * testCase.expected), 0x49a3, { 0x80, 0xbd, 0xe8, 0xfd, 0xd0, 0x45, 0x18, 0x5c } }; + VERIFY_ARE_EQUAL(expectedGUID, static_cast(profile.Guid())); + } + } + } + void TerminalSettingsTests::TestTerminalArgsForBinding() { static constexpr std::string_view settingsJson{ R"( diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp index a9975b661..0d63043de 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp @@ -1031,9 +1031,11 @@ namespace TerminalAppLocalTests // The first action is going to always be a new-tab action VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action()); - auto actionAndArgs = appArgs._startupActions.at(1); + const auto actionAndArgs = appArgs._startupActions.at(1); VERIFY_ARE_EQUAL(ShortcutAction::NextTab, actionAndArgs.Action()); - VERIFY_IS_NULL(actionAndArgs.Args()); + VERIFY_IS_NOT_NULL(actionAndArgs.Args()); + const auto myArgs = actionAndArgs.Args().as(); + VERIFY_ARE_EQUAL(TabSwitcherMode::Disabled, myArgs.SwitcherMode().Value()); } { AppCommandlineArgs appArgs{}; @@ -1047,7 +1049,9 @@ namespace TerminalAppLocalTests auto actionAndArgs = appArgs._startupActions.at(1); VERIFY_ARE_EQUAL(ShortcutAction::PrevTab, actionAndArgs.Action()); - VERIFY_IS_NULL(actionAndArgs.Args()); + VERIFY_IS_NOT_NULL(actionAndArgs.Args()); + const auto myArgs = actionAndArgs.Args().as(); + VERIFY_ARE_EQUAL(TabSwitcherMode::Disabled, myArgs.SwitcherMode().Value()); } { AppCommandlineArgs appArgs{}; diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index fd7a8e446..f2c6fac9d 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -16,6 +16,31 @@ using namespace winrt::Microsoft::Terminal::Control; namespace TerminalAppLocalTests { + static constexpr std::wstring_view inboxSettings{ LR"({ + "schemes": [{ + "name": "Campbell", + "foreground": "#CCCCCC", + "background": "#0C0C0C", + "cursorColor": "#FFFFFF", + "black": "#0C0C0C", + "red": "#C50F1F", + "green": "#13A10E", + "yellow": "#C19C00", + "blue": "#0037DA", + "purple": "#881798", + "cyan": "#3A96DD", + "white": "#CCCCCC", + "brightBlack": "#767676", + "brightRed": "#E74856", + "brightGreen": "#16C60C", + "brightYellow": "#F9F1A5", + "brightBlue": "#3B78FF", + "brightPurple": "#B4009E", + "brightCyan": "#61D6D6", + "brightWhite": "#F2F2F2" + }] + })" }; + // 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 @@ -107,11 +132,10 @@ namespace TerminalAppLocalTests "iterateOn": "profiles", "command": { "action": "splitPane", "profile": "${profile.name}" } }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -231,11 +255,10 @@ namespace TerminalAppLocalTests "iterateOn": "profiles", "command": { "action": "splitPane", "profile": "${profile.name}" } }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -357,11 +380,10 @@ namespace TerminalAppLocalTests "iterateOn": "profiles", "command": { "action": "splitPane", "profile": "${profile.name}" } }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -495,11 +517,10 @@ namespace TerminalAppLocalTests } ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -590,11 +611,10 @@ namespace TerminalAppLocalTests }, ] }, - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -714,11 +734,10 @@ namespace TerminalAppLocalTests { "command": { "action": "splitPane", "profile": "${profile.name}", "split": "down" } } ] } - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -851,11 +870,10 @@ namespace TerminalAppLocalTests } ] } - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -954,11 +972,10 @@ namespace TerminalAppLocalTests } ] } - ], - "schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors. + ] })" }; - CascadiaSettings settings{ settingsJson, {} }; + CascadiaSettings settings{ settingsJson, inboxSettings }; VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); @@ -1085,9 +1102,72 @@ namespace TerminalAppLocalTests } ], "schemes": [ - { "name": "scheme_0" }, - { "name": "scheme_1" }, - { "name": "scheme_2" }, + { + "name": "Campbell", + "foreground": "#CCCCCC", + "background": "#0C0C0C", + "cursorColor": "#FFFFFF", + "black": "#0C0C0C", + "red": "#C50F1F", + "green": "#13A10E", + "yellow": "#C19C00", + "blue": "#0037DA", + "purple": "#881798", + "cyan": "#3A96DD", + "white": "#CCCCCC", + "brightBlack": "#767676", + "brightRed": "#E74856", + "brightGreen": "#16C60C", + "brightYellow": "#F9F1A5", + "brightBlue": "#3B78FF", + "brightPurple": "#B4009E", + "brightCyan": "#61D6D6", + "brightWhite": "#F2F2F2" + }, + { + "name": "Campbell PowerShell", + "foreground": "#CCCCCC", + "background": "#012456", + "cursorColor": "#FFFFFF", + "black": "#0C0C0C", + "red": "#C50F1F", + "green": "#13A10E", + "yellow": "#C19C00", + "blue": "#0037DA", + "purple": "#881798", + "cyan": "#3A96DD", + "white": "#CCCCCC", + "brightBlack": "#767676", + "brightRed": "#E74856", + "brightGreen": "#16C60C", + "brightYellow": "#F9F1A5", + "brightBlue": "#3B78FF", + "brightPurple": "#B4009E", + "brightCyan": "#61D6D6", + "brightWhite": "#F2F2F2" + }, + { + "name": "Vintage", + "foreground": "#C0C0C0", + "background": "#000000", + "cursorColor": "#FFFFFF", + "black": "#000000", + "red": "#800000", + "green": "#008000", + "yellow": "#808000", + "blue": "#000080", + "purple": "#800080", + "cyan": "#008080", + "white": "#C0C0C0", + "brightBlack": "#808080", + "brightRed": "#FF0000", + "brightGreen": "#00FF00", + "brightYellow": "#FFFF00", + "brightBlue": "#0000FF", + "brightPurple": "#FF00FF", + "brightCyan": "#00FFFF", + "brightWhite": "#FFFFFF" + } ], "actions": [ { @@ -1100,10 +1180,6 @@ namespace TerminalAppLocalTests CascadiaSettings settings{ settingsJson, {} }; - // Since at least one profile does not reference a color scheme, - // we add a warning saying "the color scheme is unknown" - VERIFY_ARE_EQUAL(1u, settings.Warnings().Size()); - VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); auto nameMap{ settings.ActionMap().NameMap() }; @@ -1130,8 +1206,6 @@ namespace TerminalAppLocalTests auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); - // This is the same warning as above - VERIFY_ARE_EQUAL(1u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, expandedCommands.Size()); // Yes, this test is testing splitPane with profiles named after each @@ -1139,7 +1213,7 @@ namespace TerminalAppLocalTests // just easy tests to write. { - auto command = expandedCommands.Lookup(L"iterable command scheme_0"); + auto command = expandedCommands.Lookup(L"iterable command Campbell"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -1153,11 +1227,11 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"scheme_0", realArgs.TerminalArgs().Profile()); + VERIFY_ARE_EQUAL(L"Campbell", realArgs.TerminalArgs().Profile()); } { - auto command = expandedCommands.Lookup(L"iterable command scheme_1"); + auto command = expandedCommands.Lookup(L"iterable command Campbell PowerShell"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -1171,11 +1245,11 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"scheme_1", realArgs.TerminalArgs().Profile()); + VERIFY_ARE_EQUAL(L"Campbell PowerShell", realArgs.TerminalArgs().Profile()); } { - auto command = expandedCommands.Lookup(L"iterable command scheme_2"); + auto command = expandedCommands.Lookup(L"iterable command Vintage"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -1189,7 +1263,7 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); VERIFY_IS_FALSE(realArgs.TerminalArgs().Profile().empty()); - VERIFY_ARE_EQUAL(L"scheme_2", realArgs.TerminalArgs().Profile()); + VERIFY_ARE_EQUAL(L"Vintage", realArgs.TerminalArgs().Profile()); } } diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index ecb40c3eb..4afa3a03b 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -508,7 +508,7 @@ namespace TerminalAppLocalTests Log::Comment(NoThrowString().Format(L"Duplicate the first pane")); result = RunOnUIThread([&page]() { - page->_SplitPane(SplitDirection::Automatic, SplitType::Duplicate, 0.5f, nullptr); + page->_SplitPane(SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, true, nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); @@ -526,7 +526,7 @@ namespace TerminalAppLocalTests Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash")); result = RunOnUIThread([&page]() { - page->_SplitPane(SplitDirection::Automatic, SplitType::Duplicate, 0.5f, nullptr); + page->_SplitPane(SplitDirection::Automatic, 0.5f, page->_MakePane(nullptr, true, nullptr)); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); @@ -844,7 +844,7 @@ namespace TerminalAppLocalTests // | 1 | 2 | // | | | // ------------------- - page->_SplitPane(SplitDirection::Right, SplitType::Duplicate, 0.5f, nullptr); + page->_SplitPane(SplitDirection::Right, 0.5f, page->_MakePane(nullptr, true, nullptr)); secondId = tab->_activePane->Id().value(); }); Sleep(250); @@ -862,7 +862,7 @@ namespace TerminalAppLocalTests // | 3 | | // | | | // ------------------- - page->_SplitPane(SplitDirection::Down, SplitType::Duplicate, 0.5f, nullptr); + page->_SplitPane(SplitDirection::Down, 0.5f, page->_MakePane(nullptr, true, nullptr)); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); // Split again to make the 3rd tab thirdId = tab->_activePane->Id().value(); @@ -882,7 +882,7 @@ namespace TerminalAppLocalTests // | 3 | 4 | // | | | // ------------------- - page->_SplitPane(SplitDirection::Down, SplitType::Duplicate, 0.5f, nullptr); + page->_SplitPane(SplitDirection::Down, 0.5f, page->_MakePane(nullptr, true, nullptr)); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); fourthId = tab->_activePane->Id().value(); }); diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index 02025be81..6a1a8282c 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp @@ -977,7 +977,7 @@ void HwndTerminal::_StringPaste(const wchar_t* const pData) noexcept CATCH_LOG(); } -COORD HwndTerminal::GetFontSize() const +COORD HwndTerminal::GetFontSize() const noexcept { return _actualFont.GetSize(); } diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp index bc9c8ba4a..0510675ef 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.hpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.hpp @@ -128,7 +128,7 @@ private: void _SendCharEvent(wchar_t ch, WORD scanCode, WORD flags) noexcept; // Inherited via IControlAccessibilityInfo - COORD GetFontSize() const override; + COORD GetFontSize() const noexcept override; RECT GetBounds() const noexcept override; double GetScaleFactor() const noexcept override; void ChangeViewport(const SMALL_RECT NewWindow) override; diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index ae79289fa..3feed58a0 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -174,10 +174,9 @@ namespace winrt::TerminalApp::implementation else if (const auto& realArgs = args.ActionArgs().try_as()) { _SplitPane(realArgs.SplitDirection(), - realArgs.SplitMode(), // This is safe, we're already filtering so the value is (0, 1) ::base::saturated_cast(realArgs.SplitSize()), - realArgs.TerminalArgs()); + _MakePane(realArgs.TerminalArgs(), realArgs.SplitMode() == SplitType::Duplicate)); args.Handled(true); } } @@ -556,7 +555,7 @@ namespace winrt::TerminalApp::implementation auto actions = winrt::single_threaded_vector(std::move( TerminalPage::ConvertExecuteCommandlineToActions(realArgs))); - if (_startupActions.Size() != 0) + if (actions.Size() != 0) { actionArgs.Handled(true); ProcessStartupActions(actions, false); diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 72f25b83e..7e5949577 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -334,6 +334,9 @@ namespace winrt::TerminalApp::implementation _RefreshThemeRoutine(); _ApplyStartupTaskStateChange(); + auto args = winrt::make_self(RS_(L"SettingsMenuItem"), SystemMenuChangeAction::Add, SystemMenuItemHandler(this, &AppLogic::_OpenSettingsUI)); + _SystemMenuChangeRequestedHandlers(*this, *args); + TraceLoggingWrite( g_hTerminalAppProvider, "AppCreated", @@ -375,6 +378,8 @@ namespace winrt::TerminalApp::implementation co_return ContentDialogResult::None; } + _dialog = dialog; + // IMPORTANT: This is necessary as documented in the ContentDialog MSDN docs. // Since we're hosting the dialog in a Xaml island, we need to connect it to the // xaml tree somehow. @@ -412,6 +417,16 @@ namespace winrt::TerminalApp::implementation // be released so another can be shown } + // Method Description: + // - Dismiss the (only) visible ContentDialog + void AppLogic::DismissDialog() + { + if (auto localDialog = std::exchange(_dialog, nullptr)) + { + localDialog.Hide(); + } + } + // Method Description: // - Displays a dialog for errors found while loading or validating the // settings. Uses the resources under the provided title and content keys @@ -1039,6 +1054,11 @@ namespace winrt::TerminalApp::implementation _SettingsChangedHandlers(*this, nullptr); } + void AppLogic::_OpenSettingsUI() + { + _root->OpenSettingsUI(); + } + // Method Description: // - Returns a pointer to the global shared settings. [[nodiscard]] CascadiaSettings AppLogic::GetSettings() const noexcept @@ -1545,6 +1565,11 @@ namespace winrt::TerminalApp::implementation return _root->IsQuakeWindow(); } + void AppLogic::RequestExitFullscreen() + { + _root->SetFullscreen(false); + } + bool AppLogic::GetMinimizeToNotificationArea() { if constexpr (Feature_NotificationIcon::IsEnabled()) diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 17bd61aaa..7c1681db1 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -5,6 +5,7 @@ #include "AppLogic.g.h" #include "FindTargetWindowResult.g.h" +#include "SystemMenuChangeArgs.g.h" #include "Jumplist.h" #include "LanguageProfileNotifier.h" #include "TerminalPage.h" @@ -35,6 +36,17 @@ namespace winrt::TerminalApp::implementation FindTargetWindowResult(id, L""){}; }; + struct SystemMenuChangeArgs : SystemMenuChangeArgsT + { + WINRT_PROPERTY(winrt::hstring, Name, L""); + WINRT_PROPERTY(SystemMenuChangeAction, Action, SystemMenuChangeAction::Add); + WINRT_PROPERTY(SystemMenuItemHandler, Handler, nullptr); + + public: + SystemMenuChangeArgs(const winrt::hstring& name, SystemMenuChangeAction action, SystemMenuItemHandler handler = nullptr) : + _Name{ name }, _Action{ action }, _Handler{ handler } {}; + }; + struct AppLogic : AppLogicT { public: @@ -79,6 +91,7 @@ namespace winrt::TerminalApp::implementation void SetPersistedLayoutIdx(const uint32_t idx); void SetNumberOfOpenWindows(const uint64_t num); bool IsQuakeWindow() const noexcept; + void RequestExitFullscreen(); Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); bool CenterOnLaunch(); @@ -106,12 +119,14 @@ namespace winrt::TerminalApp::implementation bool GetShowTitleInTitlebar(); winrt::Windows::Foundation::IAsyncOperation ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog); + void DismissDialog(); Windows::Foundation::Collections::IMapView GlobalHotkeys(); // -------------------------------- WinRT Events --------------------------------- TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme); TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable); + TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs); private: bool _isUwp{ false }; @@ -132,6 +147,7 @@ namespace winrt::TerminalApp::implementation uint64_t _numOpenWindows{ 0 }; std::shared_mutex _dialogLock; + winrt::Windows::UI::Xaml::Controls::ContentDialog _dialog; ::TerminalApp::AppCommandlineArgs _appArgs; ::TerminalApp::AppCommandlineArgs _settingsAppArgs; @@ -161,6 +177,7 @@ namespace winrt::TerminalApp::implementation void _RegisterSettingsChange(); fire_and_forget _DispatchReloadSettings(); void _ReloadSettings(); + void _OpenSettingsUI(); void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index cfe93321a..973e8aa1a 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -19,6 +19,20 @@ namespace TerminalApp String WindowName { get; }; }; + delegate void SystemMenuItemHandler(); + + enum SystemMenuChangeAction + { + Add = 0, + Remove = 1 + }; + + [default_interface] runtimeclass SystemMenuChangeArgs { + String Name { get; }; + SystemMenuChangeAction Action { get; }; + SystemMenuItemHandler Handler { get; }; + }; + [default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter { AppLogic(); @@ -60,6 +74,7 @@ namespace TerminalApp void SetPersistedLayoutIdx(UInt32 idx); void SetNumberOfOpenWindows(UInt64 num); void RenameFailed(); + void RequestExitFullscreen(); Boolean IsQuakeWindow(); Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); @@ -91,6 +106,7 @@ namespace TerminalApp // See IDialogPresenter and TerminalPage's DialogPresenter for more // information. Windows.Foundation.IAsyncOperation ShowDialog(Windows.UI.Xaml.Controls.ContentDialog dialog); + void DismissDialog(); event Windows.Foundation.TypedEventHandler SetTitleBarContent; event Windows.Foundation.TypedEventHandler TitleChanged; @@ -109,5 +125,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler CloseRequested; event Windows.Foundation.TypedEventHandler OpenSystemMenu; event Windows.Foundation.TypedEventHandler QuitRequested; + event Windows.Foundation.TypedEventHandler SystemMenuChangeRequested; } } diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 54f327b3e..0d84d8f05 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -21,8 +21,22 @@ namespace winrt::Microsoft::TerminalApp::implementation } void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) {} ~DebugInputTapConnection() = default; - void Start() + winrt::fire_and_forget Start() { + // GH#11282: It's possible that we're about to be started, _before_ + // our paired connection is started. Both will get Start()'ed when + // their owning TermControl is finally laid out. However, if we're + // started first, then we'll immediately start printing to the other + // control as well, which might not have initialized yet. If we do + // that, we'll explode. + // + // Instead, wait here until the other connection is started too, + // before actually starting the connection to the client app. This + // will ensure both controls are initialized before the client app + // is. + co_await winrt::resume_background(); + _pairedTap->_start.wait(); + _wrappedConnection.Start(); } void WriteInput(hstring const& data) @@ -59,6 +73,9 @@ namespace winrt::Microsoft::TerminalApp::implementation void DebugTapConnection::Start() { // presume the wrapped connection is started. + + // This is explained in the comment for GH#11282 above. + _start.count_down(); } void DebugTapConnection::WriteInput(hstring const& data) diff --git a/src/cascadia/TerminalApp/DebugTapConnection.h b/src/cascadia/TerminalApp/DebugTapConnection.h index c5156afc4..56d509fbf 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.h +++ b/src/cascadia/TerminalApp/DebugTapConnection.h @@ -5,6 +5,7 @@ #include #include "../../inc/cppwinrt_utils.h" +#include namespace winrt::Microsoft::TerminalApp::implementation { @@ -36,6 +37,8 @@ namespace winrt::Microsoft::TerminalApp::implementation winrt::weak_ref _wrappedConnection; winrt::weak_ref _inputSide; + til::latch _start{ 1 }; + friend class DebugInputTapConnection; }; } diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index e04f558f6..ef8e12522 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2351,35 +2351,32 @@ std::optional Pane::PreCalculateCanSplit(const std::shared_ptr targe } // Method Description: -// - Split the focused pane in our tree of panes, and place the given -// TermControl into the newly created pane. If we're the focused pane, then -// we'll create two new children, and place them side-by-side in our Grid. +// - The same as above, except this takes in the pane directly instead of a +// profile and control to make a pane with // Arguments: // - splitType: what type of split we want to create. -// - profile: The profile to associate with the newly created pane. -// - control: A TermControl to use in the new pane. +// - splitSize: the desired size of the split +// - newPane: the new pane // Return Value: // - The two newly created Panes, with the original pane first std::pair, std::shared_ptr> Pane::Split(SplitDirection splitType, const float splitSize, - const Profile& profile, - const TermControl& control) + std::shared_ptr newPane) { if (!_lastActive) { if (_firstChild && _firstChild->_HasFocusedChild()) { - return _firstChild->Split(splitType, splitSize, profile, control); + return _firstChild->Split(splitType, splitSize, newPane); } else if (_secondChild && _secondChild->_HasFocusedChild()) { - return _secondChild->Split(splitType, splitSize, profile, control); + return _secondChild->Split(splitType, splitSize, newPane); } return { nullptr, nullptr }; } - auto newPane = std::make_shared(profile, control); return _Split(splitType, splitSize, newPane); } diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 9892a6e8a..920124c33 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -110,8 +110,7 @@ public: std::pair, std::shared_ptr> Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType, const float splitSize, - const winrt::Microsoft::Terminal::Settings::Model::Profile& profile, - const winrt::Microsoft::Terminal::Control::TermControl& control); + std::shared_ptr pane); bool ToggleSplitOrientation(); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; std::optional PreCalculateAutoSplit(const std::shared_ptr target, diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 55e51abbb..56bc3ed17 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -718,10 +718,17 @@ Termination behavior can be configured in advanced profile settings. + + Windows Terminal can be set as the default terminal application in your settings. + Don't show again This Terminal window is running as Admin + + Open Settings + This is a call-to-action hyperlink; it will open the settings. + diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index b99af3236..e67ad7780 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -68,7 +68,7 @@ namespace winrt::TerminalApp::implementation const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; const auto settings{ TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings) }; - _CreateNewTabWithProfileAndSettings(profile, settings, existingConnection); + _CreateNewTabFromPane(_MakePane(newTerminalArgs, false, existingConnection)); const uint32_t tabCount = _tabs.Size(); const bool usedManualProfile = (newTerminalArgs != nullptr) && @@ -244,58 +244,6 @@ namespace winrt::TerminalApp::implementation _InitializeTab(newTabImpl); } - // Method Description: - // - Creates a new tab with the given settings. If the tab bar is not being - // currently displayed, it will be shown. - // Arguments: - // - profile: profile settings for this connection - // - settings: the TerminalSettings object to use to create the TerminalControl with. - // - existingConnection: optionally receives a connection from the outside world instead of attempting to create one - void TerminalPage::_CreateNewTabWithProfileAndSettings(const Profile& profile, const TerminalSettingsCreateResult& settings, TerminalConnection::ITerminalConnection existingConnection) - { - // Initialize the new tab - // Create a connection based on the values in our settings object if we weren't given one. - auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, settings.DefaultSettings()); - - // If we had an `existingConnection`, then this is an inbound handoff from somewhere else. - // We need to tell it about our size information so it can match the dimensions of what - // we are about to present. - if (existingConnection) - { - connection.Resize(settings.DefaultSettings().InitialRows(), settings.DefaultSettings().InitialCols()); - } - - TerminalConnection::ITerminalConnection debugConnection{ nullptr }; - if (_settings.GlobalSettings().DebugFeaturesEnabled()) - { - const CoreWindow window = CoreWindow::GetForCurrentThread(); - const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); - const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); - const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) && - WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); - if (bothAltsPressed) - { - std::tie(connection, debugConnection) = OpenDebugTapConnection(connection); - } - } - - // Give term control a child of the settings so that any overrides go in the child - // This way, when we do a settings reload we just update the parent and the overrides remain - auto term = _InitControl(settings, connection); - - auto newTabImpl = winrt::make_self(profile, term); - _RegisterTerminalEvents(term); - _InitializeTab(newTabImpl); - - if (debugConnection) // this will only be set if global debugging is on and tap is active - { - auto newControl = _InitControl(settings, debugConnection); - _RegisterTerminalEvents(newControl); - // Split (auto) with the debug tap. - newTabImpl->SplitPane(SplitDirection::Automatic, 0.5f, profile, newControl); - } - } - // Method Description: // - Get the icon of the currently focused terminal control, and set its // tab's icon to that icon. @@ -365,28 +313,14 @@ namespace winrt::TerminalApp::implementation // In the future, it may be preferable to just duplicate the // current control's live settings (which will include changes // made through VT). + _CreateNewTabFromPane(_MakePane(nullptr, true, nullptr)); - if (auto profile = tab.GetFocusedProfile()) + const auto runtimeTabText{ tab.GetTabText() }; + if (!runtimeTabText.empty()) { - // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. - profile = GetClosestProfileForDuplicationOfProfile(profile); - const auto settingsCreateResult{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; - const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory(); - const auto validWorkingDirectory = !workingDirectory.empty(); - if (validWorkingDirectory) + if (auto newTab{ _GetFocusedTabImpl() }) { - settingsCreateResult.DefaultSettings().StartingDirectory(workingDirectory); - } - - _CreateNewTabWithProfileAndSettings(profile, settingsCreateResult); - - const auto runtimeTabText{ tab.GetTabText() }; - if (!runtimeTabText.empty()) - { - if (auto newTab{ _GetFocusedTabImpl() }) - { - newTab->SetTabText(runtimeTabText); - } + newTab->SetTabText(runtimeTabText); } } } @@ -402,7 +336,7 @@ namespace winrt::TerminalApp::implementation try { _SetFocusedTab(tab); - _SplitPane(tab, SplitDirection::Automatic, SplitType::Duplicate); + _SplitPane(tab, SplitDirection::Automatic, 0.5f, _MakePane(nullptr, true)); } CATCH_LOG(); } diff --git a/src/cascadia/TerminalApp/TabRowControl.xaml b/src/cascadia/TerminalApp/TabRowControl.xaml index b3b0f58c3..0cce26ef3 100644 --- a/src/cascadia/TerminalApp/TabRowControl.xaml +++ b/src/cascadia/TerminalApp/TabRowControl.xaml @@ -23,7 +23,7 @@ _SplitPane(SplitDirection::Automatic, - SplitType::Manual, - 0.5f, - newTerminalArgs); - } - else if (shiftPressed && !debugTap) + if (shiftPressed && !debugTap) { // Manually fill in the evaluated profile. if (newTerminalArgs.ProfileIndex() != nullptr) @@ -871,7 +866,17 @@ namespace winrt::TerminalApp::implementation } else { - LOG_IF_FAILED(this->_OpenNewTab(newTerminalArgs)); + const auto newPane = _MakePane(newTerminalArgs); + if (altPressed && !debugTap) + { + this->_SplitPane(SplitDirection::Automatic, + 0.5f, + newPane); + } + else + { + _CreateNewTabFromPane(newPane); + } } } @@ -1483,18 +1488,15 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Split the focused pane either horizontally or vertically, and place the - // given TermControl into the newly created pane. + // given pane accordingly in the tree // Arguments: + // - newPane: the pane to add to our tree of panes // - splitDirection: one value from the TerminalApp::SplitDirection enum, indicating how the // new pane should be split from its parent. - // - splitMode: value from TerminalApp::SplitType enum, indicating the profile to be used in the newly split pane. - // - newTerminalArgs: An object that may contain a blob of parameters to - // control which profile is created and with possible other - // configurations. See CascadiaSettings::BuildSettings for more details. + // - splitSize: the size of the split void TerminalPage::_SplitPane(const SplitDirection splitDirection, - const SplitType splitMode, const float splitSize, - const NewTerminalArgs& newTerminalArgs) + std::shared_ptr newPane) { const auto focusedTab{ _GetFocusedTabImpl() }; @@ -1504,101 +1506,52 @@ namespace winrt::TerminalApp::implementation return; } - _SplitPane(*focusedTab, splitDirection, splitMode, splitSize, newTerminalArgs); + _SplitPane(*focusedTab, splitDirection, splitSize, newPane); } // Method Description: // - Split the focused pane of the given tab, either horizontally or vertically, and place the - // given TermControl into the newly created pane. + // given pane accordingly // Arguments: // - tab: The tab that is going to be split. + // - newPane: the pane to add to our tree of panes // - splitDirection: one value from the TerminalApp::SplitDirection enum, indicating how the // new pane should be split from its parent. - // - splitMode: value from TerminalApp::SplitType enum, indicating the profile to be used in the newly split pane. - // - newTerminalArgs: An object that may contain a blob of parameters to - // control which profile is created and with possible other - // configurations. See CascadiaSettings::BuildSettings for more details. + // - splitSize: the size of the split void TerminalPage::_SplitPane(TerminalTab& tab, const SplitDirection splitDirection, - const SplitType splitMode, const float splitSize, - const NewTerminalArgs& newTerminalArgs) + std::shared_ptr newPane) { - try + const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); + const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); + const winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight }; + + auto realSplitType = splitDirection; + if (realSplitType == SplitDirection::Automatic) { - TerminalSettingsCreateResult controlSettings{ nullptr }; - Profile profile{ nullptr }; + realSplitType = tab.PreCalculateAutoSplit(availableSpace); + } - if (splitMode == SplitType::Duplicate) + const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace); + if (!canSplit) + { + return; + } + + _UnZoomIfNeeded(); + tab.SplitPane(realSplitType, splitSize, newPane); + + // After GH#6586, the control will no longer focus itself + // automatically when it's finished being laid out. Manually focus + // the control here instead. + if (_startupState == StartupState::Initialized) + { + if (const auto control = _GetActiveControl()) { - profile = tab.GetFocusedProfile(); - if (profile) - { - // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. - profile = GetClosestProfileForDuplicationOfProfile(profile); - controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings); - const auto workingDirectory = tab.GetActiveTerminalControl().WorkingDirectory(); - const auto validWorkingDirectory = !workingDirectory.empty(); - if (validWorkingDirectory) - { - controlSettings.DefaultSettings().StartingDirectory(workingDirectory); - } - } - // TODO: GH#5047 - In the future, we should get the Profile of - // the focused pane, and use that to build a new instance of the - // settings so we can duplicate this tab/pane. - // - // Currently, if the profile doesn't exist anymore in our - // settings, we'll silently do nothing. - // - // In the future, it will be preferable to just duplicate the - // current control's settings, but we can't do that currently, - // because we won't be able to create a new instance of the - // connection without keeping an instance of the original Profile - // object around. - } - if (!profile) - { - profile = _settings.GetProfileForArgs(newTerminalArgs); - controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings); - } - - const auto controlConnection = _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings()); - - const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); - const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); - const winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight }; - - auto realSplitType = splitDirection; - if (realSplitType == SplitDirection::Automatic) - { - realSplitType = tab.PreCalculateAutoSplit(availableSpace); - } - - const auto canSplit = tab.PreCalculateCanSplit(realSplitType, splitSize, availableSpace); - if (!canSplit) - { - return; - } - - auto newControl = _InitControl(controlSettings, controlConnection); - - // Hookup our event handlers to the new terminal - _RegisterTerminalEvents(newControl); - - _UnZoomIfNeeded(); - - tab.SplitPane(realSplitType, splitSize, profile, newControl); - - // After GH#6586, the control will no longer focus itself - // automatically when it's finished being laid out. Manually focus - // the control here instead. - if (_startupState == StartupState::Initialized) - { - _GetActiveControl().Focus(FocusState::Programmatic); + control.Focus(FocusState::Programmatic); } } - CATCH_LOG(); } // Method Description: @@ -1867,6 +1820,22 @@ namespace winrt::TerminalApp::implementation } } + if (_settings.GlobalSettings().TrimPaste()) + { + std::wstring_view textView{ text }; + const auto pos = textView.find_last_not_of(L"\t\n\v\f\r "); + if (pos == textView.npos) + { + // Text is all white space, nothing to paste + co_return; + } + else if (const auto toRemove = textView.size() - 1 - pos; toRemove > 0) + { + textView.remove_suffix(toRemove); + text = { textView }; + } + } + bool warnMultiLine = _settings.GlobalSettings().WarnAboutMultiLinePaste(); if (warnMultiLine) { @@ -2090,7 +2059,7 @@ namespace winrt::TerminalApp::implementation { if (target == SettingsTarget::SettingsUI) { - _OpenSettingsUI(); + OpenSettingsUI(); } else { @@ -2167,6 +2136,96 @@ namespace winrt::TerminalApp::implementation return term; } + // Method Description: + // - Creates a pane and returns a shared_ptr to it + // - The caller should handle where the pane goes after creation, + // either to split an already existing pane or to create a new tab with it + // Arguments: + // - newTerminalArgs: an object that may contain a blob of parameters to + // control which profile is created and with possible other + // configurations. See CascadiaSettings::BuildSettings for more details. + // - duplicate: a boolean to indicate whether the pane we create should be + // a duplicate of the currently focused pane + // - existingConnection: optionally receives a connection from the outside + // world instead of attempting to create one + std::shared_ptr TerminalPage::_MakePane(const NewTerminalArgs& newTerminalArgs, const bool duplicate, TerminalConnection::ITerminalConnection existingConnection) + { + TerminalSettingsCreateResult controlSettings{ nullptr }; + Profile profile{ nullptr }; + + if (duplicate) + { + const auto focusedTab{ _GetFocusedTabImpl() }; + if (focusedTab) + { + profile = focusedTab->GetFocusedProfile(); + if (profile) + { + // TODO GH#5047 If we cache the NewTerminalArgs, we no longer need to do this. + profile = GetClosestProfileForDuplicationOfProfile(profile); + controlSettings = TerminalSettings::CreateWithProfile(_settings, profile, *_bindings); + const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory(); + const auto validWorkingDirectory = !workingDirectory.empty(); + if (validWorkingDirectory) + { + controlSettings.DefaultSettings().StartingDirectory(workingDirectory); + } + } + } + } + if (!profile) + { + profile = _settings.GetProfileForArgs(newTerminalArgs); + controlSettings = TerminalSettings::CreateWithNewTerminalArgs(_settings, newTerminalArgs, *_bindings); + } + + auto connection = existingConnection ? existingConnection : _CreateConnectionFromSettings(profile, controlSettings.DefaultSettings()); + if (existingConnection) + { + connection.Resize(controlSettings.DefaultSettings().InitialRows(), controlSettings.DefaultSettings().InitialCols()); + } + + TerminalConnection::ITerminalConnection debugConnection{ nullptr }; + if (_settings.GlobalSettings().DebugFeaturesEnabled()) + { + const CoreWindow window = CoreWindow::GetForCurrentThread(); + const auto rAltState = window.GetKeyState(VirtualKey::RightMenu); + const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu); + const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) && + WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down); + if (bothAltsPressed) + { + std::tie(connection, debugConnection) = OpenDebugTapConnection(connection); + } + } + + const auto control = _InitControl(controlSettings, connection); + _RegisterTerminalEvents(control); + + auto resultPane = std::make_shared(profile, control); + + if (debugConnection) // this will only be set if global debugging is on and tap is active + { + auto newControl = _InitControl(controlSettings, debugConnection); + _RegisterTerminalEvents(newControl); + // Split (auto) with the debug tap. + auto debugPane = std::make_shared(profile, newControl); + + // Since we're doing this split directly on the pane (instead of going through TerminalTab, + // we need to handle the panes 'active' states + + // Set the pane we're splitting to active (otherwise Split will not do anything) + resultPane->SetActive(); + auto [original, _] = resultPane->Split(SplitDirection::Automatic, 0.5f, debugPane); + + // Set the non-debug pane as active + resultPane->ClearActive(); + original->SetActive(); + } + + return resultPane; + } + // Method Description: // - Hook up keybindings, and refresh the UI of the terminal. // This includes update the settings of all the tabs according @@ -2476,9 +2535,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::ToggleFullscreen() { - _isFullscreen = !_isFullscreen; - _UpdateTabView(); - _FullscreenChangedHandlers(*this, nullptr); + SetFullscreen(!_isFullscreen); } // Method Description: @@ -2695,52 +2752,51 @@ namespace winrt::TerminalApp::implementation return _isAlwaysOnTop; } - HRESULT TerminalPage::_OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection) + void TerminalPage::SetFullscreen(bool newFullscreen) + { + if (_isFullscreen == newFullscreen) + { + return; + } + _isFullscreen = newFullscreen; + _UpdateTabView(); + _FullscreenChangedHandlers(*this, nullptr); + } + + HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection) { // We need to be on the UI thread in order for _OpenNewTab to run successfully. // HasThreadAccess will return true if we're currently on a UI thread and false otherwise. // When we're on a COM thread, we'll need to dispatch the calls to the UI thread // and wait on it hence the locking mechanism. - if (Dispatcher().HasThreadAccess()) - { - try - { - NewTerminalArgs newTerminalArgs{}; - // TODO GH#10952: When we pass the actual commandline (or originating application), the - // settings model can choose the right settings based on command matching, or synthesize - // a profile from the registry/link settings (TODO GH#9458). - // TODO GH#9458: Get and pass the LNK/EXE filenames. - // Passing in a commandline forces GetProfileForArgs to use Base Layer instead of Default Profile; - // in the future, it can make a better decision based on the value we pull out of the process handle. - // TODO GH#5047: When we hang on to the N.T.A., try not to spawn "default... .exe" :) - newTerminalArgs.Commandline(L"default-terminal-invocation-placeholder"); - const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; - const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; - - _CreateNewTabWithProfileAndSettings(profile, settings, connection); - - // Request a summon of this window to the foreground - _SummonWindowRequestedHandlers(*this, nullptr); - } - CATCH_RETURN(); - - return S_OK; - } - else + if (!Dispatcher().HasThreadAccess()) { til::latch latch{ 1 }; HRESULT finalVal = S_OK; Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [&]() { - // Re-running ourselves under the dispatcher will cause us to take the first branch above. finalVal = _OnNewConnection(connection); - latch.count_down(); }); latch.wait(); return finalVal; } + + try + { + NewTerminalArgs newTerminalArgs; + newTerminalArgs.Commandline(connection.Commandline()); + const auto profile{ _settings.GetProfileForArgs(newTerminalArgs) }; + const auto settings{ TerminalSettings::CreateWithProfile(_settings, profile, *_bindings) }; + + _CreateNewTabFromPane(_MakePane(newTerminalArgs, false, connection)); + + // Request a summon of this window to the foreground + _SummonWindowRequestedHandlers(*this, nullptr); + return S_OK; + } + CATCH_RETURN() } // Method Description: @@ -2750,7 +2806,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void TerminalPage::_OpenSettingsUI() + void TerminalPage::OpenSettingsUI() { // If we're holding the settings tab's switch command, don't create a new one, switch to the existing one. if (!_settingsTab) @@ -2893,6 +2949,30 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Displays a info popup guiding the user into setting their default terminal. + void TerminalPage::ShowSetAsDefaultInfoBar() const + { + if (!CascadiaSettings::IsDefaultTerminalAvailable() || _IsMessageDismissed(InfoBarMessage::SetAsDefault)) + { + return; + } + + // If the user has already configured any terminal for hand-off we + // shouldn't inform them again about the possibility to do so. + if (CascadiaSettings::IsDefaultTerminalSet()) + { + _DismissMessage(InfoBarMessage::SetAsDefault); + return; + } + + if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as()) + { + TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipPresented", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); + infoBar.IsOpen(true); + } + } + // Function Description: // - Helper function to get the OS-localized name for the "Touch Keyboard // and Handwriting Panel Service". If we can't open up the service for any @@ -3313,6 +3393,40 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Persists the user's choice not to show the information bar warning about "Windows Terminal can be set as your default terminal application" + // Then hides this information buffer. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_SetAsDefaultDismissHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + _DismissMessage(InfoBarMessage::SetAsDefault); + if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as()) + { + infoBar.IsOpen(false); + } + + TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipDismissed", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + + _FocusCurrentTab(true); + } + + // Method Description: + // - Dismisses the Default Terminal tip and opens the settings. + void TerminalPage::_SetAsDefaultOpenSettingsHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) + { + if (const auto infoBar = FindName(L"SetAsDefaultInfoBar").try_as()) + { + infoBar.IsOpen(false); + } + + TraceLoggingWrite(g_hTerminalAppProvider, "SetAsDefaultTipInteracted", TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + + OpenSettingsUI(); + } + // Method Description: // - Checks whether information bar message was dismissed earlier (in the application state) // Arguments: @@ -3342,13 +3456,20 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_DismissMessage(const InfoBarMessage& message) { - auto dismissedMessages = ApplicationState::SharedInstance().DismissedMessages(); - if (!dismissedMessages) + const auto applicationState = ApplicationState::SharedInstance(); + std::vector messages; + + if (const auto values = applicationState.DismissedMessages()) { - dismissedMessages = winrt::single_threaded_vector(); + messages.resize(values.Size()); + values.GetMany(0, messages); } - dismissedMessages.Append(message); - ApplicationState::SharedInstance().DismissedMessages(dismissedMessages); + if (std::none_of(messages.begin(), messages.end(), [&](const auto& m) { return m == message; })) + { + messages.emplace_back(message); + } + + applicationState.DismissedMessages(std::move(messages)); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 26332453a..cf9477d2e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -83,6 +83,7 @@ namespace winrt::TerminalApp::implementation bool FocusMode() const; bool Fullscreen() const; bool AlwaysOnTop() const; + void SetFullscreen(bool); void SetStartupActions(std::vector& actions); @@ -95,6 +96,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::TaskbarState TaskbarState() const; void ShowKeyboardServiceWarning() const; + void ShowSetAsDefaultInfoBar() const; winrt::hstring KeyboardServiceDisabledText(); winrt::fire_and_forget IdentifyWindow(); @@ -120,6 +122,8 @@ namespace winrt::TerminalApp::implementation bool IsQuakeWindow() const noexcept; bool IsElevated() const noexcept; + void OpenSettingsUI(); + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); // -------------------------------- WinRT Events --------------------------------- @@ -208,7 +212,6 @@ namespace winrt::TerminalApp::implementation void _OpenNewTabDropdown(); HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); void _CreateNewTabFromPane(std::shared_ptr pane); - void _CreateNewTabWithProfileAndSettings(const Microsoft::Terminal::Settings::Model::Profile& profile, const Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings); winrt::fire_and_forget _OpenNewWindow(const bool elevate, const Microsoft::Terminal::Settings::Model::NewTerminalArgs newTerminalArgs); @@ -296,14 +299,12 @@ namespace winrt::TerminalApp::implementation void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference& rowsToScroll); void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitDirection splitType, - const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, - const float splitSize = 0.5f, - const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr); + const float splitSize, + std::shared_ptr newPane); void _SplitPane(TerminalTab& tab, const Microsoft::Terminal::Settings::Model::SplitDirection splitType, - const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, - const float splitSize = 0.5f, - const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr); + const float splitSize, + std::shared_ptr newPane); void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction); void _ToggleSplitOrientation(); @@ -350,6 +351,10 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::TermControl _InitControl(const winrt::Microsoft::Terminal::Settings::Model::TerminalSettingsCreateResult& settings, const winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection& connection); + std::shared_ptr _MakePane(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr, + const bool duplicate = false, + winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr); + void _RefreshUIForSettingsReload(); void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor); @@ -365,8 +370,6 @@ namespace winrt::TerminalApp::implementation void _UnZoomIfNeeded(); - void _OpenSettingsUI(); - static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll); static uint32_t _ReadSystemRowsToScroll(); @@ -387,7 +390,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr }; std::vector> _restorePreviewFuncs{}; - HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); + HRESULT _OnNewConnection(const winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection& connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _WindowRenamerActionClick(const IInspectable& sender, const IInspectable& eventArgs); @@ -403,6 +406,8 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget _ConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; void _CloseOnExitInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; void _KeyboardServiceWarningInfoDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args) const; + void _SetAsDefaultDismissHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); + void _SetAsDefaultOpenSettingsHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args); static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message); static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message); diff --git a/src/cascadia/TerminalApp/TerminalPage.xaml b/src/cascadia/TerminalApp/TerminalPage.xaml index b6a4676dd..e4be0e54a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -15,6 +15,7 @@ + @@ -22,8 +23,50 @@ + + + +