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/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 ba3387d84..98f3de653 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,11 +1185,12 @@ "properties": { "action": { "type": "string", - "pattern": "focusPane" + "const": "focusPane" }, "id": { - "type": "string", - "default": "", + "type": "integer", + "minimum": 0, + "default": 0, "description": "The ID of the pane to focus" } } @@ -1202,7 +1207,7 @@ "properties": { "action": { "type": "string", - "pattern": "globalSummon" + "const": "globalSummon" }, "desktop": { "type": "string", @@ -1229,7 +1234,7 @@ "description": "When provided, summon the window whose name or ID matches the given name value. If no such window exists, then create a new window with that name." }, "dropdownDuration": { - "type": "number", + "type": "integer", "minimum": 0, "default": 0, "description": "When provided with a positive number, \"slide\" the window in from the top of the screen using an animation that lasts dropdownDuration milliseconds." @@ -1253,7 +1258,7 @@ "properties": { "action": { "type": "string", - "pattern": "quakeMode" + "const": "quakeMode" } } } @@ -1328,6 +1333,12 @@ { "$ref": "#/$defs/MoveTabAction" }, + { + "$ref": "#/$defs/MultipleActionsAction" + }, + { + "$ref": "#/$defs/CommandPaletteAction" + }, { "$ref": "#/$defs/FindMatchAction" }, @@ -1570,22 +1581,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" }, @@ -1972,6 +1983,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/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/TextColor.cpp b/src/buffer/out/TextColor.cpp index dcd0a8df3..7c05dd200 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, @@ -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 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/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..10214b861 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}", @@ -105,7 +105,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 +117,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 +152,7 @@ namespace SettingsModelLocalTests "selectionBackground": "#CCAABB", "useAcrylic": false, - "acrylicOpacity": 0.5, + "opacity": 50, "backgroundImage": "made_you_look.jpeg", "backgroundImageStretchMode": "uniformToFill", @@ -167,7 +167,7 @@ namespace SettingsModelLocalTests "experimental.retroTerminalEffect": false })" }; - const std::string smallProfileString{ R"( + static constexpr std::string_view smallProfileString{ R"( { "name": "Custom Profile" })" }; @@ -175,7 +175,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 +192,7 @@ namespace SettingsModelLocalTests void SerializationTests::ColorScheme() { - const std::string schemeString{ R"({ + static constexpr std::string_view schemeString{ R"({ "name": "Campbell", "cursorColor": "#FFFFFF", @@ -225,56 +225,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 +286,7 @@ namespace SettingsModelLocalTests ])" }; // iterable command - const std::string actionsString9A{ R"([ + static constexpr std::string_view actionsString9A{ R"([ { "name": "New tab", "commands": [ @@ -299,7 +299,7 @@ namespace SettingsModelLocalTests ] } ])" }; - const std::string actionsString9B{ R"([ + static constexpr std::string_view actionsString9B{ R"([ { "commands": [ @@ -315,7 +315,7 @@ namespace SettingsModelLocalTests "name": "Send Input ..." } ])" }; - const std::string actionsString9C{ R""([ + static constexpr std::string_view actionsString9C{ R""([ { "commands": [ @@ -338,7 +338,7 @@ namespace SettingsModelLocalTests "name": "Send Input (Evil) ..." } ])"" }; - const std::string actionsString9D{ R""([ + static constexpr std::string_view actionsString9D{ R""([ { "command": { @@ -352,7 +352,7 @@ namespace SettingsModelLocalTests ])"" }; // unbound command - const std::string actionsString10{ R"([ + static constexpr std::string_view actionsString10{ R"([ { "command": "unbound", "keys": "ctrl+c" } ])" }; @@ -395,7 +395,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 +465,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 +474,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 e48bbe4af..b11fec3e7 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 @@ -109,11 +134,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()); @@ -233,11 +257,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()); @@ -359,11 +382,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()); @@ -497,11 +519,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()); @@ -592,11 +613,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()); @@ -716,11 +736,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()); @@ -853,11 +872,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()); @@ -956,11 +974,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()); @@ -1087,9 +1104,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": [ { @@ -1102,10 +1182,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() }; @@ -1132,8 +1208,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 @@ -1141,7 +1215,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); @@ -1155,11 +1229,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); @@ -1173,11 +1247,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); @@ -1191,7 +1265,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/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 350426f92..047750383 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -357,6 +357,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. @@ -394,6 +396,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 diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 17bd61aaa..e121043d1 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -106,6 +106,7 @@ 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(); @@ -132,6 +133,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; diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index cfe93321a..674322d35 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -91,6 +91,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; 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 74bc48bb2..e6462276d 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -143,7 +143,15 @@ NewTerminalArgs Pane::GetTerminalArgsForPane() const auto controlSettings = termControl.Settings().as(); args.Profile(controlSettings.ProfileName()); - args.StartingDirectory(controlSettings.StartingDirectory()); + // If we know the user's working directory use it instead of the profile. + if (const auto dir = _control.WorkingDirectory(); !dir.empty()) + { + args.StartingDirectory(dir); + } + else + { + args.StartingDirectory(controlSettings.StartingDirectory()); + } args.TabTitle(controlSettings.StartingTitle()); args.Commandline(controlSettings.Commandline()); args.SuppressApplicationTitle(controlSettings.SuppressApplicationTitle()); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index dcf8a262f..642a1c687 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -733,10 +733,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 4bfb7bc40..e3f1fcfde 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -20,6 +20,9 @@ #include "ColorHelper.h" #include "DebugTapConnection.h" #include "SettingsTab.h" +#include "..\TerminalSettingsModel\FileUtils.h" + +#include using namespace winrt; using namespace winrt::Windows::Foundation::Collections; @@ -431,33 +434,45 @@ namespace winrt::TerminalApp::implementation // - tab: tab to export winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab) { + // This will be used to set up the file picker "filter", to select .txt + // files by default. + static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = { + { L"Text Files (*.txt)", L"*.txt" }, + { L"All Files (*.*)", L"*.*" } + }; + // An arbitrary GUID to associate with all instances of this + // dialog, so they all re-open in the same path as they were + // open before: + static constexpr winrt::guid clientGuidExportFile{ 0xF6AF20BB, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } }; + try { if (const auto control{ tab.GetActiveTerminalControl() }) { - const FileSavePicker savePicker; - savePicker.as()->Initialize(*_hostingHwnd); - savePicker.SuggestedStartLocation(PickerLocationId::Downloads); - const auto fileChoices = single_threaded_vector({ L".txt" }); - savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices); - savePicker.SuggestedFileName(control.Title()); + // GH#11356 - we can't use the UWP apis for writing the file, + // because they don't work elevated (shocker) So just use the + // shell32 file picker manually. + auto path = co_await SaveFilePicker(*_hostingHwnd, [control](auto&& dialog) { + THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExportFile)); + try + { + // Default to the Downloads folder + auto folderShellItem{ winrt::capture(&SHGetKnownFolderItem, FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr) }; + dialog->SetDefaultFolder(folderShellItem.get()); + } + CATCH_LOG(); // non-fatal + THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes)); + THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed + THROW_IF_FAILED(dialog->SetDefaultExtension(L"txt")); - const StorageFile file = co_await savePicker.PickSaveFileAsync(); - if (file != nullptr) + // Default to using the tab title as the file name + THROW_IF_FAILED(dialog->SetFileName((control.Title() + L".txt").c_str())); + }); + + if (!path.empty()) { const auto buffer = control.ReadEntireBuffer(); - CachedFileManager::DeferUpdates(file); - co_await FileIO::WriteTextAsync(file, buffer); - const auto status = co_await CachedFileManager::CompleteUpdatesAsync(file); - switch (status) - { - case FileUpdateStatus::Complete: - case FileUpdateStatus::CompleteAndRenamed: - _ShowControlNoticeDialog(RS_(L"NoticeInfo"), RS_(L"ExportSuccess")); - break; - default: - _ShowControlNoticeDialog(RS_(L"NoticeError"), RS_(L"ExportFailure")); - } + CascadiaSettings::ExportFile(path, buffer); } } } 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 @@ ()) + { + 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 @@ -3718,6 +3732,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: @@ -3747,14 +3795,21 @@ 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 ba598dad3..fe934a38b 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -95,6 +95,7 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::TaskbarState TaskbarState() const; void ShowKeyboardServiceWarning() const; + void ShowSetAsDefaultInfoBar() const; winrt::hstring KeyboardServiceDisabledText(); winrt::fire_and_forget IdentifyWindow(); @@ -388,7 +389,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); @@ -415,6 +416,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..6c0705437 100644 --- a/src/cascadia/TerminalApp/TerminalPage.xaml +++ b/src/cascadia/TerminalApp/TerminalPage.xaml @@ -15,6 +15,7 @@ + @@ -22,8 +23,50 @@ + + + +