diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 1e33da10d7e..9dc4909592f 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -4,6 +4,7 @@ alignof bitfield bitfields CLASSNOTAVAILABLE +environstrings EXPCMDFLAGS EXPCMDSTATE fullkbd @@ -46,3 +47,4 @@ STDCPP syscall tmp tx +userenv diff --git a/.github/actions/spell-check/expect/af1ff90dc512c83c902762b02f284c1c61603b4a.txt b/.github/actions/spell-check/expect/af1ff90dc512c83c902762b02f284c1c61603b4a.txt new file mode 100644 index 00000000000..66fb99de72d --- /dev/null +++ b/.github/actions/spell-check/expect/af1ff90dc512c83c902762b02f284c1c61603b4a.txt @@ -0,0 +1,10 @@ +abcd +dst +EFG +EFGh +EMPTYBOX +GFEh +nrcs +Remoting +Scs +Shobjidl diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 01b64c42d78..9379acedf80 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -99,7 +99,6 @@ AStomps ASYNCWINDOWPOS atch ATest -atg attr ATTRCOLOR aumid @@ -160,7 +159,6 @@ bitsavers bitset BKCOLOR BKGND -BKMK Bksp blog Blt @@ -295,7 +293,6 @@ codepage codepoint codeproject COINIT -colo colorizing colororacle colorref @@ -352,10 +349,10 @@ conpty conptylib consecteturadipiscingelit conserv -consoleaccessibility consoleapi CONSOLECONTROL CONSOLEENDTASK +consolegit CONSOLEIME consoleinternal Consoleroot @@ -426,6 +423,7 @@ cstdlib cstr cstring cstyle +CSV CSwitch CText ctime @@ -640,7 +638,6 @@ DROPDOWNLIST DROPFILES drv dsm -Dst DSwap DTest dtor @@ -683,7 +680,6 @@ Elems elif elseif emacs -emptybox enabledelayedexpansion endian endif @@ -752,7 +748,6 @@ fdw fea fesb FFDE -FFF FFrom FGCOLOR fgetc @@ -769,7 +764,6 @@ FILETYPE FILEW FILLATTR FILLCONSOLEOUTPUT -Filledbox FILTERONPASTE finalizer FINDCASE @@ -821,7 +815,6 @@ fsproj fstream fte Ftm -fullcolor fullscreen fullwidth func @@ -895,6 +888,7 @@ getwriter Gfun gfx gh +gitfilters github gitlab gle @@ -1011,7 +1005,6 @@ hwheel hwnd HWNDPARENT hxx -hyperlink IAccessibility IAction IApi @@ -1034,7 +1027,6 @@ ICore IData IDCANCEL IDD -IDefault IDesktop IDictionary IDISHWND @@ -1067,6 +1059,8 @@ IInteract IInteractivity IIo IList +imagemagick +Imatch ime Imm IMouse @@ -1083,7 +1077,6 @@ INITCOMMONCONTROLSEX INITDIALOG initguid INITMENU -imagemagick inkscape inl INLINEPREFIX @@ -1384,7 +1377,6 @@ mincore mindbogglingly mingw minkernel -minmax minwin minwindef Mip @@ -1532,8 +1524,8 @@ nothrow NOTICKS NOTIMPL notin -NOTOPMOST NOTNULL +NOTOPMOST NOTRACK NOTSUPPORTED notypeopt @@ -1545,7 +1537,6 @@ NOYIELD NOZORDER NPM npos -NRCS NSTATUS ntapi ntcon @@ -1623,8 +1614,8 @@ OSCBG OSCCT OSCFG OSCRCC -OSCSCC OSCSCB +OSCSCC OSCWT OSDEPENDSROOT osfhandle @@ -1722,7 +1713,6 @@ pguid pgup PHANDLE phhook -phsl phwnd pid pidl @@ -1752,7 +1742,6 @@ POBJECT Podcast POINTSLIST POLYTEXTW -popclip popd POPF poppack @@ -1935,7 +1924,6 @@ REGSTR reingest Relayout RELBINPATH -remoting renderengine rendersize reparent @@ -1943,7 +1931,6 @@ reparenting replatformed Replymessage repositorypath -rerendered rescap Resequence Reserialize @@ -1989,7 +1976,6 @@ RMENU roadmap robomac roundtrip -ROWSTOSCROLL rparen RRF RRRGGGBB @@ -2051,7 +2037,6 @@ SCROLLSCALE SCROLLSCREENBUFFER Scrollup Scrolluppage -SCS scursor sddl sdeleted @@ -2121,7 +2106,6 @@ Shl shlguid shlobj shlwapi -shobjidl SHORTPATH SHOWCURSOR SHOWMAXIMIZED @@ -2194,14 +2178,11 @@ STARTWPARMSW Statusline stdafx STDAPI -stdarg stdcall -stddef stderr stdexcept stdin stdio -stdlib STDMETHODCALLTYPE STDMETHODIMP stdout @@ -2295,7 +2276,6 @@ telnetd telnetpp templated terminalcore -terminalnuget TERMINALSCROLLING terminfo TEs @@ -2351,7 +2331,6 @@ TMult tmultiple tmux todo -Tofill tofrom tokenhelpers tokenized @@ -2362,6 +2341,7 @@ Toolset tooltip TOPDOWNDIB TOPLEFT +toplevel TOPRIGHT TOpt tosign @@ -2447,7 +2427,6 @@ undef Unescape unexpand Unfocus -unfocuses unhighlighting unhosted unicode @@ -2753,33 +2732,26 @@ WWith wx wxh xa -xab xact xamarin xaml Xamlmeta xargs xaz -xb -xbc xbf xbutton XBUTTONDBLCLK XBUTTONDOWN XBUTTONUP -xca XCast -xce XCENTER XColors xcopy XCount -xdd xdy xe XEncoding xff -xffff XFile xlang XManifest diff --git a/OpenConsole.sln b/OpenConsole.sln index 4d0d2e61481..935e252c3f5 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -160,15 +160,11 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalConnection", "src\cascadia\TerminalConnection\TerminalConnection.vcxproj", "{CA5CAD1A-C46D-4588-B1C0-40F31AE9100B}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalCore", "src\cascadia\TerminalCore\lib\TerminalCore-lib.vcxproj", "{CA5CAD1A-ABCD-429C-B551-8562EC954746}" - ProjectSection(ProjectDependencies) = postProject - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} - EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalControl", "src\cascadia\TerminalControl\TerminalControl.vcxproj", "{CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED}" ProjectSection(ProjectDependencies) = postProject {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} {CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746} - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} {1CF55140-EF6A-4736-A403-957E4F7430BB} = {1CF55140-EF6A-4736-A403-957E4F7430BB} {48D21369-3D7B-4431-9967-24E81292CF63} = {48D21369-3D7B-4431-9967-24E81292CF63} EndProjectSection @@ -178,7 +174,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminal", "src\casc {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} {CA5CAD1A-ABCD-429C-B551-8562EC954746} = {CA5CAD1A-ABCD-429C-B551-8562EC954746} - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} EndProjectSection EndProject @@ -187,11 +182,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia {CA5CAD1A-9A12-429C-B551-8562EC954746} = {CA5CAD1A-9A12-429C-B551-8562EC954746} {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} = {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalSettings", "src\cascadia\TerminalSettings\TerminalSettings.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminalShellExt", "src\cascadia\ShellExtension\WindowsTerminalShellExt.vcxproj", "{F2ED628A-DB22-446F-A081-4CC845B51A2B}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalCore", "src\cascadia\UnitTests_TerminalCore\UnitTests.vcxproj", "{2C2BEEF4-9333-4D05-B12A-1905CBF112F9}" @@ -238,7 +230,6 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalAppLib", "src\cascadia\TerminalApp\lib\TerminalAppLib.vcxproj", "{CA5CAD1A-9A12-429C-B551-8562EC954746}" ProjectSection(ProjectDependencies) = postProject {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LocalTests_TerminalApp", "src\cascadia\LocalTests_TerminalApp\TerminalApp.LocalTests.vcxproj", "{CA5CAD1A-B11C-4DDB-A4FE-C3AFAE9B5506}" @@ -1389,35 +1380,6 @@ Global {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x64.Build.0 = Release|x64 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12}.Release|x86.Build.0 = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x64Test.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|DotNet_x86Test.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.ActiveCfg = AuditMode|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x64.Build.0 = AuditMode|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.AuditMode|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|ARM64.Build.0 = Debug|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x64Test.ActiveCfg = Debug|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x64Test.Build.0 = Debug|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|DotNet_x86Test.Build.0 = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.ActiveCfg = Debug|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x64.Build.0 = Debug|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.ActiveCfg = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Debug|x86.Build.0 = Debug|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.ActiveCfg = Release|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|ARM64.Build.0 = Release|ARM64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x64Test.ActiveCfg = Release|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x64Test.Build.0 = Release|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|DotNet_x86Test.Build.0 = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.ActiveCfg = Release|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.Build.0 = Release|x64 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32 - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|Any CPU.ActiveCfg = Release|Win32 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32 @@ -2073,7 +2035,6 @@ Global {CA5CAD1A-44BD-4AC7-AC72-6CA5B3AB89ED} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {59840756-302F-44DF-AA47-441A9D673202} - {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202} {F2ED628A-DB22-446F-A081-4CC845B51A2B} = {59840756-302F-44DF-AA47-441A9D673202} {2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} diff --git a/doc/Niksa.md b/doc/Niksa.md index 4ad72148cf6..e543191a6c7 100644 --- a/doc/Niksa.md +++ b/doc/Niksa.md @@ -9,6 +9,8 @@ This document serves as a storage point for those posts. - [Output Processing between "Far East" and "Western"](#fesb) - [Why do we not backport things?](#backport) - [Why can't we have mixed elevated and non-elevated tabs in the Terminal?](#elevation) +- [What's the difference between a shell and a terminal?](#shell-vs-terminal) + ## Why do we avoid changing CMD.exe? `setlocal` doesn't behave the same way as an environment variable. It's a thing that would have to be put in at the top of the batch script that is `somefile.cmd` as one of its first commands to adjust the way that one specific batch file is processed by the `cmd.exe` engine. That's probably not suitable for your needs, but that's the way we have to go. @@ -179,3 +181,20 @@ Other platforms have accepted that risk in preference for user convenience. They Original Source: https://github.com/microsoft/terminal/issues/632#issuecomment-519375707 +## What's the difference between a shell and a terminal? + +_guest speaker @zadjii-msft_ + +I think there might be a bit of a misunderstanding here - there are two different kinds of applications we're talking about here: +* 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. + +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: + +![image](https://user-images.githubusercontent.com/18356694/89556758-79d27e80-d7d7-11ea-84e2-10710e09ef4a.png) + +It's not the Terminal's responsibility to remember the commands executed by a commandline client. That's the responsibility of the _shell_. How would the terminal remember commands executed by something like `emacs` or `vim`? Those are both applications where the user is typing input and hitting enter, like they would at a cmd prompt, but without something that resembles a command history. + +Original Source: https://github.com/microsoft/terminal/issues/6500#issuecomment-670035468 diff --git a/doc/cascadia/SettingsSchema.md b/doc/cascadia/SettingsSchema.md index 9c58edc89f4..66e5cb67113 100644 --- a/doc/cascadia/SettingsSchema.md +++ b/doc/cascadia/SettingsSchema.md @@ -142,6 +142,7 @@ For commands with arguments: | `scrollUp` | Move the screen up. | | | | | `scrollUpPage` | Move the screen up a whole page. | | | | | `scrollDownPage` | Move the screen down a whole page. | | | | +| `sendInput` | Sends some text input to the shell. | `input` | string | The text input to feed into the shell.
ANSI escape sequences may be used. Escape codes like `\x1b` must be written as `\u001b`.
For instance the input `"text\n"` will write "text" followed by a newline. `"\u001b[D"` will behave as if the left arrow button had been pressed. | | `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*
2. `commandLine`
3. `startingDirectory`
4. `tabTitle`
5. `index`
6. `profile`
7. `splitMode` | 1. `vertical`, `horizontal`, `auto`
2. string
3. string
4. string
5. integer
6. string
7. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.
2. Executable run within the pane.
3. Directory in which the pane will open.
4. Title of the tab when the new pane is focused.
5. Profile that will open based on its position in the dropdown (starting at 0).
6. Profile that will open based on its GUID or name.
7. Controls how the pane splits. Only accepts `duplicate` which will duplicate the focused pane's profile into a new pane. | | `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). | | `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | | diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 0ca40b685bd..e2253724a54 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -52,6 +52,7 @@ "scrollDownPage", "scrollUp", "scrollUpPage", + "sendInput", "splitPane", "switchToTab", "toggleFocusMode", @@ -59,11 +60,15 @@ "toggleAlwaysOnTop", "toggleRetroEffect", "find", + "setColorScheme", "setTabColor", "openTabColorPicker", "renameTab", "commandPalette", "wt", + "closeOtherTabs", + "closeTabsAfter", + "tabSwitcher", "unbound" ], "type": "string" @@ -85,6 +90,14 @@ ], "type": "string" }, + "AnchorKey": { + "enum": [ + "ctrl", + "alt", + "shift" + ], + "type": "string" + }, "NewTerminalArgs": { "properties": { "commandline": { @@ -218,6 +231,28 @@ ], "required": [ "direction" ] }, + "SendInputAction": { + "description": "Arguments corresponding to a Send Input Action", + "allOf": [ + { + "$ref": "#/definitions/ShortcutAction" + }, + { + "properties": { + "action": { + "type": "string", + "pattern": "sendInput" + }, + "input": { + "type": "string", + "default": "", + "description": "The text input to feed into the shell. ANSI escape sequences may be used. Escape codes like \\x1b must be written as \\u001b." + } + } + } + ], + "required": [ "input" ] + }, "SplitPaneAction": { "description": "Arguments corresponding to a Split Pane Action", "allOf": [ @@ -281,6 +316,23 @@ } ] }, + "SetColorSchemeAction": { + "description": "Arguments corresponding to a Set Color Scheme Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "setColorScheme" }, + "colorScheme": { + "type": "string", + "default": "", + "description": "the name of the scheme to apply to the active pane" + } + } + } + ], + "required": [ "name" ] + }, "WtAction": { "description": "Arguments corresponding to a wt Action", "allOf": [ @@ -298,6 +350,56 @@ ], "required": [ "commandline" ] }, + "CloseOtherTabsAction": { + "description": "Arguments for a closeOtherTabs action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "closeOtherTabs" }, + "index": { + "type": "integer", + "default": "", + "description": "close the tabs following the tab at this index" + } + } + } + ], + "required": [ "index" ] + }, + "CloseTabsAfterAction": { + "description": "Arguments for a closeTabsAfter action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "closeTabsAfter" }, + "index": { + "type": "integer", + "default": "", + "description": "close the tabs other than the one at this index" + } + } + } + ], + "required": [ "index" ] + }, + "TabSwitcherAction": { + "description": "Arguments corresponding to a Tab Switcher Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "tabSwitcher" }, + "anchorKey": { + "$ref": "#/definitions/AnchorKey", + "default": null, + "description": "If provided, the tab switcher will stay open as long as the anchor key is held down. The anchor key should be part of the keybinding that opens the switcher." + } + } + } + ] + }, "Keybinding": { "additionalProperties": false, "properties": { @@ -311,10 +413,15 @@ { "$ref": "#/definitions/SwitchToTabAction" }, { "$ref": "#/definitions/MoveFocusAction" }, { "$ref": "#/definitions/ResizePaneAction" }, + { "$ref": "#/definitions/SendInputAction" }, { "$ref": "#/definitions/SplitPaneAction" }, { "$ref": "#/definitions/OpenSettingsAction" }, { "$ref": "#/definitions/SetTabColorAction" }, + { "$ref": "#/definitions/SetColorSchemeAction" }, { "$ref": "#/definitions/WtAction" }, + { "$ref": "#/definitions/CloseOtherTabsAction" }, + { "$ref": "#/definitions/CloseTabsAfterAction" }, + { "$ref": "#/definitions/TabSwitcherAction" }, { "type": "null" } ] }, @@ -678,8 +785,15 @@ "padding": { "default": "8, 8, 8, 8", "description": "Sets the padding around the text within the window. Can have three different formats:\n -\"#\" sets the same padding for all sides \n -\"#, #\" sets the same padding for left-right and top-bottom\n -\"#, #, #, #\" sets the padding individually for left, top, right, and bottom.", - "pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$", - "type": "string" + "oneOf": [ + { + "pattern": "^-?[0-9]+(\\.[0-9]+)?( *, *-?[0-9]+(\\.[0-9]+)?|( *, *-?[0-9]+(\\.[0-9]+)?){3})?$", + "type": "string" + }, + { + "type": "integer" + } + ] }, "scrollbarState": { "default": "visible", diff --git a/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md new file mode 100644 index 00000000000..210db5499c0 --- /dev/null +++ b/doc/specs/#1571 - New Tab Menu Customization/#1571 - New Tab Menu Customization.md @@ -0,0 +1,300 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2020-5-13 +last updated: 2020-08-04 +issue id: 1571 +--- + +# New Tab Menu Customization + +## Abstract + +Many users have lots and _lots_ of profiles that they use. Some of these +profiles the user might not use that frequently. When that happens, the new tab +dropdown can become quite cluttered. + +A common ask is for the ability to reorder and reorganize this dropdown. This +spec provides a design for how the user might be able to specify the +customization in their settings. + +## Inspiration + +Largely, this spec was inspired by discussion in +[#1571](https://github.com/microsoft/terminal/issues/1571#issuecomment-519504048) +and the _many_ linked threads. + +## Solution Design + +This design proposes adding a new setting `"newTabMenu"`. When unset, (the +default), the new tab menu is populated with all the profiles, in the order they +appear in the users settings file. When set, this enables the user to control +the appearance of the new tab dropdown. Let's take a look at an example: + +```json +{ + "profiles":{ ... }, + "newTabMenu": [ + { "type":"profile", "profile": "cmd" }, + { "type":"profile", "profile": "Windows PowerShell" }, + { "type":"separator" }, + { + "type":"folder", + "name": "ssh", + "icon": "C:\\path\\to\\icon.png", + "entries":[ + { "type":"profile", "profile": "Host 1" }, + { "type":"profile", "profile": "8.8.8.8" }, + { "type":"profile", "profile": "Host 2" } + ] + }, + { "type":"separator" }, + { "type":"profile", "profile": "Ubuntu-18.04" }, + { "type":"profile", "profile": "Fedora" } + ] +} +``` + +If a user were to use this as their new tab menu, that they would get is a menu +that looks like this: + +![fig 1](Menu-Customization-000.png) + +_fig 1_: A _very rough_ mockup of what this feature might look like + +There are five `type`s of objects in this menu: +* `"type":"profile"`: This is a profile. Clicking on this entry will open a new + tab, with that profile. The profile is identified with the `"profile"` + parameter, which accepts either a profile `name` or GUID. The icon for this + entry will be the profile's icon, and the text on the entry will be the + profile's name. +* `"type":"separator"`: This represents a XAML `MenuFlyoutSeparator`, enabling + the user to visually space out entries. +* `"type":"folder"`: This represents a nested menu of entries. + - The `"name"` property provides a string of text to display for the group. + - The `"icon"` property provides a path to a image to use as the icon. This + property is optional. + - The `"entries"` property specifies a list of menu entries that will appear + nested under this entry. This can contain other `"type":"folder"` groups as + well! +* `"type":"action"`: This represents a menu entry that should execute a specific + `ShortcutAction`. + - the `id` property will specify the global action ID (see [#6899], [#7175]) + to identify the action to perform when the user selects the entry. Actions + with invalid IDs will be ignored and omitted from the list. + - The text for this entry will be the action's label (which is + either provided as the `"name"` in the global list of actions, or the + generated name if no `name` was provided) + - The icon for this entry will similarly re-use the action's `icon`. +* `"type":"remainingProfiles"`: This is a special type of entry that will be + expanded to contain one `"type":"profile"` entry for every profile that was + not already listed in the menu. This will allow users to add one entry for + just "all the profiles they haven't manually added to the menu". + - This type of entry can only be specified once - trying to add it to the menu + twice will raise a warning, and ignore all but the first `remainingProfiles` + entry. + - This type of entry can also be set inside a `folder` entry, allowing users + to highlight only a couple profiles in the top-level of the menu, but + enabling all other profiles to also be accessible. + - The "name" of these entries will simply be the name of the profile + - The "icon" of these entries will simply be the profile's icon + +The "default" new tab menu could be imagined as the following blob of json: + +```json +{ + "newTabMenu": [ + { "type":"remainingProfiles" } + ] +} +``` + +### Other considerations + +Also considered during the investigation for this feature was re-using the list +of profiles to expose the structure of the new tab menu. For example, doing +something like: + +```json +"profiles": { + "defaults": {}, + "list": + [ + { "name": "cmd" }, + { "name": "powershell" }, + { "type": "separator" }, + { + "type": "folder" , + "profiles": [ + { "name": "ubuntu" } + ] + } + ] +} +``` + +This option was not pursued because we felt that it needlessly complicated the +contents of the list of profiles objects. We'd rather have the `profiles` list +exclusively contain `Profile` objects, and have other elements of the json +_refer_ to those profiles. What if someone would like to have an action that +opened a new tab with profile index 4, and then they set that action as entry 4 +in the profile's list? That would certainly be some sort of unexpected behavior. + +Additionally, what if someone wants to have an entry that opens a tab with one +pane with one profile in it, and another pane with different profile in it? Or +what if they want the same profile to appear twice in the menu? + +By overloading the structure of the `profiles` list, we're forcing all other +consumers of the list of profiles to care about the structure of the elements of +the list. These other consumers should only really care about the list of +profiles, and not necessarily how they're structured in the new tab dropdown. +Furthermore, it complicates the list of profiles, by adding actions intermixed +with the profiles. + +The design chosen in this spec more cleanly separates the responsibilities of +the list of profiles and the contents of the new tab menu. This way, each object +can be defined independent of the structure of the other. + +## UI/UX Design + +See the above _figure 1_. + +The profile's `icon` will also appear as the icon on `profile` entries. If +there's a keybinding bound to open a new tab with that profile, then that will +also be added to the `MenuFlyoutItem` as the accelerator text, similar to the +text we have nowadays. + +Beneath the list of profiles will _always_ be the same "Settings", "Feedback" +and "About" entries, separated by a `MenuFlyoutSeparator`. This is consistent +with the UI as it exists with no customization. These entries cannot be removed +with this feature, only the list of profiles customized. + +## Capabilities + +### Accessibility + +This menu will be added to the XAML tree in the same fashion as the current new +tab flyout, so there should be no dramatic change here. + +### Security + +_(no change expected)_ + +### Reliability + +_(no change expected)_ + +### Compatibility + +_(no change expected)_ + +### Performance, Power, and Efficiency + +_(no change expected)_ + +## Potential Issues + +Currently, the `openTab` and `splitPane` keybindings will accept a `index` +parameter to say either: +* "Create a new tab/pane with the N'th profile" +* "Create a new tab/pane with the profile at index N in the new +tab dropdown". + +These two were previously synonymous, as the N'th profile was always the N'th in +the dropdown. However, with this change, we'll be changing the meaning of that +argument to mean explicitly the first option - "Open a tab/pane with the N'th +profile". + +A previous version of this spec considered changing the meaning of that +parameter to mean "open the entry at index N", the second option. However, in +[Command Palette, Addendum 1], we found that naming that command would become +unnecessarily complex. + +To cover that above scenario, we could consider adding an `index` parameter to +the `openNewTabDropdown` action. If specified, that would open either the N'th +action in the dropdown (ignoring separators), or open the dropdown with the n'th +item selected. + +The N'th entry in the menu won't always be a profile: it might be a folder with +more options, or it might be an action (that might not be opening a new tab/pane +at all). + +Given all the above scenarios, `openNewTabDropdown` with an `"index":N` +parameter will behave in the following ways. If the Nth top-level entry in the +new tab menu is a: +* `"type":"profile"`: perform the `newTab` or `splitPane` action with that profile. +* `"type":"folder"`: Focus the first element in the sub menu, so the user could + navigate it with the keyboard. +* `"type":"separator"`: Ignore these when counting top-level entries. +* `"type":"action"`: Perform the action. + +So for example: + +``` +New Tab Button ▽ +├─ Folder 1 +│ └─ Profile A +│ └─ Action B +├─ Separator +├─ Folder 2 +│ └─ Profile C +│ └─ Profile D +├─ Action E +└─ Profile F +``` + +And assuming the user has bound: +```json +{ + "bindings": + [ + { "command": { "action": "openNewTabDropdown", "index": 0 }, "keys": "ctrl+shift+1" }, + { "command": { "action": "openNewTabDropdown", "index": 1 }, "keys": "ctrl+shift+2" }, + { "command": { "action": "openNewTabDropdown", "index": 2 }, "keys": "ctrl+shift+3" }, + { "command": { "action": "openNewTabDropdown", "index": 3 }, "keys": "ctrl+shift+4" }, + ] +} +``` + +* ctrl+shift+1 focuses "Profile A", but the user needs to press + enter/space to creates a new tab/split +* ctrl+shift+2 focuses "Profile C", but the user needs to press + enter/space to creates a new tab/split +* ctrl+shift+3 performs Action E +* ctrl+shift+4 Creates a new tab/split with Profile F + +## Future considerations + +* The user could set a `"name"`/`"text"`, or `"icon"` property to these menu + items manually, to override the value from the profile or action. These + settings would be totally optional, but it's not unreasonable that someone + might want this. +* We may want to consider adding a default icon for all folders or actions in + the menu. For example, a folder (like 📁) for `folder` entries, or something + like ⚡ for actions. We'll leave these unset by default, and evaluate setting + these icons by default in the future. +* Something considered during review was a way to specify "All my WSL profiles". + Maybe the user wants to have all their profiles generated by the WSL Distro + Generator appear in a "WSL" folder. This would likely require a more elaborate + filtering syntax, to be able to select only profiles where a certain property + has a specific value. Consider the user who has multiple "SSH + me@\.com" profiles, and they want all their "SSH\*" profiles to + appear in an "SSH" folder. This feels out-of-scope for this spec. +* A similar structure could potentially also be used for customizing the context + menu within a control, or the context menu for the tab. (see [#3337]) + - In both of those cases, it might be important to somehow refer to the + context of the current tab or control in the json. Think for example about + "Close tab" or "Close other tabs" - currently, those work by _knowing_ which + tab the "action" is specified for, not by actually using a `closeTab` action. + In the future, they might need to be implemented as something like + - Close Tab: `{ "action": "closeTab", "index": "${selectedTab.index}" }` + - Close Other Tabs: `{ "action": "closeTabs", "otherThan": "${selectedTab.index}" }` + - Close Tabs to the Right: `{ "action": "closeTabs", "after": "${selectedTab.index}" }` + + + +[#2046]: https://github.com/microsoft/terminal/issues/2046 +[Command Palette, Addendum 1]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Unified%20keybindings%20and%20commands%2C%20and%20synthesized%20action%20names.md + +[#3337]: https://github.com/microsoft/terminal/issues/3337 +[#6899]: https://github.com/microsoft/terminal/issues/6899 +[#7175]: https://github.com/microsoft/terminal/issues/7175 diff --git a/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png b/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png new file mode 100644 index 00000000000..54ade166f34 Binary files /dev/null and b/doc/specs/#1571 - New Tab Menu Customization/Menu-Customization-000.png differ diff --git a/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md new file mode 100644 index 00000000000..20760641e7a --- /dev/null +++ b/doc/specs/#6899 - Action IDs/#6899 - Action IDs.md @@ -0,0 +1,228 @@ +--- +author: Mike Griese @zadjii-msft +created on: 2020-07-13 +last updated: 2020-07-22 +issue id: 6899 +--- + +# Action IDs + +## Abstract + +This document is intended to serve as an addition to the [Command Palette Spec], +as well as the [New Tab Menu Customization Spec]. + +As we come to rely more on actions being a mechanism by which the user defines +"do something in the Terminal", we'll want to make it even easier for users to +re-use the actions that they've already defined, as to reduce duplicated json as +much as possible. This spec proposes a mechanism by which actions could be +uniquely identifiable, so that the user could refer to bindings in other +contexts without needing to replicate an entire json blob. + +## Solution Design + +This spec was largely inspired by the following diagram from @DHowett: + +![figure 1](data-mockup.png) + +The goal is to introduce an `id` parameter by which actions could be uniquely +refered to. If we'd ever like to use an action outside the list of `actions`, we +can simply refer to the action's ID, allowing the user to only define the action +_once_. + +We'll start by renaming `bindings` to `actions`. `bindings` was suggested as a +rename for `keybindings` in [#6532], as a way to make the name more generic. +Discussion with the team lead to the understanding that the name `actions` would +be even better, as a way of making the meaning of the "list of actions" more +obvious. + +When we're parsing `actions`, we'll make three passes: +* The first pass will scan the list for objects with an `id` property. We'll + attempt to parse those entries into `ActionAndArgs` which we'll store in the + global `id->ActionAndArgs` map. If any entry doesn't have an `id` set, we'll + skip it in this phase. If an entry doesn't have a `command` set, we'll ignore + it in this pass. +* The second pass will scan for _keybindings_. Any entries with `keys` set will + create a `KeyChord->ActionAndArgs` entry in the keybindings map. If the entry + has an `id` set, then we'll simply re-use the action we've already parsed for + the `id`, from the action map. If there isn't an `id`, then we'll parse the + action manually at this time. Entries without a `keys` set will be ignored in + this pass. +* The final pass will be to generate _commands_. Similar to the keybindings + pass, we'll attempt to lookup actions for entries with an `id` set. If there + isn't an `id`, then we'll parse the action manually at this time. We'll then + get the name for the entry, either from the `name` property if it's set, or + the action's `GenerateName` method. + +For a visual representation, let's assume the user has the following in their +`actions`: + +![figure 2](data-mockup-actions.png) + +We'll first parse the `actions` to generate the mapping of `id`->`Actions`: + +![figure 3](data-mockup-actions-and-ids.png) + +Then, we'll parse the `actions` to generate the mapping of keys to actions, with +some actions already being defined in the map of `id`->`Actions`: + +![figure 4](data-mockup-actions-and-ids-and-keys.png) + + +When layering `actions`, if a later settings file contains an action with the +same `id`, it will replace the current value. In this way, users can redefine +actions, or remove default ones (with something like `{ "id": +"Terminal.OpenTab", "command":null }` + +We'd maintain a large list of default actions, each with unique `id`s set. These +are all given `id`'s with a `Terminal.` prefix, to easily identify them as +built-in, default actions. Not all of these actions will be given keys, but they +will all be given `id`s. + +> 👉 NOTE: The IDs for the default actions will need to be manually created, not +> autogenerated. These `id`s are not strings displayed in the user interface, so +> localization is not a concern. + +As we add additional menus to the Terminal, like the customization for the new +tab dropdown, or the tab context menu, or the `TermControl` context menu, they +could all refer to these actions by `id`, rather than duplicating the same json. + + +### Existing Scenarios + +Keybindings will still be stored as a `keys->Action` mapping, so the user will +still be able to override default keybindings exactly the same as before. + +Similarly, commands in the Command Palette will continue using their existing +`name->Action` mapping they're currently using. For a binding like + +```json +{ "keys": "ctrl+alt+x", "id": "Terminal.OpenDefaultSettings" }, +``` +* We'll bind whatever action is defined as `Terminal.OpenDefaultSettings` to + ctrl+alt+x. +* We'll use whatever action is defined as `Terminal.OpenDefaultSettings` to + generate a name for the command palette. + +### Future Context Menus + +In [New Tab Menu Customization Spec], we discuss allowing the user to bind +actions to the new tab menu. In that spec, they can do so with something like +the following: + +```json +{ + "newTabMenu": [ + { "type":"action", "command": { "action": "adjustFontSize", "delta": 1 }, } + { "type":"action", "command": { "action": "adjustFontSize", "delta": -1 }, } + { "type":"action", "command": "resetFontSize", } + { "type":"profile", "profile": "cmd" }, + { "type":"profile", "profile": "Windows PowerShell" }, + { "type":"separator" }, + { + "type":"folder", + "name": "Settings...", + "icon": "C:\\path\\to\\icon.png", + "entries":[ + { "type":"action", "command": "openSettings" }, + { "type":"action", "command": { "action": "openSettings", "target": "defaultsFile" } }, + ] + } + ] +} +``` + +In this example, the user has also exposed the "Increase font size", "Decrease +font size", and "Reset font size" actions, as well as the settings files in a +submenu. With this proposal, the above could instead be re-written as: + +```json +{ + "newTabMenu": [ + { "type":"action", "id": "Terminal.IncreaseFontSize" }, + { "type":"action", "id": "Terminal.DecreaseFontSize" }, + { "type":"action", "id": "Terminal.ResetFontSize" }, + { "type":"profile", "profile": "cmd" }, + { "type":"profile", "profile": "Windows PowerShell" }, + { "type":"separator" }, + { + "type":"folder", + "name": "Settings...", + "icon": "C:\\path\\to\\icon.png", + "entries":[ + { "type":"action", "id": "Terminal.OpenDefaultSettings" }, + { "type":"action", "id": "Terminal.OpenSettings" }, + ] + } + ] +} +``` + +In this example, the actions are looked up from the global map using the `id` +provided, enabling the user to re-use their existing definitions. If the user +re-defined the `Terminal.IncreaseFontSize` action to mean something else, then +the action in the new tab menu will also be automatically updated. + +Furthermore, when additional menus are added (such as the tab context menu, or +the `TermControl` context menu), these could also leverage a similar syntax to +the above to allow re-use of the `id` parameter. + +Discussion with the team also suggested that users shouldn't be able to define +actions in these menus _at all_. The actions should exclusively be defined in +`actions`, and other menus should only be able to refer to these actions by +`id`. + +## UI/UX Design + +There's not a whole lot of UI for this feature specifically. This is largely +behind-the-scenes refactoring of how actions can be defined. + +## Capabilities + +### Accessibility + +_(not applicable)_ + +### Security + +_(no change expected)_ + +### Reliability + +_(no change expected)_ + +### Compatibility + +_(no change expected)_ + +### Performance, Power, and Efficiency + +_(no change expected)_ + +## Potential Issues + +This won't necessarily play well with iterable commands in the Command Palette, +but that's okay. For iterable commands, users will still need to define the +actions manually. + +## Future considerations + +* See the following issues for other places where this might be useful: + - [#1912] - Context Menu for Tabs + * See also [#5524], [#5025], [#5633] + - [#3337] - Right-click menu inside TerminalControl (w/ Copy & Paste?) + * See also [#5633] and [#5025], both those actions seem reasonable in either + the tab context menu or the control context menu. + + +[Command Palette Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%232046%20-%20Command%20Palette.md +[New Tab Menu Customization Spec]: https://github.com/microsoft/terminal/blob/master/doc/specs/%231571%20-%20New%20Tab%20Menu%20Customization.md + +[#1571]: https://github.com/microsoft/terminal/issues/1571 +[#1912]: https://github.com/microsoft/terminal/issues/1912 +[#3337]: https://github.com/microsoft/terminal/issues/3337 +[#5025]: https://github.com/microsoft/terminal/issues/5025 +[#5524]: https://github.com/microsoft/terminal/issues/5524 +[#5633]: https://github.com/microsoft/terminal/issues/5633 +[#6532]: https://github.com/microsoft/terminal/issues/6532 +[#6899]: https://github.com/microsoft/terminal/issues/6899 diff --git a/doc/specs/#6899 - Action IDs/data-mockup-002.png b/doc/specs/#6899 - Action IDs/data-mockup-002.png new file mode 100644 index 00000000000..1a165d59fd5 Binary files /dev/null and b/doc/specs/#6899 - Action IDs/data-mockup-002.png differ diff --git a/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids-and-keys.png b/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids-and-keys.png new file mode 100644 index 00000000000..f1148daaf3c Binary files /dev/null and b/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids-and-keys.png differ diff --git a/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids.png b/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids.png new file mode 100644 index 00000000000..cf7a031ba99 Binary files /dev/null and b/doc/specs/#6899 - Action IDs/data-mockup-actions-and-ids.png differ diff --git a/doc/specs/#6899 - Action IDs/data-mockup-actions.png b/doc/specs/#6899 - Action IDs/data-mockup-actions.png new file mode 100644 index 00000000000..ef4543f3223 Binary files /dev/null and b/doc/specs/#6899 - Action IDs/data-mockup-actions.png differ diff --git a/doc/specs/#6899 - Action IDs/data-mockup.png b/doc/specs/#6899 - Action IDs/data-mockup.png new file mode 100644 index 00000000000..dc16b2c4c2d Binary files /dev/null and b/doc/specs/#6899 - Action IDs/data-mockup.png differ diff --git a/src/buffer/out/TextAttribute.cpp b/src/buffer/out/TextAttribute.cpp index ad7ee0546e7..06c94184432 100644 --- a/src/buffer/out/TextAttribute.cpp +++ b/src/buffer/out/TextAttribute.cpp @@ -249,6 +249,11 @@ bool TextAttribute::IsUnderlined() const noexcept return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::Underlined); } +bool TextAttribute::IsDoublyUnderlined() const noexcept +{ + return WI_IsFlagSet(_extendedAttrs, ExtendedAttributes::DoublyUnderlined); +} + bool TextAttribute::IsOverlined() const noexcept { return WI_IsFlagSet(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL); @@ -294,6 +299,11 @@ void TextAttribute::SetUnderlined(bool isUnderlined) noexcept WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::Underlined, isUnderlined); } +void TextAttribute::SetDoublyUnderlined(bool isDoublyUnderlined) noexcept +{ + WI_UpdateFlag(_extendedAttrs, ExtendedAttributes::DoublyUnderlined, isDoublyUnderlined); +} + void TextAttribute::SetOverlined(bool isOverlined) noexcept { WI_UpdateFlag(_wAttrLegacy, COMMON_LVB_GRID_HORIZONTAL, isOverlined); diff --git a/src/buffer/out/TextAttribute.hpp b/src/buffer/out/TextAttribute.hpp index 7cec03f75e6..22c61a6aa91 100644 --- a/src/buffer/out/TextAttribute.hpp +++ b/src/buffer/out/TextAttribute.hpp @@ -95,6 +95,7 @@ class TextAttribute final bool IsInvisible() const noexcept; bool IsCrossedOut() const noexcept; bool IsUnderlined() const noexcept; + bool IsDoublyUnderlined() const noexcept; bool IsOverlined() const noexcept; bool IsReverseVideo() const noexcept; @@ -105,6 +106,7 @@ class TextAttribute final void SetInvisible(bool isInvisible) noexcept; void SetCrossedOut(bool isCrossedOut) noexcept; void SetUnderlined(bool isUnderlined) noexcept; + void SetDoublyUnderlined(bool isDoublyUnderlined) noexcept; void SetOverlined(bool isOverlined) noexcept; void SetReverseVideo(bool isReversed) noexcept; diff --git a/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp b/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp index 1babcf6a2c8..28e53cdf5a0 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandTests.cpp @@ -10,7 +10,7 @@ using namespace Microsoft::Console; using namespace TerminalApp; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; diff --git a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp index ad51cf8269b..b7f48b42027 100644 --- a/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/KeyBindingsTests.cpp @@ -11,7 +11,7 @@ using namespace Microsoft::Console; using namespace TerminalApp; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index f1eeddc2641..abd710a6833 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -16,7 +16,7 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; namespace TerminalAppLocalTests { diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj index 8faeef38f1d..b99a6146693 100644 --- a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj @@ -79,7 +79,6 @@ - diff --git a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj index c1745442044..f035d8a74d1 100644 --- a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj @@ -45,11 +45,11 @@ "taef.png" is actually in the package. taef.png will get copied to the OutputPath when taef is run, but if this isn't set to false, then the CI will try and make sure taef.png is in the OutputPath at build time.--> - + false - + 4453;%(DisableSpecificWarnings) @@ -94,7 +94,6 @@ {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} - diff --git a/src/cascadia/LocalTests_TerminalApp/TestUtils.h b/src/cascadia/LocalTests_TerminalApp/TestUtils.h index ea1df508e1f..c34552b019a 100644 --- a/src/cascadia/LocalTests_TerminalApp/TestUtils.h +++ b/src/cascadia/LocalTests_TerminalApp/TestUtils.h @@ -24,18 +24,18 @@ class TerminalAppLocalTests::TestUtils // Return Value: // - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it. static const winrt::TerminalApp::ActionAndArgs GetActionAndArgs(const winrt::TerminalApp::implementation::AppKeyBindings& bindings, - const winrt::Microsoft::Terminal::Settings::KeyChord& kc) + const winrt::Microsoft::Terminal::TerminalControl::KeyChord& kc) { std::wstring buffer{ L"" }; - if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Ctrl)) + if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Ctrl)) { buffer += L"Ctrl+"; } - if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Shift)) + if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Shift)) { buffer += L"Shift+"; } - if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::Settings::KeyModifiers::Alt)) + if (WI_IsFlagSet(kc.Modifiers(), winrt::Microsoft::Terminal::TerminalControl::KeyModifiers::Alt)) { buffer += L"Alt+"; } diff --git a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp index bb7112efb1b..c194e5d77a8 100644 --- a/src/cascadia/PublicTerminalCore/HwndTerminal.cpp +++ b/src/cascadia/PublicTerminalCore/HwndTerminal.cpp @@ -16,6 +16,10 @@ using namespace ::Microsoft::Terminal::Core; static LPCWSTR term_window_class = L"HwndTerminalClass"; +// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx +// "If the high-order bit is 1, the key is down; otherwise, it is up." +static constexpr short KeyPressed{ gsl::narrow_cast(0x8000) }; + static constexpr bool _IsMouseMessage(UINT uMsg) { return uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONUP || uMsg == WM_LBUTTONDBLCLK || @@ -645,7 +649,13 @@ try cursorPosition = coordsToTransform; } - return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta); + const TerminalInput::MouseButtonState state{ + WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed), + WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed), + WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed) + }; + + return _terminal->SendMouseEvent(cursorPosition / fontSize, uMsg, getControlKeyState(), wheelDelta, state); } catch (...) { diff --git a/src/cascadia/TerminalApp/ActionAndArgs.cpp b/src/cascadia/TerminalApp/ActionAndArgs.cpp index f28bfcca8a6..150a31c92ea 100644 --- a/src/cascadia/TerminalApp/ActionAndArgs.cpp +++ b/src/cascadia/TerminalApp/ActionAndArgs.cpp @@ -27,7 +27,9 @@ static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" }; static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" }; static constexpr std::string_view SwitchToTabKey{ "switchToTab" }; static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings +static constexpr std::string_view SendInputKey{ "sendInput" }; static constexpr std::string_view SplitPaneKey{ "splitPane" }; +static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" }; static constexpr std::string_view ResizePaneKey{ "resizePane" }; static constexpr std::string_view MoveFocusKey{ "moveFocus" }; static constexpr std::string_view FindKey{ "find" }; @@ -35,11 +37,15 @@ static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" }; static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" }; static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" }; static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" }; +static constexpr std::string_view SetColorSchemeKey{ "setColorScheme" }; static constexpr std::string_view SetTabColorKey{ "setTabColor" }; static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" }; static constexpr std::string_view RenameTabKey{ "renameTab" }; static constexpr std::string_view ExecuteCommandlineKey{ "wt" }; static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" }; +static constexpr std::string_view CloseOtherTabsKey{ "closeOtherTabs" }; +static constexpr std::string_view CloseTabsAfterKey{ "closeTabsAfter" }; +static constexpr std::string_view ToggleTabSwitcherKey{ "tabSwitcher" }; static constexpr std::string_view ActionKey{ "action" }; @@ -80,11 +86,14 @@ namespace winrt::TerminalApp::implementation { ResizePaneKey, ShortcutAction::ResizePane }, { MoveFocusKey, ShortcutAction::MoveFocus }, { OpenSettingsKey, ShortcutAction::OpenSettings }, + { SetColorSchemeKey, ShortcutAction::SetColorScheme }, { ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect }, { ToggleFocusModeKey, ShortcutAction::ToggleFocusMode }, { ToggleFullscreenKey, ShortcutAction::ToggleFullscreen }, { ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop }, + { SendInputKey, ShortcutAction::SendInput }, { SplitPaneKey, ShortcutAction::SplitPane }, + { TogglePaneZoomKey, ShortcutAction::TogglePaneZoom }, { SetTabColorKey, ShortcutAction::SetTabColor }, { OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker }, { UnboundKey, ShortcutAction::Invalid }, @@ -92,6 +101,9 @@ namespace winrt::TerminalApp::implementation { RenameTabKey, ShortcutAction::RenameTab }, { ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline }, { ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette }, + { CloseOtherTabsKey, ShortcutAction::CloseOtherTabs }, + { CloseTabsAfterKey, ShortcutAction::CloseTabsAfter }, + { ToggleTabSwitcherKey, ShortcutAction::ToggleTabSwitcher }, }; using ParseResult = std::tuple>; @@ -115,16 +127,26 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::AdjustFontSize, winrt::TerminalApp::implementation::AdjustFontSizeArgs::FromJson }, + { ShortcutAction::SendInput, winrt::TerminalApp::implementation::SendInputArgs::FromJson }, + { ShortcutAction::SplitPane, winrt::TerminalApp::implementation::SplitPaneArgs::FromJson }, { ShortcutAction::OpenSettings, winrt::TerminalApp::implementation::OpenSettingsArgs::FromJson }, + { ShortcutAction::SetColorScheme, winrt::TerminalApp::implementation::SetColorSchemeArgs::FromJson }, + { ShortcutAction::SetTabColor, winrt::TerminalApp::implementation::SetTabColorArgs::FromJson }, { ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson }, { ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson }, + { ShortcutAction::CloseOtherTabs, winrt::TerminalApp::implementation::CloseOtherTabsArgs::FromJson }, + + { ShortcutAction::CloseTabsAfter, winrt::TerminalApp::implementation::CloseTabsAfterArgs::FromJson }, + + { ShortcutAction::ToggleTabSwitcher, winrt::TerminalApp::implementation::ToggleTabSwitcherArgs::FromJson }, + { ShortcutAction::Invalid, nullptr }, }; @@ -266,14 +288,19 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") }, { ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") }, { ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") }, + { ShortcutAction::SendInput, L"" }, { ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") }, + { ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") }, { ShortcutAction::Invalid, L"" }, { ShortcutAction::Find, RS_(L"FindCommandKey") }, + { ShortcutAction::SetColorScheme, L"" }, { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") }, { ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") }, { ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") }, { ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") }, { ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") }, + { ShortcutAction::CloseOtherTabs, L"" }, // Intentionally omitted, must be generated by GenerateName + { ShortcutAction::CloseTabsAfter, L"" }, // Intentionally omitted, must be generated by GenerateName }; }(); diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp index a5a83203afa..1bc3983481d 100644 --- a/src/cascadia/TerminalApp/ActionArgs.cpp +++ b/src/cascadia/TerminalApp/ActionArgs.cpp @@ -13,11 +13,16 @@ #include "ResizePaneArgs.g.cpp" #include "MoveFocusArgs.g.cpp" #include "AdjustFontSizeArgs.g.cpp" +#include "SendInputArgs.g.cpp" #include "SplitPaneArgs.g.cpp" #include "OpenSettingsArgs.g.cpp" +#include "SetColorSchemeArgs.g.cpp" #include "SetTabColorArgs.g.cpp" #include "RenameTabArgs.g.cpp" #include "ExecuteCommandlineArgs.g.cpp" +#include "ToggleTabSwitcherArgs.g.h" + +#include "Utils.h" #include @@ -165,6 +170,16 @@ namespace winrt::TerminalApp::implementation } } + winrt::hstring SendInputArgs::GenerateName() const + { + // The string will be similar to the following: + // * "Send Input: ...input..." + + auto escapedInput = VisualizeControlCodes(_Input); + auto name = fmt::format(std::wstring_view(RS_(L"SendInputCommandKey")), escapedInput); + return winrt::hstring{ name }; + } + winrt::hstring SplitPaneArgs::GenerateName() const { // The string will be similar to the following: @@ -229,6 +244,19 @@ namespace winrt::TerminalApp::implementation } } + winrt::hstring SetColorSchemeArgs::GenerateName() const + { + // "Set color scheme to "{_SchemeName}"" + if (!_SchemeName.empty()) + { + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"SetColorSchemeCommandKey")), + _SchemeName.c_str()) + }; + } + return L""; + } + winrt::hstring SetTabColorArgs::GenerateName() const { // "Set tab color to #RRGGBB" @@ -272,4 +300,39 @@ namespace winrt::TerminalApp::implementation return L""; } + winrt::hstring CloseOtherTabsArgs::GenerateName() const + { + // "Close tabs other than index {0}" + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"CloseOtherTabsCommandKey")), + _Index) + }; + } + + winrt::hstring CloseTabsAfterArgs::GenerateName() const + { + // "Close tabs after index {0}" + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"CloseTabsAfterCommandKey")), + _Index) + }; + } + + winrt::hstring ToggleTabSwitcherArgs::GenerateName() const + { + // If there's an anchor key set, don't generate a name so that + // it won't show up in the command palette. Only an unanchored + // tab switcher should be able to be toggled from the palette. + // TODO: GH#7179 - once this goes in, make sure to hide the + // anchor mode command that was given a name in settings. + if (_AnchorKey != Windows::System::VirtualKey::None) + { + return L""; + } + else + { + return RS_(L"ToggleTabSwitcherCommandKey"); + } + } + } diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index 091774230d1..d52cc3f4494 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -13,11 +13,16 @@ #include "ResizePaneArgs.g.h" #include "MoveFocusArgs.g.h" #include "AdjustFontSizeArgs.g.h" +#include "SendInputArgs.g.h" #include "SplitPaneArgs.g.h" #include "OpenSettingsArgs.g.h" +#include "SetColorSchemeArgs.g.h" #include "SetTabColorArgs.g.h" #include "RenameTabArgs.g.h" #include "ExecuteCommandlineArgs.g.h" +#include "CloseOtherTabsArgs.g.h" +#include "CloseTabsAfterArgs.g.h" +#include "ToggleTabSwitcherArgs.g.h" #include "../../cascadia/inc/cppwinrt_utils.h" #include "Utils.h" @@ -266,6 +271,37 @@ namespace winrt::TerminalApp::implementation } }; + struct SendInputArgs : public SendInputArgsT + { + SendInputArgs() = default; + GETSET_PROPERTY(winrt::hstring, Input, L""); + + static constexpr std::string_view InputKey{ "input" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + if (auto otherAsUs = other.try_as(); otherAsUs) + { + return otherAsUs->_Input == _Input; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, InputKey, args->_Input); + if (args->_Input.empty()) + { + return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } }; + } + return { *args, {} }; + } + }; + struct SplitPaneArgs : public SplitPaneArgsT { SplitPaneArgs() = default; @@ -330,6 +366,38 @@ namespace winrt::TerminalApp::implementation } }; + struct SetColorSchemeArgs : public SetColorSchemeArgsT + { + SetColorSchemeArgs() = default; + GETSET_PROPERTY(winrt::hstring, SchemeName, L""); + + static constexpr std::string_view NameKey{ "colorScheme" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_SchemeName == _SchemeName; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, NameKey, args->_SchemeName); + if (args->_SchemeName.empty()) + { + return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } }; + } + return { *args, {} }; + } + }; + struct SetTabColorArgs : public SetTabColorArgsT { SetTabColorArgs() = default; @@ -421,6 +489,89 @@ namespace winrt::TerminalApp::implementation } }; + struct CloseOtherTabsArgs : public CloseOtherTabsArgsT + { + CloseOtherTabsArgs() = default; + GETSET_PROPERTY(uint32_t, Index, 0); + + static constexpr std::string_view IndexKey{ "index" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Index == _Index; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, IndexKey, args->_Index); + return { *args, {} }; + } + }; + + struct CloseTabsAfterArgs : public CloseTabsAfterArgsT + { + CloseTabsAfterArgs() = default; + GETSET_PROPERTY(uint32_t, Index, 0); + + static constexpr std::string_view IndexKey{ "index" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Index == _Index; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, IndexKey, args->_Index); + return { *args, {} }; + } + }; + + struct ToggleTabSwitcherArgs : public ToggleTabSwitcherArgsT + { + ToggleTabSwitcherArgs() = default; + GETSET_PROPERTY(Windows::System::VirtualKey, AnchorKey, Windows::System::VirtualKey::None); + + static constexpr std::string_view AnchorJsonKey{ "anchorKey" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_AnchorKey == _AnchorKey; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, AnchorJsonKey, args->_AnchorKey); + return { *args, {} }; + } + }; } namespace winrt::TerminalApp::factory_implementation diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index 84e5588bf2d..ee50771eb94 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -94,6 +94,11 @@ namespace TerminalApp Int32 Delta { get; }; }; + [default_interface] runtimeclass SendInputArgs : IActionArgs + { + String Input { get; }; + }; + [default_interface] runtimeclass SplitPaneArgs : IActionArgs { SplitState SplitStyle { get; }; @@ -106,6 +111,11 @@ namespace TerminalApp SettingsTarget Target { get; }; }; + [default_interface] runtimeclass SetColorSchemeArgs : IActionArgs + { + String SchemeName { get; }; + }; + [default_interface] runtimeclass SetTabColorArgs : IActionArgs { Windows.Foundation.IReference TabColor { get; }; @@ -120,4 +130,19 @@ namespace TerminalApp { String Commandline; }; + + [default_interface] runtimeclass CloseOtherTabsArgs : IActionArgs + { + UInt32 Index { get; }; + }; + + [default_interface] runtimeclass CloseTabsAfterArgs : IActionArgs + { + UInt32 Index { get; }; + }; + + [default_interface] runtimeclass ToggleTabSwitcherArgs : IActionArgs + { + Windows.System.VirtualKey AnchorKey { get; }; + }; } diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 63a2743d80b..80a06e733de 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -14,7 +14,6 @@ using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::System; using namespace winrt::Microsoft::Terminal; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace ::TerminalApp; @@ -90,6 +89,21 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } + void TerminalPage::_HandleSendInput(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (args == nullptr) + { + args.Handled(false); + } + else if (const auto& realArgs = args.ActionArgs().try_as()) + { + const auto termControl = _GetActiveControl(); + termControl.SendInput(realArgs.Input()); + args.Handled(true); + } + } + void TerminalPage::_HandleSplitPane(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { @@ -104,6 +118,26 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + auto activeTab = _GetFocusedTab(); + if (activeTab) + { + // First thing's first, remove the current content from the UI + // tree. This is important, because we might be leaving zoom, and if + // a pane is zoomed, then it's currently in the UI tree, and should + // be removed before it's re-added in Pane::Restore + _tabContent.Children().Clear(); + + activeTab->ToggleZoom(); + + // Update the selected tab, to trigger us to re-add the tab's GetRootElement to the UI tree + _UpdatedSelectedTab(_tabView.SelectedIndex()); + } + args.Handled(true); + } + void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { @@ -266,12 +300,34 @@ namespace winrt::TerminalApp::implementation { // TODO GH#6677: When we add support for commandline mode, first set the // mode that the command palette should be in, before making it visible. + CommandPalette().EnableCommandPaletteMode(); CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ? Visibility::Collapsed : Visibility::Visible); args.Handled(true); } + void TerminalPage::_HandleSetColorScheme(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + args.Handled(false); + if (const auto& realArgs = args.ActionArgs().try_as()) + { + if (auto activeTab = _GetFocusedTab()) + { + if (auto activeControl = activeTab->GetActiveTerminalControl()) + { + auto controlSettings = activeControl.Settings(); + if (_settings->ApplyColorScheme(controlSettings, realArgs.SchemeName())) + { + activeControl.UpdateSettings(controlSettings); + args.Handled(true); + } + } + } + } + } + void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { @@ -290,11 +346,11 @@ namespace winrt::TerminalApp::implementation { if (tabColor.has_value()) { - activeTab->SetTabColor(tabColor.value()); + activeTab->SetRuntimeTabColor(tabColor.value()); } else { - activeTab->ResetTabColor(); + activeTab->ResetRuntimeTabColor(); } } args.Handled(true); @@ -351,4 +407,74 @@ namespace winrt::TerminalApp::implementation } } } + + void TerminalPage::_HandleCloseOtherTabs(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& actionArgs) + { + if (const auto& realArgs = actionArgs.ActionArgs().try_as()) + { + uint32_t index = realArgs.Index(); + + // Remove tabs after the current one + while (_tabs.Size() > index + 1) + { + _RemoveTabViewItemByIndex(_tabs.Size() - 1); + } + + // Remove all of them leading up to the selected tab + while (_tabs.Size() > 1) + { + _RemoveTabViewItemByIndex(0); + } + + actionArgs.Handled(true); + } + } + + void TerminalPage::_HandleCloseTabsAfter(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& actionArgs) + { + if (const auto& realArgs = actionArgs.ActionArgs().try_as()) + { + uint32_t index = realArgs.Index(); + + // Remove tabs after the current one + while (_tabs.Size() > index + 1) + { + _RemoveTabViewItemByIndex(_tabs.Size() - 1); + } + + // TODO:GH#7182 For whatever reason, if you run this action + // when the tab that's currently focused is _before_ the `index` + // param, then the tabs will expand to fill the entire width of the + // tab row, until you mouse over them. Probably has something to do + // with tabs not resizing down until there's a mouse exit event. + + actionArgs.Handled(true); + } + } + + void TerminalPage::_HandleToggleTabSwitcher(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + if (const auto& realArgs = args.ActionArgs().try_as()) + { + auto anchorKey = realArgs.AnchorKey(); + + auto opt = _GetFocusedTabIndex(); + uint32_t startIdx = opt ? *opt : 0; + + if (anchorKey != VirtualKey::None) + { + // TODO: GH#7178 - delta should also have the option of being -1, in the case when + // a user decides to open the tab switcher going to the prev tab. + int delta = 1; + startIdx = (startIdx + _tabs.Size() + delta) % _tabs.Size(); + } + + CommandPalette().EnableTabSwitcherMode(anchorKey, startIdx); + CommandPalette().Visibility(Visibility::Visible); + } + args.Handled(true); + } } diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index 58821ac5a6a..4ac3f012c2c 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -9,12 +9,12 @@ using namespace winrt::Microsoft::Terminal; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; namespace winrt::TerminalApp::implementation { void AppKeyBindings::SetKeyBinding(const TerminalApp::ActionAndArgs& actionAndArgs, - const Settings::KeyChord& chord) + const KeyChord& chord) { _keyShortcuts[chord] = actionAndArgs; } @@ -25,7 +25,7 @@ namespace winrt::TerminalApp::implementation // - chord: the keystroke to remove the action for. // Return Value: // - - void AppKeyBindings::ClearKeyBinding(const Settings::KeyChord& chord) + void AppKeyBindings::ClearKeyBinding(const KeyChord& chord) { _keyShortcuts.erase(chord); } @@ -67,7 +67,7 @@ namespace winrt::TerminalApp::implementation return { nullptr }; } - bool AppKeyBindings::TryKeyChord(const Settings::KeyChord& kc) + bool AppKeyBindings::TryKeyChord(const KeyChord& kc) { const auto keyIter = _keyShortcuts.find(kc); if (keyIter != _keyShortcuts.end()) @@ -87,19 +87,19 @@ namespace winrt::TerminalApp::implementation // - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML // Return Value: // - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used. - Windows::System::VirtualKeyModifiers AppKeyBindings::ConvertVKModifiers(Settings::KeyModifiers modifiers) + Windows::System::VirtualKeyModifiers AppKeyBindings::ConvertVKModifiers(KeyModifiers modifiers) { Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None; - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Ctrl)) + if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl)) { keyModifiers |= Windows::System::VirtualKeyModifiers::Control; } - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Shift)) + if (WI_IsFlagSet(modifiers, KeyModifiers::Shift)) { keyModifiers |= Windows::System::VirtualKeyModifiers::Shift; } - if (WI_IsFlagSet(modifiers, Settings::KeyModifiers::Alt)) + if (WI_IsFlagSet(modifiers, KeyModifiers::Alt)) { // note: Menu is the Alt VK_MENU keyModifiers |= Windows::System::VirtualKeyModifiers::Menu; diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index 63df542a75a..4feb7e8968a 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -20,10 +20,10 @@ namespace winrt::TerminalApp::implementation { struct KeyChordHash { - std::size_t operator()(const winrt::Microsoft::Terminal::Settings::KeyChord& key) const + std::size_t operator()(const winrt::Microsoft::Terminal::TerminalControl::KeyChord& key) const { std::hash keyHash; - std::hash modifiersHash; + std::hash modifiersHash; std::size_t hashedKey = keyHash(key.Vkey()); std::size_t hashedMods = modifiersHash(key.Modifiers()); return hashedKey ^ hashedMods; @@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation struct KeyChordEquality { - bool operator()(const winrt::Microsoft::Terminal::Settings::KeyChord& lhs, const winrt::Microsoft::Terminal::Settings::KeyChord& rhs) const + bool operator()(const winrt::Microsoft::Terminal::TerminalControl::KeyChord& lhs, const winrt::Microsoft::Terminal::TerminalControl::KeyChord& rhs) const { return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey(); } @@ -42,15 +42,15 @@ namespace winrt::TerminalApp::implementation { AppKeyBindings() = default; - bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc); + bool TryKeyChord(winrt::Microsoft::Terminal::TerminalControl::KeyChord const& kc); void SetKeyBinding(TerminalApp::ActionAndArgs const& actionAndArgs, - winrt::Microsoft::Terminal::Settings::KeyChord const& chord); - void ClearKeyBinding(winrt::Microsoft::Terminal::Settings::KeyChord const& chord); - Microsoft::Terminal::Settings::KeyChord GetKeyBindingForAction(TerminalApp::ShortcutAction const& action); - Microsoft::Terminal::Settings::KeyChord GetKeyBindingForActionWithArgs(TerminalApp::ActionAndArgs const& actionAndArgs); + winrt::Microsoft::Terminal::TerminalControl::KeyChord const& chord); + void ClearKeyBinding(winrt::Microsoft::Terminal::TerminalControl::KeyChord const& chord); + Microsoft::Terminal::TerminalControl::KeyChord GetKeyBindingForAction(TerminalApp::ShortcutAction const& action); + Microsoft::Terminal::TerminalControl::KeyChord GetKeyBindingForActionWithArgs(TerminalApp::ActionAndArgs const& actionAndArgs); - static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::Settings::KeyModifiers modifiers); + static Windows::System::VirtualKeyModifiers ConvertVKModifiers(winrt::Microsoft::Terminal::TerminalControl::KeyModifiers modifiers); // Defined in AppKeyBindingsSerialization.cpp std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(const Json::Value& json); @@ -59,7 +59,7 @@ namespace winrt::TerminalApp::implementation void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); private: - std::unordered_map _keyShortcuts; + std::unordered_map _keyShortcuts; winrt::TerminalApp::ShortcutActionDispatch _dispatch{ nullptr }; diff --git a/src/cascadia/TerminalApp/AppKeyBindings.idl b/src/cascadia/TerminalApp/AppKeyBindings.idl index db287b79267..0dcba935591 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.idl +++ b/src/cascadia/TerminalApp/AppKeyBindings.idl @@ -5,15 +5,15 @@ import "../ShortcutActionDispatch.idl"; namespace TerminalApp { - [default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings + [default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.TerminalControl.IKeyBindings { AppKeyBindings(); - void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.Settings.KeyChord chord); - void ClearKeyBinding(Microsoft.Terminal.Settings.KeyChord chord); + void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.TerminalControl.KeyChord chord); + void ClearKeyBinding(Microsoft.Terminal.TerminalControl.KeyChord chord); - Microsoft.Terminal.Settings.KeyChord GetKeyBindingForAction(ShortcutAction action); - Microsoft.Terminal.Settings.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs); + Microsoft.Terminal.TerminalControl.KeyChord GetKeyBindingForAction(ShortcutAction action); + Microsoft.Terminal.TerminalControl.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs); void SetDispatch(ShortcutActionDispatch dispatch); } diff --git a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp index 6be18ca683e..2bb507ec86f 100644 --- a/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindingsSerialization.cpp @@ -12,9 +12,8 @@ #include "KeyChordSerialization.h" #include "Utils.h" #include "JsonUtils.h" -#include -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::TerminalApp; static constexpr std::string_view KeysKey{ "keys" }; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 5a6ec188703..9b6c3eddc77 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -15,7 +15,6 @@ using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::System; using namespace winrt::Microsoft::Terminal; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace ::TerminalApp; @@ -633,6 +632,12 @@ namespace winrt::TerminalApp::implementation hr = E_INVALIDARG; _settingsLoadExceptionText = _GetErrorText(ex.Error()); } + catch (const ::TerminalApp::SettingsTypedDeserializationException& e) + { + hr = E_INVALIDARG; + std::string_view what{ e.what() }; + _settingsLoadExceptionText = til::u8u16(what); + } catch (...) { hr = wil::ResultFromCaughtException(); diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp index 96e947e952d..182ae429b2d 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp @@ -17,7 +17,6 @@ #include "WslDistroGenerator.h" #include "AzureCloudShellGenerator.h" -using namespace winrt::Microsoft::Terminal::Settings; using namespace ::TerminalApp; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::TerminalApp; @@ -226,9 +225,12 @@ void CascadiaSettings::_ValidateProfilesHaveGuid() void CascadiaSettings::_ResolveDefaultProfile() { const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() }; - auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) }; - auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) }; - GlobalSettings().DefaultProfile(defaultProfileGuid); + if (unparsedDefaultProfile) + { + auto maybeParsedDefaultProfile{ _GetProfileGuidByName(*unparsedDefaultProfile) }; + auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, GUID{}) }; + GlobalSettings().DefaultProfile(defaultProfileGuid); + } } // Method Description: @@ -566,7 +568,7 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs profileByName = _GetProfileGuidByName(newTerminalArgs.Profile()); } - return Utils::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile()); + return til::coalesce_value(profileByName, profileByIndex, _globals.DefaultProfile()); } // Method Description: @@ -745,3 +747,26 @@ const ColorScheme* CascadiaSettings::GetColorSchemeForProfile(const GUID profile return nullptr; } } + +// Method Description: +// - Apply the color scheme (provided by name) to the given IControlSettings. +// The settings are modified in-place. +// - If the name doesn't correspond to any of our schemes, this does nothing. +// Arguments: +// - settings: the IControlSettings object to modify +// - name: the name of the scheme to apply +// Return Value: +// - true iff we found a matching scheme for the name schemeName +bool CascadiaSettings::ApplyColorScheme(winrt::Microsoft::Terminal::TerminalControl::IControlSettings& settings, + std::wstring_view schemeName) +{ + std::wstring name{ schemeName }; + auto schemeAndName = _globals.GetColorSchemes().find(name); + if (schemeAndName != _globals.GetColorSchemes().end()) + { + const auto& scheme = schemeAndName->second; + scheme.ApplyScheme(settings); + return true; + } + return false; +} diff --git a/src/cascadia/TerminalApp/CascadiaSettings.h b/src/cascadia/TerminalApp/CascadiaSettings.h index 08a65fba5d7..a4b61893112 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.h +++ b/src/cascadia/TerminalApp/CascadiaSettings.h @@ -39,9 +39,17 @@ namespace TerminalAppUnitTests namespace TerminalApp { + class SettingsTypedDeserializationException; class CascadiaSettings; }; +class TerminalApp::SettingsTypedDeserializationException final : public std::runtime_error +{ +public: + SettingsTypedDeserializationException(const std::string_view description) : + runtime_error(description.data()) {} +}; + class TerminalApp::CascadiaSettings final { public: @@ -74,6 +82,8 @@ class TerminalApp::CascadiaSettings final std::vector& GetWarnings(); + bool ApplyColorScheme(winrt::Microsoft::Terminal::TerminalControl::IControlSettings& settings, std::wstring_view schemeName); + private: GlobalAppSettings _globals; std::vector _profiles; diff --git a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp index 1a8f2b6e71f..bba84551f4d 100644 --- a/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettingsSerialization.cpp @@ -42,6 +42,54 @@ static constexpr std::string_view Utf8Bom{ u8"\uFEFF" }; static constexpr std::string_view SettingsSchemaFragment{ "\n" R"( "$schema": "https://aka.ms/terminal-profiles-schema")" }; +static std::tuple _LineAndColumnFromPosition(const std::string_view string, ptrdiff_t position) +{ + size_t line = 1, column = position + 1; + auto lastNL = string.find_last_of('\n', position); + if (lastNL != std::string::npos) + { + column = (position - lastNL); + line = std::count(string.cbegin(), string.cbegin() + lastNL + 1, '\n') + 1; + } + + return { line, column }; +} + +static void _CatchRethrowSerializationExceptionWithLocationInfo(std::string_view settingsString) +{ + std::string msg; + + try + { + throw; + } + catch (const JsonUtils::DeserializationError& e) + { + static constexpr std::string_view basicHeader{ "* Line {line}, Column {column}\n{message}" }; + static constexpr std::string_view keyedHeader{ "* Line {line}, Column {column} ({key})\n{message}" }; + + std::string jsonValueAsString{ "array or object" }; + try + { + jsonValueAsString = e.jsonValue.asString(); + } + catch (...) + { + // discard: we're in the middle of error handling + } + + msg = fmt::format(" Have: \"{}\"\n Expected: {}", jsonValueAsString, e.expectedType); + + auto [l, c] = _LineAndColumnFromPosition(settingsString, e.jsonValue.getOffsetStart()); + msg = fmt::format((e.key ? keyedHeader : basicHeader), + fmt::arg("line", l), + fmt::arg("column", c), + fmt::arg("key", e.key.value_or("")), + fmt::arg("message", msg)); + throw SettingsTypedDeserializationException{ msg }; + } +} + // Method Description: // - Creates a CascadiaSettings from whatever's saved on disk, or instantiates // a new one with the default values. If we're running as a packaged app, @@ -61,7 +109,7 @@ std::unique_ptr CascadiaSettings::LoadAll() // GH 3588, we need this below to know if the user chose something that wasn't our default. // Collect it up here in case it gets modified by any of the other layers between now and when // the user's preferences are loaded and layered. - const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().UnparsedDefaultProfile(); + const auto hardcodedDefaultGuid = resultPtr->GlobalSettings().DefaultProfile(); std::optional fileData = _ReadUserSettings(); const bool foundFile = fileData.has_value(); @@ -90,16 +138,23 @@ std::unique_ptr CascadiaSettings::LoadAll() needToWriteFile = true; } - // See microsoft/terminal#2325: find the defaultSettings from the user's - // settings. Layer those settings upon all the existing profiles we have - // (defaults and dynamic profiles). We'll also set - // _userDefaultProfileSettings here. When we LayerJson below to apply the - // user settings, we'll make sure to use these defaultSettings _before_ any - // profiles the user might have. - resultPtr->_ApplyDefaultsFromUserSettings(); - - // Apply the user's settings - resultPtr->LayerJson(resultPtr->_userSettings); + try + { + // See microsoft/terminal#2325: find the defaultSettings from the user's + // settings. Layer those settings upon all the existing profiles we have + // (defaults and dynamic profiles). We'll also set + // _userDefaultProfileSettings here. When we LayerJson below to apply the + // user settings, we'll make sure to use these defaultSettings _before_ any + // profiles the user might have. + resultPtr->_ApplyDefaultsFromUserSettings(); + + // Apply the user's settings + resultPtr->LayerJson(resultPtr->_userSettings); + } + catch (...) + { + _CatchRethrowSerializationExceptionWithLocationInfo(resultPtr->_userSettingsString); + } // After layering the user settings, check if there are any new profiles // that need to be inserted into their user settings file. @@ -141,12 +196,11 @@ std::unique_ptr CascadiaSettings::LoadAll() // is a lot of computation we can skip if no one cares. if (TraceLoggingProviderEnabled(g_hTerminalAppProvider, 0, MICROSOFT_KEYWORD_MEASURES)) { - const auto hardcodedDefaultGuidAsGuid = Utils::GuidFromString(hardcodedDefaultGuid); const auto guid = resultPtr->GlobalSettings().DefaultProfile(); // Compare to the defaults.json one that we set on install. // If it's different, log what the user chose. - if (hardcodedDefaultGuidAsGuid != guid) + if (hardcodedDefaultGuid != guid) { TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider @@ -229,6 +283,7 @@ std::unique_ptr CascadiaSettings::LoadDefaults() // them from a file (and the potential that could fail) resultPtr->_ParseJsonString(DefaultJson, true); resultPtr->LayerJson(resultPtr->_defaultSettings); + resultPtr->_ResolveDefaultProfile(); return resultPtr; } diff --git a/src/cascadia/TerminalApp/ColorScheme.cpp b/src/cascadia/TerminalApp/ColorScheme.cpp index 1659013809c..af70c3595e4 100644 --- a/src/cascadia/TerminalApp/ColorScheme.cpp +++ b/src/cascadia/TerminalApp/ColorScheme.cpp @@ -2,7 +2,6 @@ // Licensed under the MIT license. #include "pch.h" -#include #include "ColorScheme.h" #include "DefaultSettings.h" #include "../../types/inc/Utils.hpp" @@ -68,7 +67,7 @@ ColorScheme::~ColorScheme() // - terminalSettings: the object to apply our settings to. // Return Value: // - -void ColorScheme::ApplyScheme(TerminalSettings terminalSettings) const +void ColorScheme::ApplyScheme(const winrt::Microsoft::Terminal::TerminalControl::IControlSettings& terminalSettings) const { terminalSettings.DefaultForeground(static_cast(_defaultForeground)); terminalSettings.DefaultBackground(static_cast(_defaultBackground)); diff --git a/src/cascadia/TerminalApp/ColorScheme.h b/src/cascadia/TerminalApp/ColorScheme.h index dca0f53759a..2267eb8f006 100644 --- a/src/cascadia/TerminalApp/ColorScheme.h +++ b/src/cascadia/TerminalApp/ColorScheme.h @@ -15,7 +15,6 @@ Author(s): --*/ #pragma once -#include #include "TerminalSettings.h" #include "../../inc/conattrs.hpp" @@ -38,7 +37,7 @@ class TerminalApp::ColorScheme ColorScheme(std::wstring name, til::color defaultFg, til::color defaultBg, til::color cursorColor); ~ColorScheme(); - void ApplyScheme(winrt::TerminalApp::TerminalSettings terminalSettings) const; + void ApplyScheme(const winrt::Microsoft::Terminal::TerminalControl::IControlSettings& terminalSettings) const; static ColorScheme FromJson(const Json::Value& json); bool ShouldBeLayered(const Json::Value& json) const; diff --git a/src/cascadia/TerminalApp/Command.cpp b/src/cascadia/TerminalApp/Command.cpp index f3fe1219126..6d164bc7445 100644 --- a/src/cascadia/TerminalApp/Command.cpp +++ b/src/cascadia/TerminalApp/Command.cpp @@ -10,7 +10,6 @@ #include "JsonUtils.h" #include -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::TerminalApp; using namespace ::TerminalApp; diff --git a/src/cascadia/TerminalApp/Command.h b/src/cascadia/TerminalApp/Command.h index 2882fc8244a..b037df3beb7 100644 --- a/src/cascadia/TerminalApp/Command.h +++ b/src/cascadia/TerminalApp/Command.h @@ -32,10 +32,13 @@ namespace winrt::TerminalApp::implementation static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map& commands, const Json::Value& json); + winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker; + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr); }; } diff --git a/src/cascadia/TerminalApp/Command.idl b/src/cascadia/TerminalApp/Command.idl index 80e1a2b476b..f54de393f9e 100644 --- a/src/cascadia/TerminalApp/Command.idl +++ b/src/cascadia/TerminalApp/Command.idl @@ -12,5 +12,7 @@ namespace TerminalApp String Name; ActionAndArgs Action; String KeyChordText; + + Windows.UI.Xaml.Controls.IconSource IconSource; } } diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index fd0a139281b..b743d8c1e29 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -3,9 +3,13 @@ #include "pch.h" #include "CommandPalette.h" +#include "ActionAndArgs.h" +#include "ActionArgs.h" +#include "Command.h" + +#include #include "CommandPalette.g.cpp" -#include using namespace winrt; using namespace winrt::TerminalApp; @@ -13,15 +17,18 @@ using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::System; using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; namespace winrt::TerminalApp::implementation { - CommandPalette::CommandPalette() + CommandPalette::CommandPalette() : + _switcherStartIdx{ 0 } { InitializeComponent(); _filteredActions = winrt::single_threaded_observable_vector(); - _allActions = winrt::single_threaded_vector(); + _allCommands = winrt::single_threaded_vector(); + _allTabActions = winrt::single_threaded_vector(); if (CommandPaletteShadow()) { @@ -39,8 +46,26 @@ namespace winrt::TerminalApp::implementation RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) { if (Visibility() == Visibility::Visible) { - _searchBox().Focus(FocusState::Programmatic); - _filteredActionsView().SelectedIndex(0); + if (_currentMode == CommandPaletteMode::TabSwitcherMode) + { + if (_anchorKey != VirtualKey::None) + { + _searchBox().Visibility(Visibility::Collapsed); + _filteredActionsView().Focus(FocusState::Keyboard); + } + else + { + _searchBox().Focus(FocusState::Programmatic); + } + + _filteredActionsView().SelectedIndex(_switcherStartIdx); + _filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem()); + } + else + { + _searchBox().Focus(FocusState::Programmatic); + _filteredActionsView().SelectedIndex(0); + } TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider @@ -56,6 +81,19 @@ namespace winrt::TerminalApp::implementation _dismissPalette(); } }); + + // Focusing the ListView when the Command Palette control is set to Visible + // for the first time fails because the ListView hasn't finished loading by + // the time Focus is called. Luckily, We can listen to SizeChanged to know + // when the ListView has been measured out and is ready, and we'll immediately + // revoke the handler because we only needed to handle it once on initialization. + _sizeChangedRevoker = _filteredActionsView().SizeChanged(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) { + if (_currentMode == CommandPaletteMode::TabSwitcherMode && _anchorKey != VirtualKey::None) + { + _filteredActionsView().Focus(FocusState::Keyboard); + } + _sizeChangedRevoker.revoke(); + }); } // Method Description: @@ -78,6 +116,36 @@ namespace winrt::TerminalApp::implementation _filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem()); } + void CommandPalette::_previewKeyDownHandler(IInspectable const& /*sender*/, + Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) + { + auto key = e.OriginalKey(); + + // Some keypresses such as Tab, Return, Esc, and Arrow Keys are ignored by controls because + // they're not considered input key presses. While they don't raise KeyDown events, + // they do raise PreviewKeyDown events. + // + // Only give anchored tab switcher the ability to cycle through tabs with the tab button. + // For unanchored mode, accessibility becomes an issue when we try to hijack tab since it's + // a really widely used keyboard navigation key. + if (_currentMode == CommandPaletteMode::TabSwitcherMode && + key == VirtualKey::Tab && + _anchorKey != VirtualKey::None) + { + auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift); + if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down)) + { + _selectNextItem(false); + e.Handled(true); + } + else + { + _selectNextItem(true); + e.Handled(true); + } + } + } + // Method Description: // - Process keystrokes in the input box. This is used for moving focus up // and down the list of commands in Action mode, and for executing @@ -109,7 +177,7 @@ namespace winrt::TerminalApp::implementation if (const auto selectedItem = _filteredActionsView().SelectedItem()) { - _dispatchCommand(selectedItem.try_as()); + _dispatchCommand(selectedItem.try_as()); } e.Handled(true); @@ -130,6 +198,34 @@ namespace winrt::TerminalApp::implementation } } + void CommandPalette::_keyUpHandler(IInspectable const& /*sender*/, + Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e) + { + auto key = e.OriginalKey(); + + if (_currentMode == CommandPaletteMode::TabSwitcherMode) + { + if (_anchorKey && key == _anchorKey.value()) + { + // Once the user lifts the anchor key, we'll switch to the currently selected tab + // then close the tab switcher. + + if (const auto selectedItem = _filteredActionsView().SelectedItem()) + { + if (const auto data = selectedItem.try_as()) + { + const auto actionAndArgs = data.Action(); + _dispatch.DoAction(actionAndArgs); + _updateFilteredActions(); + _dismissPalette(); + } + } + + e.Handled(true); + } + } + } + // Method Description: // - This event is triggered when someone clicks anywhere in the bounds of // the window that's _not_ the command palette UI. When that happens, @@ -172,6 +268,28 @@ namespace winrt::TerminalApp::implementation _dispatchCommand(e.ClickedItem().try_as()); } + // Method Description: + // - Retrieve the list of commands that we should currently be filtering. + // * If the user has command with subcommands, this will return that command's subcommands. + // * If we're in Tab Switcher mode, return the tab actions. + // * Otherwise, just return the list of all the top-level commands. + // Arguments: + // - + // Return Value: + // - A list of Commands to filter. + Collections::IVector CommandPalette::_commandsToFilter() + { + switch (_currentMode) + { + case CommandPaletteMode::ActionMode: + return _allCommands; + case CommandPaletteMode::TabSwitcherMode: + return _allTabActions; + default: + return _allCommands; + } + } + // Method Description: // - Helper method for retrieving the action from a command the user // selected, and dispatching that command. Also fires a tracelogging event @@ -185,6 +303,10 @@ namespace winrt::TerminalApp::implementation { if (command) { + // Close before we dispatch so that actions that open the command + // palette like the Tab Switcher will be able to have the last laugh. + _close(); + const auto actionAndArgs = command.Action(); _dispatch.DoAction(actionAndArgs); @@ -195,8 +317,6 @@ namespace winrt::TerminalApp::implementation TraceLoggingUInt32(_searchBox().Text().size(), "SearchTextLength", "Number of characters in the search string"), TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance)); - - _close(); } } @@ -236,17 +356,59 @@ namespace winrt::TerminalApp::implementation _noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible); } - Collections::IObservableVector CommandPalette::FilteredActions() + Collections::IObservableVector CommandPalette::FilteredActions() { return _filteredActions; } - void CommandPalette::SetActions(Collections::IVector const& actions) + void CommandPalette::SetCommands(Collections::IVector const& actions) { - _allActions = actions; + _allCommands = actions; _updateFilteredActions(); } + void CommandPalette::EnableCommandPaletteMode() + { + _switchToMode(CommandPaletteMode::ActionMode); + _updateFilteredActions(); + } + + void CommandPalette::_switchToMode(CommandPaletteMode mode) + { + // The smooth remove/add animations that happen during + // UpdateFilteredActions don't work very well when switching between + // modes because of the sheer amount of remove/adds. So, let's just + // clear + append when switching between modes. + if (mode != _currentMode) + { + _currentMode = mode; + _filteredActions.Clear(); + auto commandsToFilter = _commandsToFilter(); + + for (auto action : commandsToFilter) + { + _filteredActions.Append(action); + } + + switch (_currentMode) + { + case CommandPaletteMode::TabSwitcherMode: + { + SearchBoxText(RS_(L"TabSwitcher_SearchBoxText")); + NoMatchesText(RS_(L"TabSwitcher_NoMatchesText")); + ControlName(RS_(L"TabSwitcherControlName")); + break; + } + case CommandPaletteMode::ActionMode: + default: + SearchBoxText(RS_(L"CommandPalette_SearchBox/PlaceholderText")); + NoMatchesText(RS_(L"CommandPalette_NoMatchesText/Text")); + ControlName(RS_(L"CommandPaletteControlName")); + break; + } + } + } + // This is a helper to aid in sorting commands by their `Name`s, alphabetically. static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs) { @@ -260,13 +422,23 @@ namespace winrt::TerminalApp::implementation { TerminalApp::Command command; int weight; + int inOrderCounter; bool operator<(const WeightedCommand& other) const { - // If two commands have the same weight, then we'll sort them alphabetically. if (weight == other.weight) { - return !_compareCommandNames(command, other.command); + // If two commands have the same weight, then we'll sort them alphabetically. + // If they both have the same name, fall back to the order in which they were + // pushed into the heap. + if (command.Name() == other.command.Name()) + { + return inOrderCounter > other.inOrderCounter; + } + else + { + return !_compareCommandNames(command, other.command); + } } return weight < other.weight; } @@ -287,16 +459,30 @@ namespace winrt::TerminalApp::implementation auto searchText = _searchBox().Text(); const bool addAll = searchText.empty(); + auto commandsToFilter = _commandsToFilter(); + // If there's no filter text, then just add all the commands in order to the list. // - TODO GH#6647:Possibly add the MRU commands first in order, followed // by the rest of the commands. if (addAll) { + // If TabSwitcherMode, just add all as is. We don't want + // them to be sorted alphabetically. + if (_currentMode == CommandPaletteMode::TabSwitcherMode) + { + for (auto action : commandsToFilter) + { + actions.push_back(action); + } + + return actions; + } + // Add all the commands, but make sure they're sorted alphabetically. std::vector sortedCommands; - sortedCommands.reserve(_allActions.Size()); + sortedCommands.reserve(commandsToFilter.Size()); - for (auto action : _allActions) + for (auto action : commandsToFilter) { sortedCommands.push_back(action); } @@ -326,7 +512,12 @@ namespace winrt::TerminalApp::implementation // appear first in the list. The ordering will be determined by the // match weight produced by _getWeight. std::priority_queue heap; - for (auto action : _allActions) + + // TODO GH#7205: Find a better way to ensure that WCs of the same + // weight and name stay in the order in which they were pushed onto + // the PQ. + uint32_t counter = 0; + for (auto action : commandsToFilter) { const auto weight = CommandPalette::_getWeight(searchText, action.Name()); if (weight > 0) @@ -334,6 +525,8 @@ namespace winrt::TerminalApp::implementation WeightedCommand wc; wc.command = action; wc.weight = weight; + wc.inOrderCounter = counter++; + heap.push(wc); } } @@ -503,8 +696,150 @@ namespace winrt::TerminalApp::implementation { Visibility(Visibility::Collapsed); + // Reset visibility in case anchor mode tab switcher just finished. + _searchBox().Visibility(Visibility::Visible); + // Clear the text box each time we close the dialog. This is consistent with VsCode. _searchBox().Text(L""); } + // Method Description: + // - Listens for changes to TerminalPage's _tabs vector. Updates our vector of + // tab switching commands accordingly. + // Arguments: + // - s: The vector being listened to. + // - e: The vector changed args that tells us whether a change, insert, or removal was performed + // on the listened-to vector. + // Return Value: + // - + void CommandPalette::OnTabsChanged(const IInspectable& s, const IVectorChangedEventArgs& e) + { + if (auto tabList = s.try_as>()) + { + auto idx = e.Index(); + auto changedEvent = e.CollectionChange(); + + switch (changedEvent) + { + case CollectionChange::ItemChanged: + { + winrt::com_ptr item; + item.copy_from(winrt::get_self(_allTabActions.GetAt(idx))); + item->propertyChangedRevoker.revoke(); + + auto tab = tabList.GetAt(idx); + GenerateCommandForTab(idx, false, tab); + UpdateTabIndices(idx); + break; + } + case CollectionChange::ItemInserted: + { + auto tab = tabList.GetAt(idx); + GenerateCommandForTab(idx, true, tab); + UpdateTabIndices(idx); + break; + } + case CollectionChange::ItemRemoved: + { + winrt::com_ptr item; + item.copy_from(winrt::get_self(_allTabActions.GetAt(idx))); + item->propertyChangedRevoker.revoke(); + + _allTabActions.RemoveAt(idx); + UpdateTabIndices(idx); + break; + } + } + + _updateFilteredActions(); + } + } + + // Method Description: + // - In the case where a tab is removed or reordered, the given indices of + // the tab switch commands following the removed/reordered tab will get out of sync by 1 + // (e.g. if tab 1 is removed, tabs 2,3,4,... need to become tabs 1,2,3,...) + // This function just loops through the tabs following startIdx and adjusts their given indices. + // Arguments: + // - startIdx: The index to start the update loop at. + // Return Value: + // - + void CommandPalette::UpdateTabIndices(const uint32_t startIdx) + { + if (startIdx != _allTabActions.Size() - 1) + { + for (auto i = startIdx; i < _allTabActions.Size(); ++i) + { + auto command = _allTabActions.GetAt(i); + + command.Action().Args().as()->TabIndex(i); + } + } + } + + // Method Description: + // - Create a tab switching command based on the given tab object and insert/update the command + // at the given index. The command will call a SwitchToTab action on the given idx. + // Arguments: + // - idx: The index to insert or update the tab switch command. + // - tab: The tab object to refer to when creating the tab switch command. + // Return Value: + // - + void CommandPalette::GenerateCommandForTab(const uint32_t idx, bool inserted, TerminalApp::Tab& tab) + { + auto focusTabAction = winrt::make_self(); + auto args = winrt::make_self(); + args->TabIndex(idx); + + focusTabAction->Action(ShortcutAction::SwitchToTab); + focusTabAction->Args(*args); + + auto command = winrt::make_self(); + command->Action(*focusTabAction); + command->Name(tab.Title()); + command->IconSource(tab.IconSource()); + + // Listen for changes to the Tab so we can update this Command's attributes accordingly. + auto weakThis{ get_weak() }; + auto weakCommand{ command->get_weak() }; + command->propertyChangedRevoker = tab.PropertyChanged(winrt::auto_revoke, [weakThis, weakCommand, tab](auto&&, const Windows::UI::Xaml::Data::PropertyChangedEventArgs& args) { + auto palette{ weakThis.get() }; + auto command{ weakCommand.get() }; + + if (palette && command) + { + if (args.PropertyName() == L"Title") + { + if (command->Name() != tab.Title()) + { + command->Name(tab.Title()); + } + } + if (args.PropertyName() == L"IconSource") + { + if (command->IconSource() != tab.IconSource()) + { + command->IconSource(tab.IconSource()); + } + } + } + }); + + if (inserted) + { + _allTabActions.InsertAt(idx, *command); + } + else + { + _allTabActions.SetAt(idx, *command); + } + } + + void CommandPalette::EnableTabSwitcherMode(const VirtualKey& anchorKey, const uint32_t startIdx) + { + _switcherStartIdx = startIdx; + _anchorKey = anchorKey; + _switchToMode(CommandPaletteMode::TabSwitcherMode); + _updateFilteredActions(); + } } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index a14466f4f36..b062b42003c 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -8,26 +8,50 @@ namespace winrt::TerminalApp::implementation { + enum class CommandPaletteMode + { + ActionMode = 0, + TabSwitcherMode + }; + struct CommandPalette : CommandPaletteT { CommandPalette(); Windows::Foundation::Collections::IObservableVector FilteredActions(); - void SetActions(Windows::Foundation::Collections::IVector const& actions); + + void SetCommands(Windows::Foundation::Collections::IVector const& actions); + void EnableCommandPaletteMode(); void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); + // Tab Switcher + void EnableTabSwitcherMode(const Windows::System::VirtualKey& anchorKey, const uint32_t startIdx); + void OnTabsChanged(const Windows::Foundation::IInspectable& s, const Windows::Foundation::Collections::IVectorChangedEventArgs& e); + + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::hstring, SearchBoxText, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::hstring, ControlName, _PropertyChangedHandlers); + private: friend struct CommandPaletteT; // for Xaml to bind events Windows::Foundation::Collections::IObservableVector _filteredActions{ nullptr }; - Windows::Foundation::Collections::IVector _allActions{ nullptr }; + + Windows::Foundation::Collections::IVector _allCommands{ nullptr }; winrt::TerminalApp::ShortcutActionDispatch _dispatch; + Windows::Foundation::Collections::IVector _commandsToFilter(); + void _filterTextChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void _previewKeyDownHandler(Windows::Foundation::IInspectable const& sender, + Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); void _keyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); + void _keyUpHandler(Windows::Foundation::IInspectable const& sender, + Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); @@ -41,6 +65,18 @@ namespace winrt::TerminalApp::implementation static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name); void _close(); + CommandPaletteMode _currentMode; + void _switchToMode(CommandPaletteMode mode); + + // Tab Switcher + std::optional _anchorKey; + void GenerateCommandForTab(const uint32_t idx, bool inserted, winrt::TerminalApp::Tab& tab); + void UpdateTabIndices(const uint32_t startIdx); + Windows::Foundation::Collections::IVector _allTabActions{ nullptr }; + uint32_t _switcherStartIdx; + + winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker; + void _dispatchCommand(const TerminalApp::Command& command); void _dismissPalette(); diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index f0e7c9bb539..8286077a4e4 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -5,14 +5,22 @@ import "../Command.idl"; namespace TerminalApp { - [default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid + [default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.UserControl, Windows.UI.Xaml.Data.INotifyPropertyChanged { CommandPalette(); + String NoMatchesText { get; }; + String SearchBoxText { get; }; + String ControlName { get; }; + Windows.Foundation.Collections.IObservableVector FilteredActions { get; }; - void SetActions(Windows.Foundation.Collections.IVector actions); + void SetCommands(Windows.Foundation.Collections.IVector actions); + void EnableCommandPaletteMode(); void SetDispatch(ShortcutActionDispatch dispatch); + + void EnableTabSwitcherMode(Windows.System.VirtualKey anchorKey, UInt32 startIdx); + void OnTabsChanged(IInspectable s, Windows.Foundation.Collections.IVectorChangedEventArgs e); } } diff --git a/src/cascadia/TerminalApp/CommandPalette.xaml b/src/cascadia/TerminalApp/CommandPalette.xaml index 39b0e1a25be..50b35e8bbb6 100644 --- a/src/cascadia/TerminalApp/CommandPalette.xaml +++ b/src/cascadia/TerminalApp/CommandPalette.xaml @@ -1,6 +1,6 @@ - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)" + TabNavigation="Cycle" + IsTabStop="True" + AllowFocusOnInteraction="True" PointerPressed="_rootPointerPressed" - mc:Ignorable="d"> + PreviewKeyDown="_previewKeyDownHandler" + KeyDown="_keyDownHandler" + PreviewKeyUp="_keyUpHandler" + mc:Ignorable="d" + AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"> - + + - + - - - - - + + + + + - - - - + + + + - + - VerticalAlignment="Stretch"> - HorizontalAlignment="Stretch" VerticalAlignment="Top"> - - - - - + + + + + - - + - - + Grid.Row="1" + Text="{x:Bind NoMatchesText, Mode=OneWay}"> + - AllowDrop="False" IsItemClickEnabled="True" ItemClick="_listItemClicked" + PreviewKeyDown="_keyDownHandler" ItemsSource="{x:Bind FilteredActions}"> - - + + - - - + - - - - - - + + + + + + - + - - - - - + - - - - - + + + + + - + - + + diff --git a/src/cascadia/TerminalApp/DebugTapConnection.cpp b/src/cascadia/TerminalApp/DebugTapConnection.cpp index 21994920ecd..d149ad46213 100644 --- a/src/cascadia/TerminalApp/DebugTapConnection.cpp +++ b/src/cascadia/TerminalApp/DebugTapConnection.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "DebugTapConnection.h" +#include "Utils.h" using namespace ::winrt::Microsoft::Terminal::TerminalConnection; using namespace ::winrt::Windows::Foundation; @@ -91,36 +92,15 @@ namespace winrt::Microsoft::TerminalApp::implementation return ConnectionState::Failed; } - static std::wstring _sanitizeString(const std::wstring_view str) - { - std::wstring newString{ str.begin(), str.end() }; - for (auto& ch : newString) - { - if (ch < 0x20) - { - ch += 0x2400; - } - else if (ch == 0x20) - { - ch = 0x2423; // replace space with ␣ - } - else if (ch == 0x7f) - { - ch = 0x2421; // replace del with ␡ - } - } - return newString; - } - void DebugTapConnection::_OutputHandler(const hstring str) { - _TerminalOutputHandlers(_sanitizeString(str)); + _TerminalOutputHandlers(VisualizeControlCodes(str)); } // Called by the DebugInputTapConnection to print user input void DebugTapConnection::_PrintInput(const hstring& str) { - auto clean{ _sanitizeString(str) }; + auto clean{ VisualizeControlCodes(str) }; auto formatted{ wil::str_printf(L"\x1b[91m%ls\x1b[m", clean.data()) }; _TerminalOutputHandlers(formatted); } diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index dd3b17ae723..afeca688c6d 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -10,7 +10,6 @@ #include "TerminalSettingsSerializationHelpers.h" using namespace TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::TerminalApp; using namespace winrt::Windows::UI::Xaml; using namespace ::Microsoft::Console; @@ -90,9 +89,9 @@ GUID GlobalAppSettings::DefaultProfile() const return _defaultProfile; } -std::wstring GlobalAppSettings::UnparsedDefaultProfile() const +std::optional GlobalAppSettings::UnparsedDefaultProfile() const { - return _unparsedDefaultProfile.value(); + return _unparsedDefaultProfile; } AppKeyBindings GlobalAppSettings::GetKeybindings() const noexcept diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.h b/src/cascadia/TerminalApp/GlobalAppSettings.h index 78222106716..ba51109fa0b 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.h +++ b/src/cascadia/TerminalApp/GlobalAppSettings.h @@ -56,7 +56,7 @@ class TerminalApp::GlobalAppSettings final // by higher layers in the app. void DefaultProfile(const GUID defaultProfile) noexcept; GUID DefaultProfile() const; - std::wstring UnparsedDefaultProfile() const; + std::optional UnparsedDefaultProfile() const; GETSET_PROPERTY(int32_t, InitialRows); // default value set in constructor GETSET_PROPERTY(int32_t, InitialCols); // default value set in constructor diff --git a/src/cascadia/TerminalApp/JsonUtils.h b/src/cascadia/TerminalApp/JsonUtils.h index b98ade1da9d..b15eb1478a2 100644 --- a/src/cascadia/TerminalApp/JsonUtils.h +++ b/src/cascadia/TerminalApp/JsonUtils.h @@ -69,52 +69,24 @@ namespace TerminalApp::JsonUtils }; } - // These exceptions cannot use localized messages, as we do not have - // guaranteed access to the resource loader. - class TypeMismatchException : public std::runtime_error + class DeserializationError : public std::runtime_error { public: - TypeMismatchException() : - runtime_error("unexpected data type") {} - }; + DeserializationError(const Json::Value& value) : + runtime_error("failed to deserialize"), + jsonValue{ value } {} - class KeyedException : public std::runtime_error - { - public: - KeyedException(const std::string_view key, std::exception_ptr exception) : - runtime_error(fmt::format("error parsing \"{0}\"", key).c_str()), - _key{ key }, - _innerException{ std::move(exception) } {} - - std::string GetKey() const + void SetKey(std::string_view newKey) { - return _key; - } - - [[noreturn]] void RethrowInner() const - { - std::rethrow_exception(_innerException); - } - - private: - std::string _key; - std::exception_ptr _innerException; - }; - - class UnexpectedValueException : public std::runtime_error - { - public: - UnexpectedValueException(const std::string_view value) : - runtime_error(fmt::format("unexpected value \"{0}\"", value).c_str()), - _value{ value } {} - - std::string GetValue() const - { - return _value; + if (!key) + { + key = newKey; + } } - private: - std::string _value; + std::optional key; + Json::Value jsonValue; + std::string expectedType; }; template @@ -123,6 +95,8 @@ namespace TerminalApp::JsonUtils // Forward-declare these so the linker can pick up specializations from elsewhere! T FromJson(const Json::Value&); bool CanConvert(const Json::Value& json); + + std::string TypeDescription() const { return ""; } }; template<> @@ -137,6 +111,11 @@ namespace TerminalApp::JsonUtils { return json.isString(); } + + std::string TypeDescription() const + { + return "string"; + } }; template<> @@ -151,6 +130,11 @@ namespace TerminalApp::JsonUtils { return json.isString(); } + + std::string TypeDescription() const + { + return "string"; + } }; #ifdef WINRT_BASE_H @@ -177,6 +161,11 @@ namespace TerminalApp::JsonUtils { return json.isBool(); } + + std::string TypeDescription() const + { + return "true | false"; + } }; template<> @@ -191,6 +180,11 @@ namespace TerminalApp::JsonUtils { return json.isInt(); } + + std::string TypeDescription() const + { + return "number"; + } }; template<> @@ -205,6 +199,11 @@ namespace TerminalApp::JsonUtils { return json.isUInt(); } + + std::string TypeDescription() const + { + return "number (>= 0)"; + } }; template<> @@ -219,6 +218,11 @@ namespace TerminalApp::JsonUtils { return json.isNumeric(); } + + std::string TypeDescription() const + { + return "number"; + } }; template<> @@ -233,6 +237,11 @@ namespace TerminalApp::JsonUtils { return json.isNumeric(); } + + std::string TypeDescription() const + { + return "number"; + } }; template<> @@ -253,6 +262,11 @@ namespace TerminalApp::JsonUtils const auto string{ Detail::GetStringView(json) }; return string.length() == 38 && string.front() == '{' && string.back() == '}'; } + + std::string TypeDescription() const + { + return "guid"; + } }; // (GUID and winrt::guid are mutually convertible!) @@ -279,6 +293,11 @@ namespace TerminalApp::JsonUtils const auto string{ Detail::GetStringView(json) }; return (string.length() == 7 || string.length() == 4) && string.front() == '#'; } + + std::string TypeDescription() const + { + return "color (#rrggbb, #rgb)"; + } }; template @@ -298,13 +317,22 @@ namespace TerminalApp::JsonUtils } } - throw UnexpectedValueException{ name }; + DeserializationError e{ json }; + e.expectedType = TypeDescription(); + throw e; } bool CanConvert(const Json::Value& json) { return json.isString(); } + + std::string TypeDescription() const + { + std::vector names; + std::transform(TBase::mappings.cbegin(), TBase::mappings.cend(), std::back_inserter(names), [](auto&& p) { return p.first; }); + return fmt::format("{}", fmt::join(names, " | ")); + } }; // FlagMapper is EnumMapper, but it works for bitfields. @@ -343,7 +371,9 @@ namespace TerminalApp::JsonUtils (value == AllClear && newFlag != AllClear))) { // attempt to combine AllClear (explicitly) with anything else - throw UnexpectedValueException{ element.asString() }; + DeserializationError e{ element }; + e.expectedType = TypeDescription(); + throw e; } value |= newFlag; } @@ -360,6 +390,30 @@ namespace TerminalApp::JsonUtils } }; + template + struct PermissiveStringConverter + { + }; + + template<> + struct PermissiveStringConverter + { + std::wstring FromJson(const Json::Value& json) + { + return til::u8u16(json.asString()); + } + + bool CanConvert(const Json::Value& /*unused*/) + { + return true; + } + + std::string TypeDescription() const + { + return "any"; + } + }; + // Method Description: // - Helper that will populate a reference with a value converted from a json object. // Arguments: @@ -388,7 +442,9 @@ namespace TerminalApp::JsonUtils { if (!conv.CanConvert(json)) { - throw TypeMismatchException{}; + DeserializationError e{ json }; + e.expectedType = conv.TypeDescription(); + throw e; } target = conv.FromJson(json); @@ -416,10 +472,10 @@ namespace TerminalApp::JsonUtils { return GetValue(*found, target, std::forward(conv)); } - catch (...) + catch (DeserializationError& e) { - // Wrap any caught exceptions in one that preserves context. - throw KeyedException(key, std::current_exception()); + e.SetKey(key); + throw; // rethrow now that it has a key } } return false; diff --git a/src/cascadia/TerminalApp/KeyChordSerialization.cpp b/src/cascadia/TerminalApp/KeyChordSerialization.cpp index b825c251047..69625004d14 100644 --- a/src/cascadia/TerminalApp/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalApp/KeyChordSerialization.cpp @@ -4,7 +4,7 @@ #include "pch.h" #include "KeyChordSerialization.h" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; static constexpr std::wstring_view CTRL_KEY{ L"ctrl" }; static constexpr std::wstring_view SHIFT_KEY{ L"shift" }; @@ -99,7 +99,7 @@ static const std::unordered_map vkeyNamePairs { // - hstr: the string to parse into a keychord. // Return Value: // - a newly constructed KeyChord -winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) +KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) { std::wstring wstr{ hstr }; @@ -201,7 +201,7 @@ winrt::Microsoft::Terminal::Settings::KeyChord KeyChordSerialization::FromString } } - return winrt::Microsoft::Terminal::Settings::KeyChord{ modifiers, vkey }; + return KeyChord{ modifiers, vkey }; } // Function Description: diff --git a/src/cascadia/TerminalApp/KeyChordSerialization.h b/src/cascadia/TerminalApp/KeyChordSerialization.h index 8f9cf4f26ee..4da2f0020a7 100644 --- a/src/cascadia/TerminalApp/KeyChordSerialization.h +++ b/src/cascadia/TerminalApp/KeyChordSerialization.h @@ -2,11 +2,11 @@ // Licensed under the MIT license. #pragma once -#include +#include class KeyChordSerialization final { public: - static winrt::Microsoft::Terminal::Settings::KeyChord FromString(const winrt::hstring& str); - static winrt::hstring ToString(const winrt::Microsoft::Terminal::Settings::KeyChord& chord); + static winrt::Microsoft::Terminal::TerminalControl::KeyChord FromString(const winrt::hstring& str); + static winrt::hstring ToString(const winrt::Microsoft::Terminal::TerminalControl::KeyChord& chord); }; diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index f8e61ae8379..180100f4a97 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -12,7 +12,6 @@ using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Xaml::Media; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::Microsoft::Terminal::TerminalConnection; using namespace winrt::TerminalApp; @@ -841,21 +840,29 @@ void Pane::_UpdateBorders() double top = 0, bottom = 0, left = 0, right = 0; Thickness newBorders{ 0 }; - if (WI_IsFlagSet(_borders, Borders::Top)) + if (_zoomed) { - top = PaneBorderSize; + // When the pane is zoomed, manually show all the borders around the window. + top = bottom = right = left = PaneBorderSize; } - if (WI_IsFlagSet(_borders, Borders::Bottom)) - { - bottom = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Left)) - { - left = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Right)) + else { - right = PaneBorderSize; + if (WI_IsFlagSet(_borders, Borders::Top)) + { + top = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Bottom)) + { + bottom = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Left)) + { + left = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Right)) + { + right = PaneBorderSize; + } } _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); } @@ -1173,6 +1180,76 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState return { _firstChild, _secondChild }; } +// Method Description: +// - Recursively attempt to "zoom" the given pane. When the pane is zoomed, it +// won't be displayed as part of the tab tree, instead it'll take up the full +// content of the tab. When we find the given pane, we'll need to remove it +// from the UI tree, so that the caller can re-add it. We'll also set some +// internal state, so the pane can display all of its borders. +// Arguments: +// - zoomedPane: This is the pane which we're attempting to zoom on. +// Return Value: +// - +void Pane::Maximize(std::shared_ptr zoomedPane) +{ + if (_IsLeaf()) + { + _zoomed = (zoomedPane == shared_from_this()); + _UpdateBorders(); + } + else + { + if (zoomedPane == _firstChild || zoomedPane == _secondChild) + { + // When we're zooming the pane, we'll need to remove it from our UI + // tree. Easy way: just remove both children. We'll re-attach both + // when we un-zoom. + _root.Children().Clear(); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild->Maximize(zoomedPane); + _secondChild->Maximize(zoomedPane); + } +} + +// Method Description: +// - Recursively attempt to "un-zoom" the given pane. This does the opposite of +// Pane::Maximize. When we find the given pane, we should return the pane to our +// UI tree. We'll also clear the internal state, so the pane can display its +// borders correctly. +// - The caller should make sure to have removed the zoomed pane from the UI +// tree _before_ calling this. +// Arguments: +// - zoomedPane: This is the pane which we're attempting to un-zoom. +// Return Value: +// - +void Pane::Restore(std::shared_ptr zoomedPane) +{ + if (_IsLeaf()) + { + _zoomed = false; + _UpdateBorders(); + } + else + { + if (zoomedPane == _firstChild || zoomedPane == _secondChild) + { + // When we're un-zooming the pane, we'll need to re-add it to our UI + // tree where it originally belonged. easy way: just re-add both. + _root.Children().Clear(); + _root.Children().Append(_firstChild->GetRootElement()); + _root.Children().Append(_secondChild->GetRootElement()); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild->Restore(zoomedPane); + _secondChild->Restore(zoomedPane); + } +} + // Method Description: // - Gets the size in pixels of each of our children, given the full size they // should fill. Since these children own their own separators (borders), this diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index dfbcf1e7bc2..6d1ffa133f1 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -72,6 +72,9 @@ class Pane : public std::enable_shared_from_this int GetLeafPaneCount() const noexcept; + void Maximize(std::shared_ptr zoomedPane); + void Restore(std::shared_ptr zoomedPane); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); @@ -103,6 +106,8 @@ class Pane : public std::enable_shared_from_this Borders _borders{ Borders::None }; + bool _zoomed{ false }; + bool _IsLeaf() const noexcept; bool _HasFocusedChild() const noexcept; void _SetupChildCloseHandlers(); diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp index b98efb9f691..b1ad96ae3f2 100644 --- a/src/cascadia/TerminalApp/Profile.cpp +++ b/src/cascadia/TerminalApp/Profile.cpp @@ -13,7 +13,7 @@ using namespace TerminalApp; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::Windows::UI::Xaml; using namespace ::Microsoft::Console; @@ -53,6 +53,7 @@ static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImag static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" }; static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" }; static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" }; +static constexpr std::string_view TabColorKey{ "tabColor" }; Profile::Profile() : Profile(std::nullopt) @@ -232,6 +233,12 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map colorRef{ _tabColor.value() }; + terminalSettings.TabColor(colorRef); + } + return terminalSettings; } @@ -395,7 +402,11 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, UseAcrylicKey, _useAcrylic); JsonUtils::GetValueForKey(json, SuppressApplicationTitleKey, _suppressApplicationTitle); JsonUtils::GetValueForKey(json, CloseOnExitKey, _closeOnExitMode); - JsonUtils::GetValueForKey(json, PaddingKey, _padding); + + // Padding was never specified as an integer, but it was a common working mistake. + // Allow it to be permissive. + JsonUtils::GetValueForKey(json, PaddingKey, _padding, JsonUtils::PermissiveStringConverter{}); + JsonUtils::GetValueForKey(json, ScrollbarStateKey, _scrollbarState); JsonUtils::GetValueForKey(json, StartingDirectoryKey, _startingDirectory); JsonUtils::GetValueForKey(json, IconKey, _icon); @@ -405,6 +416,8 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _backgroundImageAlignment); JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _retroTerminalEffect); JsonUtils::GetValueForKey(json, AntialiasingModeKey, _antialiasingMode); + + JsonUtils::GetValueForKey(json, TabColorKey, _tabColor); } void Profile::SetFontFace(std::wstring fontFace) noexcept diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h index 1e145e669c7..a6794bbcc07 100644 --- a/src/cascadia/TerminalApp/Profile.h +++ b/src/cascadia/TerminalApp/Profile.h @@ -117,12 +117,13 @@ class TerminalApp::Profile final std::optional _selectionBackground; std::optional _cursorColor; std::optional _tabTitle; + std::optional _tabColor; bool _suppressApplicationTitle; int32_t _historySize; bool _snapOnInput; bool _altGrAliasing; uint32_t _cursorHeight; - winrt::Microsoft::Terminal::Settings::CursorStyle _cursorShape; + winrt::Microsoft::Terminal::TerminalControl::CursorStyle _cursorShape; std::wstring _commandline; std::wstring _fontFace; @@ -137,13 +138,13 @@ class TerminalApp::Profile final std::optional _backgroundImageStretchMode; std::optional> _backgroundImageAlignment; - std::optional<::winrt::Microsoft::Terminal::Settings::ScrollbarState> _scrollbarState; + std::optional<::winrt::Microsoft::Terminal::TerminalControl::ScrollbarState> _scrollbarState; CloseOnExitMode _closeOnExitMode; std::wstring _padding; std::optional _icon; - winrt::Microsoft::Terminal::Settings::TextAntialiasingMode _antialiasingMode; + winrt::Microsoft::Terminal::TerminalControl::TextAntialiasingMode _antialiasingMode; friend class TerminalAppLocalTests::SettingsTests; friend class TerminalAppLocalTests::ProfileTests; diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 6ca22a39ff0..c8951fac9f2 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -449,9 +449,16 @@ New tab + + Send Input: "{0}" + {0} will be replaced with a string of input as defined by the user + Split pane + + Toggle pane zoom + New window @@ -526,6 +533,13 @@ Toggle command palette + + Command Palette + + + Set color scheme to {0} + {0} will be replaced with the name of a color scheme as defined by the user. + Set tab color to {0} {0} will be replaced with a color, displayed in hexadecimal (#RRGGBB) notation. @@ -538,14 +552,34 @@ Rename tab to "{0}" - {0} will be replaced with user-defined string + {0} will be replaced with a user-defined string Reset tab title Run commandline "{0}" in this window - {0} will be replaced with user-defined commandline + {0} will be replaced with a user-defined commandline + + + Close tabs other than index {0} + {0} will be replaced with a number + + + Close tabs after index {0} + {0} will be replaced with a number + + + Tab Switcher + + + Toggle tab switcher + + + Type a tab name... + + + No matching tab name Crimson diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 571b4589516..20a43791c2d 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -113,6 +113,12 @@ namespace winrt::TerminalApp::implementation break; } + case ShortcutAction::SendInput: + { + _SendInputHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::SplitVertical: case ShortcutAction::SplitHorizontal: case ShortcutAction::SplitPane: @@ -121,6 +127,12 @@ namespace winrt::TerminalApp::implementation break; } + case ShortcutAction::TogglePaneZoom: + { + _TogglePaneZoomHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::SwitchToTab: { _SwitchToTabHandlers(*this, *eventArgs); @@ -179,6 +191,11 @@ namespace winrt::TerminalApp::implementation _ToggleCommandPaletteHandlers(*this, *eventArgs); break; } + case ShortcutAction::SetColorScheme: + { + _SetColorSchemeHandlers(*this, *eventArgs); + break; + } case ShortcutAction::SetTabColor: { _SetTabColorHandlers(*this, *eventArgs); @@ -197,6 +214,22 @@ namespace winrt::TerminalApp::implementation case ShortcutAction::ExecuteCommandline: { _ExecuteCommandlineHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::CloseOtherTabs: + { + _CloseOtherTabsHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::CloseTabsAfter: + { + _CloseTabsAfterHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::ToggleTabSwitcher: + { + _ToggleTabSwitcherHandlers(*this, *eventArgs); + break; } default: return false; diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index 9c1dff1575c..9da3aadcdf2 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -35,7 +35,9 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(SendInput, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(TogglePaneZoom, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); @@ -51,10 +53,14 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ToggleAlwaysOnTop, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ToggleCommandPalette, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(SetColorScheme, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(OpenTabColorPicker, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(CloseOtherTabs, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(CloseTabsAfter, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(ToggleTabSwitcher, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); // clang-format on private: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index 8306c04fc46..e13c0433565 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -20,7 +20,9 @@ namespace TerminalApp PrevTab, SplitVertical, SplitHorizontal, + SendInput, SplitPane, + TogglePaneZoom, SwitchToTab, AdjustFontSize, ResetFontSize, @@ -36,11 +38,15 @@ namespace TerminalApp ToggleFullscreen, ToggleAlwaysOnTop, OpenSettings, + SetColorScheme, SetTabColor, OpenTabColorPicker, RenameTab, ExecuteCommandline, - ToggleCommandPalette + ToggleCommandPalette, + CloseOtherTabs, + CloseTabsAfter, + ToggleTabSwitcher }; [default_interface] runtimeclass ActionAndArgs { @@ -66,7 +72,9 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SwitchToTab; event Windows.Foundation.TypedEventHandler NextTab; event Windows.Foundation.TypedEventHandler PrevTab; + event Windows.Foundation.TypedEventHandler SendInput; event Windows.Foundation.TypedEventHandler SplitPane; + event Windows.Foundation.TypedEventHandler TogglePaneZoom; event Windows.Foundation.TypedEventHandler AdjustFontSize; event Windows.Foundation.TypedEventHandler ResetFontSize; event Windows.Foundation.TypedEventHandler ScrollUp; @@ -82,9 +90,13 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler ToggleFullscreen; event Windows.Foundation.TypedEventHandler ToggleAlwaysOnTop; event Windows.Foundation.TypedEventHandler ToggleCommandPalette; + event Windows.Foundation.TypedEventHandler SetColorScheme; event Windows.Foundation.TypedEventHandler SetTabColor; event Windows.Foundation.TypedEventHandler OpenTabColorPicker; event Windows.Foundation.TypedEventHandler RenameTab; event Windows.Foundation.TypedEventHandler ExecuteCommandline; + event Windows.Foundation.TypedEventHandler CloseOtherTabs; + event Windows.Foundation.TypedEventHandler CloseTabsAfter; + event Windows.Foundation.TypedEventHandler ToggleTabSwitcher; } } diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 51e54be09ee..ead28ce1f02 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -12,13 +12,13 @@ using namespace winrt; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::Windows::System; namespace winrt { namespace MUX = Microsoft::UI::Xaml; + namespace WUX = Windows::UI::Xaml; } namespace winrt::TerminalApp::implementation @@ -55,6 +55,7 @@ namespace winrt::TerminalApp::implementation }); _UpdateTitle(); + _RecalculateAndApplyTabColor(); } // Method Description: @@ -65,7 +66,14 @@ namespace winrt::TerminalApp::implementation // - The UIElement acting as root of the Tab's root pane. UIElement Tab::GetRootElement() { - return _rootPane->GetRootElement(); + if (_zoomedPane) + { + return _zoomedPane->GetRootElement(); + } + else + { + return _rootPane->GetRootElement(); + } } // Method Description: @@ -218,7 +226,8 @@ namespace winrt::TerminalApp::implementation if (auto tab{ weakThis.get() }) { - IconPath(_lastIconPath); + // The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX... + IconSource(GetColoredIcon(_lastIconPath)); _tabViewItem.IconSource(GetColoredIcon(_lastIconPath)); } } @@ -436,6 +445,16 @@ namespace winrt::TerminalApp::implementation _rootPane->Relayout(); } }); + + control.TabColorChanged([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + // The control's tabColor changed, but it is not necessarily the + // active control in this tab. We'll just recalculate the + // current color anyways. + tab->_RecalculateAndApplyTabColor(); + } + }); } // Method Description: @@ -480,6 +499,7 @@ namespace winrt::TerminalApp::implementation if (tab && sender != tab->_activePane) { tab->_UpdateActivePane(sender); + tab->_RecalculateAndApplyTabColor(); } }); } @@ -530,14 +550,14 @@ namespace winrt::TerminalApp::implementation _tabColorPickup.ColorSelected([weakThis](auto newTabColor) { if (auto tab{ weakThis.get() }) { - tab->SetTabColor(newTabColor); + tab->SetRuntimeTabColor(newTabColor); } }); _tabColorPickup.ColorCleared([weakThis]() { if (auto tab{ weakThis.get() }) { - tab->ResetTabColor(); + tab->ResetRuntimeTabColor(); } }); @@ -587,8 +607,28 @@ namespace winrt::TerminalApp::implementation if (!_inRename) { - // If we're not currently in the process of renaming the tab, then just set the tab's text to whatever our active title is. - _tabViewItem.Header(winrt::box_value(tabText)); + if (_zoomedPane) + { + Controls::StackPanel sp; + sp.Orientation(Controls::Orientation::Horizontal); + Controls::FontIcon ico; + ico.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + ico.Glyph(L"\xE8A3"); // "ZoomIn", a magnifying glass with a '+' in it. + ico.FontSize(12); + ico.Margin(ThicknessHelper::FromLengths(0, 0, 8, 0)); + sp.Children().Append(ico); + Controls::TextBlock tb; + tb.Text(tabText); + sp.Children().Append(tb); + + _tabViewItem.Header(sp); + } + else + { + // If we're not currently in the process of renaming the tab, + // then just set the tab's text to whatever our active title is. + _tabViewItem.Header(winrt::box_value(tabText)); + } } else { @@ -707,114 +747,179 @@ namespace winrt::TerminalApp::implementation // - The tab's color, if any std::optional Tab::GetTabColor() { - return _tabColor; + const auto currControlColor{ GetActiveTerminalControl().TabColor() }; + std::optional controlTabColor; + if (currControlColor != nullptr) + { + controlTabColor = currControlColor.Value(); + } + + // A Tab's color will be the result of layering a variety of sources, + // from the bottom up: + // + // Color | | Set by + // -------------------- | -- | -- + // Runtime Color | _optional_ | Color Picker / `setTabColor` action + // Control Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT + // Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme + // Tab Default Color | **default** | TabView in XAML + // + // coalesce will get us the first of these values that's + // actually set, with nullopt being our sentinel for "use the default + // tabview color" (and clear out any colors we've set). + + return til::coalesce(_runtimeTabColor, + controlTabColor, + _themeTabColor, + std::optional(std::nullopt)); } // Method Description: - // - Sets the tab background color to the color chosen by the user + // - Sets the runtime tab background color to the color chosen by the user // - Sets the tab foreground color depending on the luminance of // the background color // Arguments: - // - color: the shiny color the user picked for their tab + // - color: the color the user picked for their tab // Return Value: // - - void Tab::SetTabColor(const winrt::Windows::UI::Color& color) + void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) + { + _runtimeTabColor.emplace(color); + _RecalculateAndApplyTabColor(); + } + + // Method Description: + // - This function dispatches a function to the UI thread to recalculate + // what this tab's current background color should be. If a color is set, + // it will apply the given color to the tab's background. Otherwise, it + // will clear the tab's background color. + // Arguments: + // - + // Return Value: + // - + void Tab::_RecalculateAndApplyTabColor() { auto weakThis{ get_weak() }; - _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, color]() { + _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() { auto ptrTab = weakThis.get(); if (!ptrTab) return; auto tab{ ptrTab }; - Media::SolidColorBrush selectedTabBrush{}; - Media::SolidColorBrush deselectedTabBrush{}; - Media::SolidColorBrush fontBrush{}; - Media::SolidColorBrush hoverTabBrush{}; - // calculate the luminance of the current color and select a font - // color based on that - // see https://www.w3.org/TR/WCAG20/#relativeluminancedef - if (TerminalApp::ColorHelper::IsBrightColor(color)) + + std::optional currentColor = tab->GetTabColor(); + if (currentColor.has_value()) { - fontBrush.Color(winrt::Windows::UI::Colors::Black()); + tab->_ApplyTabColor(currentColor.value()); } else { - fontBrush.Color(winrt::Windows::UI::Colors::White()); + tab->_ClearTabBackgroundColor(); } - - hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color)); - selectedTabBrush.Color(color); - - // currently if a tab has a custom color, a deselected state is - // signified by using the same color with a bit ot transparency - auto deselectedTabColor = color; - deselectedTabColor.A = 64; - deselectedTabBrush.Color(deselectedTabColor); - - // currently if a tab has a custom color, a deselected state is - // signified by using the same color with a bit ot transparency - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); - - tab->_RefreshVisualState(); - - tab->_tabColor.emplace(color); - tab->_colorSelected(color); }); } // Method Description: - // Clear the custom color of the tab, if any + // - Applies the given color to the background of this tab's TabViewItem. + // - Sets the tab foreground color depending on the luminance of // the background color + // - This method should only be called on the UI thread. // Arguments: - // - + // - color: the color the user picked for their tab // Return Value: // - - void Tab::ResetTabColor() + void Tab::_ApplyTabColor(const winrt::Windows::UI::Color& color) { - auto weakThis{ get_weak() }; + Media::SolidColorBrush selectedTabBrush{}; + Media::SolidColorBrush deselectedTabBrush{}; + Media::SolidColorBrush fontBrush{}; + Media::SolidColorBrush hoverTabBrush{}; + // calculate the luminance of the current color and select a font + // color based on that + // see https://www.w3.org/TR/WCAG20/#relativeluminancedef + if (TerminalApp::ColorHelper::IsBrightColor(color)) + { + fontBrush.Color(winrt::Windows::UI::Colors::Black()); + } + else + { + fontBrush.Color(winrt::Windows::UI::Colors::White()); + } - _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() { - auto ptrTab = weakThis.get(); - if (!ptrTab) - return; + hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color)); + selectedTabBrush.Color(color); + + // currently if a tab has a custom color, a deselected state is + // signified by using the same color with a bit ot transparency + auto deselectedTabColor = color; + deselectedTabColor.A = 64; + deselectedTabBrush.Color(deselectedTabColor); + + // currently if a tab has a custom color, a deselected state is + // signified by using the same color with a bit ot transparency + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); + _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); + + _RefreshVisualState(); + + _colorSelected(color); + } - auto tab{ ptrTab }; - winrt::hstring keys[] = { - L"TabViewItemHeaderBackground", - L"TabViewItemHeaderBackgroundSelected", - L"TabViewItemHeaderBackgroundPointerOver", - L"TabViewItemHeaderForeground", - L"TabViewItemHeaderForegroundSelected", - L"TabViewItemHeaderForegroundPointerOver", - L"TabViewItemHeaderBackgroundPressed", - L"TabViewItemHeaderForegroundPressed", - L"TabViewButtonForegroundActiveTab" - }; - - // simply clear any of the colors in the tab's dict - for (auto keyString : keys) + // Method Description: + // - Clear the custom runtime color of the tab, if any color is set. This + // will re-apply whatever the tab's base color should be (either the color + // from the control, the theme, or the default tab color.) + // Arguments: + // - + // Return Value: + // - + void Tab::ResetRuntimeTabColor() + { + _runtimeTabColor.reset(); + _RecalculateAndApplyTabColor(); + } + + // Method Description: + // - Clear out any color we've set for the TabViewItem. + // - This method should only be called on the UI thread. + // Arguments: + // - + // Return Value: + // - + void Tab::_ClearTabBackgroundColor() + { + winrt::hstring keys[] = { + L"TabViewItemHeaderBackground", + L"TabViewItemHeaderBackgroundSelected", + L"TabViewItemHeaderBackgroundPointerOver", + L"TabViewItemHeaderForeground", + L"TabViewItemHeaderForegroundSelected", + L"TabViewItemHeaderForegroundPointerOver", + L"TabViewItemHeaderBackgroundPressed", + L"TabViewItemHeaderForegroundPressed", + L"TabViewButtonForegroundActiveTab" + }; + + // simply clear any of the colors in the tab's dict + for (auto keyString : keys) + { + auto key = winrt::box_value(keyString); + if (_tabViewItem.Resources().HasKey(key)) { - auto key = winrt::box_value(keyString); - if (tab->_tabViewItem.Resources().HasKey(key)) - { - tab->_tabViewItem.Resources().Remove(key); - } + _tabViewItem.Resources().Remove(key); } + } - tab->_RefreshVisualState(); - tab->_tabColor.reset(); - tab->_colorCleared(); - }); + _RefreshVisualState(); + _colorCleared(); } // Method Description: @@ -879,6 +984,48 @@ namespace winrt::TerminalApp::implementation { return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false); } + + // Method Description: + // - Toggle our zoom state. + // * If we're not zoomed, then zoom the active pane, making it take the + // full size of the tab. We'll achieve this by changing our response to + // Tab::GetRootElement, so that it'll return the zoomed pane only. + // * If we're currently zoomed on a pane, un-zoom that pane. + // Arguments: + // - + // Return Value: + // - + void Tab::ToggleZoom() + { + if (_zoomedPane) + { + ExitZoom(); + } + else + { + EnterZoom(); + } + } + void Tab::EnterZoom() + { + _zoomedPane = _activePane; + _rootPane->Maximize(_zoomedPane); + // Update the tab header to show the magnifying glass + _UpdateTabHeader(); + } + void Tab::ExitZoom() + { + _rootPane->Restore(_zoomedPane); + _zoomedPane = nullptr; + // Update the tab header to hide the magnifying glass + _UpdateTabHeader(); + } + + bool Tab::IsZoomed() + { + return _zoomedPane != nullptr; + } + DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate); DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 055d631c421..02055b05767 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -57,10 +57,15 @@ namespace winrt::TerminalApp::implementation std::optional GetTabColor(); - void SetTabColor(const winrt::Windows::UI::Color& color); - void ResetTabColor(); + void SetRuntimeTabColor(const winrt::Windows::UI::Color& color); + void ResetRuntimeTabColor(); void ActivateColorPicker(); + void ToggleZoom(); + bool IsZoomed(); + void EnterZoom(); + void ExitZoom(); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); @@ -68,14 +73,16 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr); private: std::shared_ptr _rootPane{ nullptr }; std::shared_ptr _activePane{ nullptr }; + std::shared_ptr _zoomedPane{ nullptr }; winrt::hstring _lastIconPath{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; - std::optional _tabColor{}; + std::optional _themeTabColor{}; + std::optional _runtimeTabColor{}; bool _focused{ false }; winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; @@ -102,6 +109,10 @@ namespace winrt::TerminalApp::implementation winrt::fire_and_forget _UpdateTitle(); void _ConstructTabRenameBox(const winrt::hstring& tabText); + void _RecalculateAndApplyTabColor(); + void _ApplyTabColor(const winrt::Windows::UI::Color& color); + void _ClearTabBackgroundColor(); + friend class ::TerminalAppLocalTests::TabTests; }; } diff --git a/src/cascadia/TerminalApp/Tab.idl b/src/cascadia/TerminalApp/Tab.idl index 955c243845a..5aba64605fc 100644 --- a/src/cascadia/TerminalApp/Tab.idl +++ b/src/cascadia/TerminalApp/Tab.idl @@ -6,6 +6,6 @@ namespace TerminalApp [default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged { String Title { get; }; - String IconPath { get; }; + Windows.UI.Xaml.Controls.IconSource IconSource { get; }; } } diff --git a/src/cascadia/TerminalApp/TerminalApp.vcxproj b/src/cascadia/TerminalApp/TerminalApp.vcxproj index 320c20a299a..a19fa271c79 100644 --- a/src/cascadia/TerminalApp/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/TerminalApp.vcxproj @@ -67,7 +67,6 @@ - - - $(_BinRoot)TerminalSettings\Microsoft.Terminal.Settings.winmd - true - false - false - $(_BinRoot)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd true diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters index fea379bc66c..9a80a146c43 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters @@ -59,6 +59,7 @@ + settings @@ -202,4 +203,4 @@ app - \ No newline at end of file + diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index 1fe8399b4ed..ab1c9ab5c55 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -10,6 +10,7 @@ #include "ConptyConnection.h" #include +#include #include "ConptyConnection.g.cpp" @@ -97,8 +98,11 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation environment.clear(); }); - // Populate the environment map with the current environment. - RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment)); + { + const auto newEnvironmentBlock{ Utils::CreateEnvironmentBlock() }; + // Populate the environment map with the current environment. + RETURN_IF_FAILED(Utils::UpdateEnvironmentMapW(environment, newEnvironmentBlock.get())); + } { // Convert connection Guid to string and ignore the enclosing '{}'. diff --git a/src/cascadia/TerminalSettings/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl similarity index 85% rename from src/cascadia/TerminalSettings/IControlSettings.idl rename to src/cascadia/TerminalControl/IControlSettings.idl index 47484d99b4a..2f140a0a3f8 100644 --- a/src/cascadia/TerminalSettings/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -2,9 +2,8 @@ // Licensed under the MIT license. import "IKeyBindings.idl"; -import "ICoreSettings.idl"; -namespace Microsoft.Terminal.Settings +namespace Microsoft.Terminal.TerminalControl { enum ScrollbarState { @@ -24,7 +23,7 @@ namespace Microsoft.Terminal.Settings // TermControl's behavior. In these settings there is both the entirety // of the Core ITerminalSettings interface, and any additional settings // for specifically the control. - interface IControlSettings requires Microsoft.Terminal.Settings.ICoreSettings + interface IControlSettings requires ICoreSettings { String ProfileName; @@ -37,7 +36,7 @@ namespace Microsoft.Terminal.Settings Windows.UI.Text.FontWeight FontWeight; String Padding; - IKeyBindings KeyBindings; + Microsoft.Terminal.TerminalControl.IKeyBindings KeyBindings; Boolean CopyOnSelect; diff --git a/src/cascadia/TerminalSettings/IKeyBindings.idl b/src/cascadia/TerminalControl/IKeyBindings.idl similarity index 78% rename from src/cascadia/TerminalSettings/IKeyBindings.idl rename to src/cascadia/TerminalControl/IKeyBindings.idl index 5c43e6dd376..6d3992c5d0a 100644 --- a/src/cascadia/TerminalSettings/IKeyBindings.idl +++ b/src/cascadia/TerminalControl/IKeyBindings.idl @@ -3,7 +3,7 @@ import "KeyChord.idl"; -namespace Microsoft.Terminal.Settings +namespace Microsoft.Terminal.TerminalControl { // [default_interface] interface IKeyBindings diff --git a/src/cascadia/TerminalControl/IMouseWheelListener.idl b/src/cascadia/TerminalControl/IMouseWheelListener.idl index 63089552683..8ba8284b5d4 100644 --- a/src/cascadia/TerminalControl/IMouseWheelListener.idl +++ b/src/cascadia/TerminalControl/IMouseWheelListener.idl @@ -11,6 +11,6 @@ namespace Microsoft.Terminal.TerminalControl [uuid("65b8b8c5-988f-43ff-aba9-e89368da1598")] interface IMouseWheelListener { - Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta); + Boolean OnMouseWheel(Windows.Foundation.Point coord, Int32 delta, Boolean leftButtonDown, Boolean midButtonDown, Boolean rightButtonDown); } } diff --git a/src/cascadia/TerminalSettings/KeyChord.cpp b/src/cascadia/TerminalControl/KeyChord.cpp similarity index 51% rename from src/cascadia/TerminalSettings/KeyChord.cpp rename to src/cascadia/TerminalControl/KeyChord.cpp index 6c3523497e0..c265b061312 100644 --- a/src/cascadia/TerminalSettings/KeyChord.cpp +++ b/src/cascadia/TerminalControl/KeyChord.cpp @@ -6,7 +6,7 @@ #include "KeyChord.g.cpp" -namespace winrt::Microsoft::Terminal::Settings::implementation +namespace winrt::Microsoft::Terminal::TerminalControl::implementation { KeyChord::KeyChord() noexcept : _modifiers{ 0 }, @@ -15,25 +15,25 @@ namespace winrt::Microsoft::Terminal::Settings::implementation } KeyChord::KeyChord(bool ctrl, bool alt, bool shift, int32_t vkey) noexcept : - _modifiers{ (ctrl ? Settings::KeyModifiers::Ctrl : Settings::KeyModifiers::None) | - (alt ? Settings::KeyModifiers::Alt : Settings::KeyModifiers::None) | - (shift ? Settings::KeyModifiers::Shift : Settings::KeyModifiers::None) }, + _modifiers{ (ctrl ? TerminalControl::KeyModifiers::Ctrl : TerminalControl::KeyModifiers::None) | + (alt ? TerminalControl::KeyModifiers::Alt : TerminalControl::KeyModifiers::None) | + (shift ? TerminalControl::KeyModifiers::Shift : TerminalControl::KeyModifiers::None) }, _vkey{ vkey } { } - KeyChord::KeyChord(Settings::KeyModifiers const& modifiers, int32_t vkey) noexcept : + KeyChord::KeyChord(TerminalControl::KeyModifiers const& modifiers, int32_t vkey) noexcept : _modifiers{ modifiers }, _vkey{ vkey } { } - Settings::KeyModifiers KeyChord::Modifiers() noexcept + TerminalControl::KeyModifiers KeyChord::Modifiers() noexcept { return _modifiers; } - void KeyChord::Modifiers(Settings::KeyModifiers const& value) noexcept + void KeyChord::Modifiers(TerminalControl::KeyModifiers const& value) noexcept { _modifiers = value; } diff --git a/src/cascadia/TerminalSettings/KeyChord.h b/src/cascadia/TerminalControl/KeyChord.h similarity index 53% rename from src/cascadia/TerminalSettings/KeyChord.h rename to src/cascadia/TerminalControl/KeyChord.h index e948c1afcd9..910eb6ca15f 100644 --- a/src/cascadia/TerminalSettings/KeyChord.h +++ b/src/cascadia/TerminalControl/KeyChord.h @@ -5,26 +5,26 @@ #include "KeyChord.g.h" -namespace winrt::Microsoft::Terminal::Settings::implementation +namespace winrt::Microsoft::Terminal::TerminalControl::implementation { struct KeyChord : KeyChordT { KeyChord() noexcept; - KeyChord(Settings::KeyModifiers const& modifiers, int32_t vkey) noexcept; + KeyChord(TerminalControl::KeyModifiers const& modifiers, int32_t vkey) noexcept; KeyChord(bool ctrl, bool alt, bool shift, int32_t vkey) noexcept; - Settings::KeyModifiers Modifiers() noexcept; - void Modifiers(Settings::KeyModifiers const& value) noexcept; + TerminalControl::KeyModifiers Modifiers() noexcept; + void Modifiers(TerminalControl::KeyModifiers const& value) noexcept; int32_t Vkey() noexcept; void Vkey(int32_t value) noexcept; private: - Settings::KeyModifiers _modifiers; + TerminalControl::KeyModifiers _modifiers; int32_t _vkey; }; } -namespace winrt::Microsoft::Terminal::Settings::factory_implementation +namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation { struct KeyChord : KeyChordT { diff --git a/src/cascadia/TerminalSettings/KeyChord.idl b/src/cascadia/TerminalControl/KeyChord.idl similarity index 87% rename from src/cascadia/TerminalSettings/KeyChord.idl rename to src/cascadia/TerminalControl/KeyChord.idl index c02459144b3..4952688a602 100644 --- a/src/cascadia/TerminalSettings/KeyChord.idl +++ b/src/cascadia/TerminalControl/KeyChord.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -namespace Microsoft.Terminal.Settings +namespace Microsoft.Terminal.TerminalControl { [flags] enum KeyModifiers diff --git a/src/cascadia/TerminalControl/TSFInputControl.cpp b/src/cascadia/TerminalControl/TSFInputControl.cpp index 1eb56878c8a..23bd9bcbec2 100644 --- a/src/cascadia/TerminalControl/TSFInputControl.cpp +++ b/src/cascadia/TerminalControl/TSFInputControl.cpp @@ -31,14 +31,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation InitializeComponent(); // Create a CoreTextEditingContext for since we are acting like a custom edit control - auto manager = Core::CoreTextServicesManager::GetForCurrentView(); + auto manager = CoreTextServicesManager::GetForCurrentView(); _editContext = manager.CreateEditContext(); // InputPane is manually shown inside of TermControl. - _editContext.InputPaneDisplayPolicy(Core::CoreTextInputPaneDisplayPolicy::Manual); + _editContext.InputPaneDisplayPolicy(CoreTextInputPaneDisplayPolicy::Manual); // set the input scope to Text because this control is for any text. - _editContext.InputScope(Core::CoreTextInputScope::Text); + _editContext.InputScope(CoreTextInputScope::Text); _textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler }); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 62bc7a948e2..058e478bc72 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -16,6 +16,7 @@ #include "TermControlAutomationPeer.h" using namespace ::Microsoft::Console::Types; +using namespace ::Microsoft::Console::VirtualTerminal; using namespace ::Microsoft::Terminal::Core; using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI::Xaml; @@ -25,7 +26,6 @@ using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::ViewManagement; using namespace winrt::Windows::UI::Input; using namespace winrt::Windows::System; -using namespace winrt::Microsoft::Terminal::Settings; using namespace winrt::Windows::ApplicationModel::DataTransfer; // The minimum delay between updates to the scroll bar's values. @@ -56,7 +56,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return initialized; } - TermControl::TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection) : + TermControl::TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection) : _connection{ connection }, _initializedTerminal{ false }, _settings{ settings }, @@ -83,6 +83,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1); _terminal->SetTitleChangedCallback(pfnTitleChanged); + auto pfnTabColorChanged = std::bind(&TermControl::_TerminalTabColorChanged, this, std::placeholders::_1); + _terminal->SetTabColorChangedCallback(pfnTabColorChanged); + auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1); _terminal->SetBackgroundCallback(pfnBackgroundColorChanged); @@ -101,8 +104,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation }; _connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn); - auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1); - _terminal->SetWriteInputCallback(inputFn); + _terminal->SetWriteInputCallback([this](std::wstring& wstr) { + _SendInputToConnection(wstr); + }); _terminal->UpdateSettings(settings); @@ -237,7 +241,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - newSettings: New settings values for the profile in this terminal. // Return Value: // - - winrt::fire_and_forget TermControl::UpdateSettings(Settings::IControlSettings newSettings) + winrt::fire_and_forget TermControl::UpdateSettings(IControlSettings newSettings) { _settings = newSettings; auto weakThis{ get_weak() }; @@ -293,6 +297,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } } + + // Method Description: + // - Writes the given sequence as input to the active terminal connection, + // Arguments: + // - wstr: the string of characters to write to the terminal connection. + // Return Value: + // - + void TermControl::SendInput(const winrt::hstring& wstr) + { + _SendInputToConnection(wstr); + } + void TermControl::ToggleRetroEffect() { auto lock = _terminal->LockForWriting(); @@ -1005,7 +1021,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } const auto modifiers = _GetPressedModifierKeys(); - return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta); + const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() }; + return _terminal->SendMouseEvent(terminalPosition, uiButton, modifiers, sWheelDelta, state); } // Method Description: @@ -1329,10 +1346,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } const auto point = args.GetCurrentPoint(*this); + const auto props = point.Properties(); + const TerminalInput::MouseButtonState state{ props.IsLeftButtonPressed(), props.IsMiddleButtonPressed(), props.IsRightButtonPressed() }; auto result = _DoMouseWheel(point.Position(), ControlKeyStates{ args.KeyModifiers() }, point.Properties().MouseWheelDelta(), - point.Properties().IsLeftButtonPressed()); + state); if (result) { args.Handled(true); @@ -1355,7 +1374,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool TermControl::_DoMouseWheel(const Windows::Foundation::Point point, const ControlKeyStates modifiers, const int32_t delta, - const bool isLeftButtonPressed) + const TerminalInput::MouseButtonState state) { if (_CanSendVTMouseInput()) { @@ -1367,7 +1386,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation return _terminal->SendMouseEvent(_GetTerminalPosition(point), WM_MOUSEWHEEL, _GetPressedModifierKeys(), - ::base::saturated_cast(delta)); + ::base::saturated_cast(delta), + state); } const auto ctrlPressed = modifiers.IsCtrlPressed(); @@ -1383,7 +1403,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } else { - _MouseScrollHandler(delta, point, isLeftButtonPressed); + _MouseScrollHandler(delta, point, state.isLeftButtonDown); } return false; } @@ -1397,11 +1417,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - location: the location of the mouse during this event. This location is // relative to the origin of the control // - delta: the mouse wheel delta that triggered this event. + // - state: the state for each of the mouse buttons individually (pressed/unpressed) bool TermControl::OnMouseWheel(const Windows::Foundation::Point location, - const int32_t delta) + const int32_t delta, + const bool leftButtonDown, + const bool midButtonDown, + const bool rightButtonDown) { const auto modifiers = _GetPressedModifierKeys(); - return _DoMouseWheel(location, modifiers, delta, false); + TerminalInput::MouseButtonState state{ leftButtonDown, midButtonDown, rightButtonDown }; + return _DoMouseWheel(location, modifiers, delta, state); } // Method Description: @@ -1752,12 +1777,18 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } // Method Description: - // - Writes the given sequence as input to the active terminal connection, + // - Writes the given sequence as input to the active terminal connection. + // - This method has been overloaded to allow zero-copy winrt::param::hstring optimizations. // Arguments: // - wstr: the string of characters to write to the terminal connection. // Return Value: // - - void TermControl::_SendInputToConnection(const std::wstring& wstr) + void TermControl::_SendInputToConnection(const winrt::hstring& wstr) + { + _connection.WriteInput(wstr); + } + + void TermControl::_SendInputToConnection(std::wstring_view wstr) { _connection.WriteInput(wstr); } @@ -2052,6 +2083,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation { _titleChangedHandlers(winrt::hstring{ wstr }); } + void TermControl::_TerminalTabColorChanged(const std::optional /*color*/) + { + _TabColorChangedHandlers(*this, nullptr); + } void TermControl::_CopyToClipboard(const std::wstring_view& wstr) { @@ -2320,7 +2355,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const int32_t& fontHeight, const winrt::Windows::UI::Text::FontWeight& fontWeight, const winrt::hstring& fontFace, - const Microsoft::Terminal::Settings::ScrollbarState& scrollState, + const ScrollbarState& scrollState, const winrt::hstring& padding, const uint32_t dpi) { @@ -2822,6 +2857,17 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _renderer->ResetErrorStateAndResume(); } + IControlSettings TermControl::Settings() const + { + return _settings; + } + + Windows::Foundation::IReference TermControl::TabColor() noexcept + { + auto coreColor = _terminal->GetTabColor(); + return coreColor.has_value() ? Windows::Foundation::IReference(coreColor.value()) : nullptr; + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 4499c589efc..b34ea7b813c 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -7,7 +7,6 @@ #include "CopyToClipboardEventArgs.g.h" #include "PasteFromClipboardEventArgs.g.h" #include -#include #include "../../renderer/base/Renderer.hpp" #include "../../renderer/dx/DxRenderer.hpp" #include "../../renderer/uia/UiaRenderer.hpp" @@ -17,6 +16,11 @@ #include "SearchBoxControl.h" #include "ThrottledFunc.h" +namespace Microsoft::Console::VirtualTerminal +{ + struct MouseButtonState; +} + namespace winrt::Microsoft::Terminal::TerminalControl::implementation { struct CopyToClipboardEventArgs : @@ -56,9 +60,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation struct TermControl : TermControlT { - TermControl(Settings::IControlSettings settings, TerminalConnection::ITerminalConnection connection); + TermControl(IControlSettings settings, TerminalConnection::ITerminalConnection connection); - winrt::fire_and_forget UpdateSettings(Settings::IControlSettings newSettings); + winrt::fire_and_forget UpdateSettings(IControlSettings newSettings); hstring Title(); hstring GetProfileName() const; @@ -77,6 +81,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void AdjustFontSize(int fontSizeDelta); void ResetFontSize(); + void SendInput(const winrt::hstring& input); void ToggleRetroEffect(); winrt::fire_and_forget RenderEngineSwapChainChanged(); @@ -88,7 +93,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation bool OnDirectKeyEvent(const uint32_t vkey, const bool down); - bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta); + bool OnMouseWheel(const Windows::Foundation::Point location, const int32_t delta, const bool leftButtonDown, const bool midButtonDown, const bool rightButtonDown); ~TermControl(); @@ -98,16 +103,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const Windows::UI::Xaml::Thickness GetPadding(); TerminalConnection::ConnectionState ConnectionState() const; + IControlSettings Settings() const; - static Windows::Foundation::Size GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi); + static Windows::Foundation::Size GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi); static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars, const int32_t& fontSize, const winrt::Windows::UI::Text::FontWeight& fontWeight, const winrt::hstring& fontFace, - const Microsoft::Terminal::Settings::ScrollbarState& scrollState, + const ScrollbarState& scrollState, const winrt::hstring& padding, const uint32_t dpi); + Windows::Foundation::IReference TabColor() noexcept; + // clang-format off // -------------------------------- WinRT Events --------------------------------- DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs); @@ -119,6 +127,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable); TYPED_EVENT(Initialized, TerminalControl::TermControl, Windows::UI::Xaml::RoutedEventArgs); + TYPED_EVENT(TabColorChanged, IInspectable, IInspectable); // clang-format on private: @@ -137,7 +146,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine; std::unique_ptr<::Microsoft::Console::Render::UiaEngine> _uiaEngine; - Settings::IControlSettings _settings; + IControlSettings _settings; bool _focused; std::atomic _closing; @@ -213,13 +222,15 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e); void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition); - void _SendInputToConnection(const std::wstring& wstr); + void _SendInputToConnection(const winrt::hstring& wstr); + void _SendInputToConnection(std::wstring_view wstr); void _SendPastedTextToConnection(const std::wstring& wstr); void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e); void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args); void _DoResizeUnderLock(const double newWidth, const double newHeight); void _RefreshSizeUnderLock(); void _TerminalTitleChanged(const std::wstring_view& wstr); + void _TerminalTabColorChanged(const std::optional color); void _CopyToClipboard(const std::wstring_view& wstr); void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize); void _TerminalCursorPositionChanged(); @@ -227,7 +238,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _MouseScrollHandler(const double mouseDelta, const Windows::Foundation::Point point, const bool isLeftButtonPressed); void _MouseZoomHandler(const double delta); void _MouseTransparencyHandler(const double delta); - bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const bool isLeftButtonPressed); + bool _DoMouseWheel(const Windows::Foundation::Point point, const ::Microsoft::Terminal::Core::ControlKeyStates modifiers, const int32_t delta, const ::Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state); bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index d9a2ac5cdff..4e3eefa0c44 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -2,6 +2,7 @@ // Licensed under the MIT license. import "IMouseWheelListener.idl"; +import "IControlSettings.idl"; namespace Microsoft.Terminal.TerminalControl { @@ -32,11 +33,13 @@ namespace Microsoft.Terminal.TerminalControl [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IDirectKeyListener, IMouseWheelListener { - TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); + TermControl(Microsoft.Terminal.TerminalControl.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); - static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi); + static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.TerminalControl.IControlSettings settings, UInt32 dpi); - void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings); + void UpdateSettings(Microsoft.Terminal.TerminalControl.IControlSettings newSettings); + + Microsoft.Terminal.TerminalControl.IControlSettings Settings { get; }; event TitleChangedEventArgs TitleChanged; event FontSizeChangedEventArgs FontSizeChanged; @@ -68,6 +71,10 @@ namespace Microsoft.Terminal.TerminalControl void AdjustFontSize(Int32 fontSizeDelta); void ResetFontSize(); + void SendInput(String input); void ToggleRetroEffect(); + + Windows.Foundation.IReference TabColor { get; }; + event Windows.Foundation.TypedEventHandler TabColorChanged; } } diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index bdad948d0b4..14992abab81 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -19,7 +19,6 @@ projects compile properly when they depend on this "Microsoft.winmd." --> 3 - + + $(MarkupCompilePass1DependsOn);OpenConsoleStripDuplicateWinmdFromReferencesBeforePass1 + $(MarkupCompilePass2DependsOn);OpenConsoleStripDuplicateWinmdFromReferencesBeforePass2 + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters index f4bdeb33de2..d9eaaf778d3 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters @@ -15,8 +15,6 @@ - - @@ -26,15 +24,15 @@ - - + + @@ -42,6 +40,8 @@ + + @@ -51,4 +51,4 @@ Resources - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettings/ICoreSettings.idl b/src/cascadia/TerminalCore/ICoreSettings.idl similarity index 85% rename from src/cascadia/TerminalSettings/ICoreSettings.idl rename to src/cascadia/TerminalCore/ICoreSettings.idl index 3bc4834592d..de11ec04b05 100644 --- a/src/cascadia/TerminalSettings/ICoreSettings.idl +++ b/src/cascadia/TerminalCore/ICoreSettings.idl @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -namespace Microsoft.Terminal.Settings +namespace Microsoft.Terminal.TerminalControl { enum CursorStyle { @@ -34,6 +34,8 @@ namespace Microsoft.Terminal.Settings String WordDelimiters; Boolean ForceVTInput; + + Windows.Foundation.IReference TabColor; }; } diff --git a/src/cascadia/TerminalCore/ITerminalInput.hpp b/src/cascadia/TerminalCore/ITerminalInput.hpp index 1d9785e4ee6..307f03b1165 100644 --- a/src/cascadia/TerminalCore/ITerminalInput.hpp +++ b/src/cascadia/TerminalCore/ITerminalInput.hpp @@ -17,7 +17,7 @@ namespace Microsoft::Terminal::Core ITerminalInput& operator=(ITerminalInput&&) = default; virtual bool SendKeyEvent(const WORD vkey, const WORD scanCode, const ControlKeyStates states, const bool keyDown) = 0; - virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) = 0; + virtual bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) = 0; virtual bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) = 0; // void SendMouseEvent(uint row, uint col, KeyModifiers modifiers); diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index b71471fc7ef..f6b3950a262 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -10,9 +10,9 @@ #include "../../inc/argb.h" #include "../../types/inc/utils.hpp" -#include "winrt/Microsoft.Terminal.Settings.h" +#include -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace Microsoft::Terminal::Core; using namespace Microsoft::Console; using namespace Microsoft::Console::Render; @@ -84,8 +84,8 @@ void Terminal::Create(COORD viewportSize, SHORT scrollbackLines, IRenderTarget& // Arguments: // - settings: the set of CoreSettings we need to use to initialize the terminal // - renderTarget: A render target the terminal can use for paint invalidation. -void Terminal::CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings, - Microsoft::Console::Render::IRenderTarget& renderTarget) +void Terminal::CreateFromSettings(ICoreSettings settings, + IRenderTarget& renderTarget) { const COORD viewportSize{ Utils::ClampToShortMax(settings.InitialCols(), 1), Utils::ClampToShortMax(settings.InitialRows(), 1) }; @@ -101,7 +101,7 @@ void Terminal::CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSet // CoreSettings object. // Arguments: // - settings: an ICoreSettings with new settings values for us to use. -void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings) +void Terminal::UpdateSettings(ICoreSettings settings) { _defaultFg = settings.DefaultForeground(); _defaultBg = settings.DefaultBackground(); @@ -147,6 +147,19 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting _terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput()); + if (settings.TabColor() == nullptr) + { + _tabColor = std::nullopt; + } + else + { + _tabColor = til::color(settings.TabColor().Value() | 0xff000000); + } + if (_pfnTabColorChanged) + { + _pfnTabColorChanged(_tabColor); + } + // TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we // have a smaller scrollback. We should do this carefully - if the new buffer // size is smaller than where the mutable viewport currently is, we'll want @@ -344,7 +357,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting // If the old scrolloffset was 0, then we weren't scrolled back at all // before, and shouldn't be now either. - _scrollOffset = originalOffsetWasZero ? 0 : ::base::ClampSub(_mutableViewport.Top(), newVisibleTop); + _scrollOffset = originalOffsetWasZero ? 0 : static_cast(::base::ClampSub(_mutableViewport.Top(), newVisibleTop)); // GH#5029 - make sure to InvalidateAll here, so that we'll paint the entire visible viewport. try @@ -430,6 +443,19 @@ bool Terminal::SendKeyEvent(const WORD vkey, _StoreKeyEvent(vkey, scanCode); + // As a Terminal we're mostly interested in getting key events from physical hardware (mouse & keyboard). + // We're thus ignoring events whose values are outside the valid range and unlikely to be generated by the current keyboard. + // It's very likely that a proper followup character event will be sent to us. + // This prominently happens using AutoHotKey's keyboard remapping feature, + // which sends input events whose vkey is 0xff and scanCode is 0. + // We need to check for this early, as _CharacterFromKeyEvent() always returns 0 for such invalid values, + // making us believe that this is an actual non-character input, while it usually isn't. + // GH#7064 + if (vkey == 0 || vkey >= 0xff || scanCode == 0) + { + return false; + } + const auto isAltOnlyPressed = states.IsAltPressed() && !states.IsCtrlPressed(); const auto isSuppressedAltGrAlias = !_altGrAliasing && states.IsAltPressed() && states.IsCtrlPressed(); @@ -484,7 +510,7 @@ bool Terminal::SendKeyEvent(const WORD vkey, // Return Value: // - true if we translated the key event, and it should not be processed any further. // - false if we did not translate the key, and it should be processed into a character. -bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) +bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const TerminalInput::MouseButtonState state) { // GH#6401: VT applications should be able to receive mouse events from outside the // terminal buffer. This is likely to happen when the user drags the cursor offscreen. @@ -493,7 +519,7 @@ bool Terminal::SendMouseEvent(const COORD viewportPos, const unsigned int uiButt #pragma warning(suppress : 26496) // analysis can't tell we're assigning through a reference below auto clampedPos{ viewportPos }; _mutableViewport.ToOrigin().Clamp(clampedPos); - return _terminalInput->HandleMouse(clampedPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta); + return _terminalInput->HandleMouse(clampedPos, uiButton, GET_KEYSTATE_WPARAM(states.Value()), wheelDelta, state); } // Method Description: @@ -817,8 +843,12 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) } } - if (updatedViewport) + // If the viewport moved, or we circled the buffer, we might need to update + // our _scrollOffset + if (updatedViewport || newRows != 0) { + const auto oldScrollOffset = _scrollOffset; + // scroll if... // - no selection is active // - viewport is already at the bottom @@ -826,6 +856,18 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition) _scrollOffset = scrollToOutput ? 0 : _scrollOffset + scrollAmount + newRows; + // Clamp the range to make sure that we don't scroll way off the top of the buffer + _scrollOffset = std::clamp(_scrollOffset, + 0, + _buffer->GetSize().Height() - _mutableViewport.Height()); + + // If the new scroll offset is different, then we'll still want to raise a scroll event + updatedViewport = updatedViewport || (oldScrollOffset != _scrollOffset); + } + + // If the viewport moved, then send a scrolling notification. + if (updatedViewport) + { _NotifyScrollEvent(); } @@ -900,6 +942,11 @@ void Terminal::SetTitleChangedCallback(std::function)> pfn) noexcept +{ + _pfnTabColorChanged.swap(pfn); +} + void Terminal::SetCopyToClipboardCallback(std::function pfn) noexcept { _pfnCopyToClipboard.swap(pfn); @@ -956,3 +1003,8 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept const auto& cursor = _buffer->GetCursor(); return cursor.IsBlinkingAllowed(); } + +const std::optional Terminal::GetTabColor() const noexcept +{ + return _tabColor; +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index ac8d59d3e02..df70bc0184c 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -19,7 +19,7 @@ // You have to forward decl the ICoreSettings here, instead of including the header. // If you include the header, there will be compilation errors with other // headers that include Terminal.hpp -namespace winrt::Microsoft::Terminal::Settings +namespace winrt::Microsoft::Terminal::TerminalControl { struct ICoreSettings; } @@ -58,10 +58,10 @@ class Microsoft::Terminal::Core::Terminal final : SHORT scrollbackLines, Microsoft::Console::Render::IRenderTarget& renderTarget); - void CreateFromSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings, + void CreateFromSettings(winrt::Microsoft::Terminal::TerminalControl::ICoreSettings settings, Microsoft::Console::Render::IRenderTarget& renderTarget); - void UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSettings settings); + void UpdateSettings(winrt::Microsoft::Terminal::TerminalControl::ICoreSettings settings); // Write goes through the parser void Write(std::wstring_view stringView); @@ -116,7 +116,7 @@ class Microsoft::Terminal::Core::Terminal final : #pragma region ITerminalInput // These methods are defined in Terminal.cpp bool SendKeyEvent(const WORD vkey, const WORD scanCode, const Microsoft::Terminal::Core::ControlKeyStates states, const bool keyDown) override; - bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta) override; + bool SendMouseEvent(const COORD viewportPos, const unsigned int uiButton, const ControlKeyStates states, const short wheelDelta, const Microsoft::Console::VirtualTerminal::TerminalInput::MouseButtonState state) override; bool SendCharEvent(const wchar_t ch, const WORD scanCode, const ControlKeyStates states) override; [[nodiscard]] HRESULT UserResize(const COORD viewportSize) noexcept override; @@ -168,6 +168,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetWriteInputCallback(std::function pfn) noexcept; void SetTitleChangedCallback(std::function pfn) noexcept; + void SetTabColorChangedCallback(std::function)> pfn) noexcept; void SetCopyToClipboardCallback(std::function pfn) noexcept; void SetScrollPositionChangedCallback(std::function pfn) noexcept; void SetCursorPositionChangedCallback(std::function pfn) noexcept; @@ -176,6 +177,8 @@ class Microsoft::Terminal::Core::Terminal final : void SetCursorOn(const bool isOn); bool IsCursorBlinkingAllowed() const noexcept; + const std::optional GetTabColor() const noexcept; + #pragma region TextSelection // These methods are defined in TerminalSelection.cpp enum class SelectionExpansionMode @@ -199,12 +202,14 @@ class Microsoft::Terminal::Core::Terminal final : std::function _pfnScrollPositionChanged; std::function _pfnBackgroundColorChanged; std::function _pfnCursorPositionChanged; + std::function)> _pfnTabColorChanged; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput; std::optional _title; std::wstring _startingTitle; + std::optional _tabColor; std::array _colorTable; COLORREF _defaultFg; diff --git a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp index f8ff0d129da..635d9bded26 100644 --- a/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatchGraphics.cpp @@ -158,8 +158,12 @@ bool TerminalDispatch::SetGraphicsRendition(const gsl::spanTerminalCore StaticLibrary 10.0.17763.0 - Microsoft.Terminal.Core + Microsoft.Terminal.TerminalControl + + + true + + + true - "$(SolutionDir)\src\cascadia\TerminalSettings\Generated Files\winrt";$(SolutionDir)src\cascadia\TerminalSettings;$(CAExcludePath) + "$(SolutionDir)\src\cascadia\TerminalCore\Generated Files\winrt";$(SolutionDir)src\cascadia\TerminalCore;$(CAExcludePath) - + - + @@ -40,20 +50,23 @@ + + + pch.h - $(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)src\cascadia\TerminalSettings\Generated Files";%(AdditionalIncludeDirectories); + $(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)src\cascadia\TerminalCore\Generated Files";%(AdditionalIncludeDirectories); - - + + diff --git a/src/cascadia/TerminalCore/pch.h b/src/cascadia/TerminalCore/pch.h index e03399daf91..58fe68df1da 100644 --- a/src/cascadia/TerminalCore/pch.h +++ b/src/cascadia/TerminalCore/pch.h @@ -4,3 +4,4 @@ #pragma once #include +#include "winrt/Windows.Foundation.h" diff --git a/src/cascadia/TerminalSettings/TerminalSettings.def b/src/cascadia/TerminalSettings/TerminalSettings.def deleted file mode 100644 index 8c1a02932d0..00000000000 --- a/src/cascadia/TerminalSettings/TerminalSettings.def +++ /dev/null @@ -1,3 +0,0 @@ -EXPORTS -DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE -DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/cascadia/TerminalSettings/TerminalSettings.vcxproj b/src/cascadia/TerminalSettings/TerminalSettings.vcxproj deleted file mode 100644 index eef3fe672f4..00000000000 --- a/src/cascadia/TerminalSettings/TerminalSettings.vcxproj +++ /dev/null @@ -1,48 +0,0 @@ - - - - {CA5CAD1A-d7ec-4107-b7c6-79cb77ae2907} - TerminalSettings - Microsoft.Terminal.Settings - - - DynamicLibrary - Console - - true - - true - - - - - - - KeyChord.idl - - - - - Create - - - KeyChord.idl - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/cascadia/TerminalSettings/packages.config b/src/cascadia/TerminalSettings/packages.config deleted file mode 100644 index 8db4233e6a9..00000000000 --- a/src/cascadia/TerminalSettings/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/cascadia/TerminalSettings/pch.h b/src/cascadia/TerminalSettings/pch.h deleted file mode 100644 index 012b411e47d..00000000000 --- a/src/cascadia/TerminalSettings/pch.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// -// pch.h -// Header for platform projection include files -// - -#pragma once - -#define WIN32_LEAN_AND_MEAN - -#include -// This is inexplicable, but for whatever reason, cppwinrt conflicts with the -// SDK definition of this function, so the only fix is to undef it. -// from WinBase.h -// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime -#ifdef GetCurrentTime -#undef GetCurrentTime -#endif - -#include -#include -#include diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index f4cc7a24063..a68aa213db1 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -8,9 +8,7 @@ // `renderer.PaintFrame()` is called, the tests will validate the expected // output, and then flush the output of the VtEngine straight to the Terminal. -#include "precomp.h" -#include -#include "../../inc/consoletaeftemplates.hpp" +#include "pch.h" #include "../../types/inc/Viewport.hpp" #include "../../types/inc/convert.hpp" diff --git a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp index 037c81db45b..27b114793f7 100644 --- a/src/cascadia/UnitTests_TerminalCore/InputTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/InputTest.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include "precomp.h" +#include "pch.h" #include #include "../cascadia/TerminalCore/Terminal.hpp" @@ -30,6 +30,7 @@ namespace TerminalCoreUnitTests TEST_METHOD(AltShiftKey); TEST_METHOD(AltSpace); + TEST_METHOD(InvalidKeyEvent); void _VerifyExpectedInput(std::wstring& actualInput) { @@ -65,4 +66,14 @@ namespace TerminalCoreUnitTests VERIFY_IS_FALSE(term.SendKeyEvent(L' ', 0, ControlKeyStates::LeftAltPressed, false)); VERIFY_IS_FALSE(term.SendCharEvent(L' ', 0, ControlKeyStates::LeftAltPressed)); } + + void InputTest::InvalidKeyEvent() + { + // Certain applications like AutoHotKey and its keyboard remapping feature, + // send us key events using SendInput() whose values are outside of the valid range. + // We don't want to handle those events as we're probably going to get a proper followup character event. + VERIFY_IS_FALSE(term.SendKeyEvent(0, 123, {}, true)); + VERIFY_IS_FALSE(term.SendKeyEvent(255, 123, {}, true)); + VERIFY_IS_FALSE(term.SendKeyEvent(123, 0, {}, true)); + } } diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h index 9d91488075c..1a328c9d015 100644 --- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -1,13 +1,14 @@ #pragma once -#include "precomp.h" +#include "pch.h" #include #include "DefaultSettings.h" -#include "winrt/Microsoft.Terminal.Settings.h" +#include +#include "../inc/cppwinrt_utils.h" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; namespace TerminalCoreUnitTests { @@ -63,6 +64,8 @@ namespace TerminalCoreUnitTests // other unimplemented methods void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {} + GETSET_PROPERTY(winrt::Windows::Foundation::IReference, TabColor, nullptr); + private: int32_t _historySize; int32_t _initialRows; diff --git a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp index aa7f28365ff..11a6d1021b3 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScreenSizeLimitsTest.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include "precomp.h" +#include "pch.h" #include #include "../cascadia/TerminalCore/Terminal.hpp" @@ -9,7 +9,7 @@ #include "../renderer/inc/DummyRenderTarget.hpp" #include "consoletaeftemplates.hpp" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace Microsoft::Terminal::Core; using namespace WEX::Logging; using namespace WEX::TestExecution; diff --git a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp index bf786ea6ca2..5e0aa4e86e5 100644 --- a/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ScrollTest.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include "precomp.h" +#include "pch.h" #include #include @@ -16,7 +16,7 @@ #include "consoletaeftemplates.hpp" #include "TestUtils.h" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace Microsoft::Terminal::Core; using namespace ::Microsoft::Console::Types; diff --git a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp index 39f78b0b41c..325cb073972 100644 --- a/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/SelectionTest.cpp @@ -5,7 +5,7 @@ * This File was generated using the VisualTAEF C++ Project Wizard. * Class Name: SelectionTest */ -#include "precomp.h" +#include "pch.h" #include #include "../cascadia/TerminalCore/Terminal.hpp" @@ -17,7 +17,7 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace Microsoft::Terminal::Core; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; namespace TerminalCoreUnitTests { diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index b236765bb43..c3c4d1bcdd9 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include "precomp.h" +#include "pch.h" #include #include "../cascadia/TerminalCore/Terminal.hpp" @@ -9,7 +9,7 @@ #include "../renderer/inc/DummyRenderTarget.hpp" #include "consoletaeftemplates.hpp" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace Microsoft::Terminal::Core; using namespace WEX::Logging; diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp index c3bca20d903..e4636081803 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalBufferTests.cpp @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -#include "precomp.h" +#include "pch.h" #include #include "../renderer/inc/DummyRenderTarget.hpp" @@ -10,7 +10,7 @@ #include "consoletaeftemplates.hpp" #include "TestUtils.h" -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace Microsoft::Terminal::Core; using namespace WEX::Common; @@ -39,6 +39,8 @@ class TerminalCoreUnitTests::TerminalBufferTests final TEST_METHOD(TestWrappingCharByChar); TEST_METHOD(TestWrappingALongString); + TEST_METHOD(DontSnapToOutputTest); + TEST_METHOD_SETUP(MethodSetup) { // STEP 1: Set up the Terminal @@ -149,3 +151,85 @@ void TerminalBufferTests::TestWrappingALongString() TestUtils::VerifyExpectedString(termTb, TestUtils::Test100CharsString, { 0, 0 }); } + +void TerminalBufferTests::DontSnapToOutputTest() +{ + auto& termTb = *term->_buffer; + auto& termSm = *term->_stateMachine; + const auto initialView = term->GetViewport(); + + VERIFY_ARE_EQUAL(0, initialView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight, initialView.BottomExclusive()); + VERIFY_ARE_EQUAL(0, term->_scrollOffset); + + // -1 so that we don't print the last \n + for (int i = 0; i < TerminalViewHeight + 8 - 1; i++) + { + termSm.ProcessString(L"x\n"); + } + + const auto secondView = term->GetViewport(); + + VERIFY_ARE_EQUAL(8, secondView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight + 8, secondView.BottomExclusive()); + VERIFY_ARE_EQUAL(0, term->_scrollOffset); + + Log::Comment(L"Scroll up one line"); + term->_scrollOffset = 1; + + const auto thirdView = term->GetViewport(); + VERIFY_ARE_EQUAL(7, thirdView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight + 7, thirdView.BottomExclusive()); + VERIFY_ARE_EQUAL(1, term->_scrollOffset); + + Log::Comment(L"Print a few lines, to see that the viewport stays where it was"); + for (int i = 0; i < 8; i++) + { + termSm.ProcessString(L"x\n"); + } + + const auto fourthView = term->GetViewport(); + VERIFY_ARE_EQUAL(7, fourthView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight + 7, fourthView.BottomExclusive()); + VERIFY_ARE_EQUAL(1 + 8, term->_scrollOffset); + + Log::Comment(L"Print enough lines to get the buffer just about ready to " + L"circle (on the next newline)"); + auto viewBottom = term->_mutableViewport.BottomInclusive(); + do + { + termSm.ProcessString(L"x\n"); + viewBottom = term->_mutableViewport.BottomInclusive(); + } while (viewBottom < termTb.GetSize().BottomInclusive()); + + const auto fifthView = term->GetViewport(); + VERIFY_ARE_EQUAL(7, fifthView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight + 7, fifthView.BottomExclusive()); + VERIFY_ARE_EQUAL(TerminalHistoryLength - 7, term->_scrollOffset); + + Log::Comment(L"Print 3 more lines, and see that we stick to where the old " + L"rows now are in the buffer (after circling)"); + for (int i = 0; i < 3; i++) + { + termSm.ProcessString(L"x\n"); + Log::Comment(NoThrowString().Format( + L"_scrollOffset: %d", term->_scrollOffset)); + } + const auto sixthView = term->GetViewport(); + VERIFY_ARE_EQUAL(4, sixthView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight + 4, sixthView.BottomExclusive()); + VERIFY_ARE_EQUAL(TerminalHistoryLength - 4, term->_scrollOffset); + + Log::Comment(L"Print 8 more lines, and see that we're now just stuck at the" + L"top of the buffer"); + for (int i = 0; i < 8; i++) + { + termSm.ProcessString(L"x\n"); + Log::Comment(NoThrowString().Format( + L"_scrollOffset: %d", term->_scrollOffset)); + } + const auto seventhView = term->GetViewport(); + VERIFY_ARE_EQUAL(0, seventhView.Top()); + VERIFY_ARE_EQUAL(TerminalViewHeight, seventhView.BottomExclusive()); + VERIFY_ARE_EQUAL(TerminalHistoryLength, term->_scrollOffset); +} diff --git a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj index 48734244141..59a1b747b3e 100644 --- a/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj +++ b/src/cascadia/UnitTests_TerminalCore/UnitTests.vcxproj @@ -7,14 +7,15 @@ UnitTests_TerminalCore Terminal.Core.Unit.Tests DynamicLibrary + false - + - + Create @@ -83,18 +84,18 @@ - + - ..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalSettings\Generated Files";$(OpenConsoleDir)\src\host;%(AdditionalIncludeDirectories) - precomp.h + ..;$(SolutionDir)src\inc;$(SolutionDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalControl\Generated Files";$(OpenConsoleDir)\src\host;%(AdditionalIncludeDirectories) + pch.h WindowsApp.lib;%(AdditionalDependencies) - + diff --git a/src/cascadia/TerminalSettings/pch.cpp b/src/cascadia/UnitTests_TerminalCore/pch.cpp similarity index 100% rename from src/cascadia/TerminalSettings/pch.cpp rename to src/cascadia/UnitTests_TerminalCore/pch.cpp diff --git a/src/cascadia/UnitTests_TerminalCore/pch.h b/src/cascadia/UnitTests_TerminalCore/pch.h new file mode 100644 index 00000000000..c33ba50c236 --- /dev/null +++ b/src/cascadia/UnitTests_TerminalCore/pch.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- pch.h + +Abstract: +- Contains external headers to include in the precompile phase of console build + process. +- Avoid including internal project headers. Instead include them only in the + classes that need them (helps with test project building). + +Author(s): +- Carlos Zamora (cazamor) April 2019 +--*/ + +#pragma once + +#define BLOCK_TIL +// This includes support libraries from the CRT, STL, WIL, and GSL +#include "LibraryIncludes.h" +// This is inexplicable, but for whatever reason, cppwinrt conflicts with the +// SDK definition of this function, so the only fix is to undef it. +// from WinBase.h +// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif + +#include +#include +#include + +#include +#include "consoletaeftemplates.hpp" + +#include +#include +#include + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#include "til.h" + +// +// These are needed because the roundtrip tests included in this library also +// re-use some conhost code that depends on these. + +#include "conddkrefs.h" +// From ntdef.h, but that can't be included or it'll fight over PROBE_ALIGNMENT and other such arch specific defs +typedef _Return_type_success_(return >= 0) LONG NTSTATUS; +/*lint -save -e624 */ // Don't complain about different typedefs. +typedef NTSTATUS* PNTSTATUS; +/*lint -restore */ // Resume checking for different typedefs. +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +// diff --git a/src/cascadia/UnitTests_TerminalCore/precomp.cpp b/src/cascadia/UnitTests_TerminalCore/precomp.cpp deleted file mode 100644 index 6a6fa8e5af1..00000000000 --- a/src/cascadia/UnitTests_TerminalCore/precomp.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" \ No newline at end of file diff --git a/src/cascadia/UnitTests_TerminalCore/precomp.h b/src/cascadia/UnitTests_TerminalCore/precomp.h deleted file mode 100644 index 53dbb752420..00000000000 --- a/src/cascadia/UnitTests_TerminalCore/precomp.h +++ /dev/null @@ -1,47 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- precomp.h - -Abstract: -- Contains external headers to include in the precompile phase of console build - process. -- Avoid including internal project headers. Instead include them only in the - classes that need them (helps with test project building). - -Author(s): -- Carlos Zamora (cazamor) April 2019 ---*/ - -#pragma once - -// -// This header and define are needed so that the console host code can build in -// this test binary. - -// Block minwindef.h min/max macros to prevent conflict -#define NOMINMAX - -// This includes a lot of common headers needed by both the host and the propsheet -// including: windows.h, winuser, ntstatus, assert, and the DDK -#include "HostAndPropsheetIncludes.h" -// - -// This includes support libraries from the CRT, STL, WIL, and GSL -#include "LibraryIncludes.h" - -#ifdef BUILDING_INSIDE_WINIDE -#define DbgRaiseAssertionFailure() __int2c() -#endif - -#include - -// Comment to build against the private SDK. -#define CON_BUILD_PUBLIC - -#ifdef CON_BUILD_PUBLIC -#define CON_USERPRIVAPI_INDIRECT -#define CON_DPIAPI_INDIRECT -#endif diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 36ec74db1a7..1f0fb413e26 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -17,6 +17,10 @@ using namespace winrt::Windows::Foundation::Numerics; using namespace ::Microsoft::Console; using namespace ::Microsoft::Console::Types; +// This magic flag is "documented" at https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301(v=vs.85).aspx +// "If the high-order bit is 1, the key is down; otherwise, it is up." +static constexpr short KeyPressed{ gsl::narrow_cast(0x8000) }; + AppHost::AppHost() noexcept : _app{}, _logic{ nullptr }, // don't make one, we're going to take a ref on app's @@ -405,7 +409,11 @@ void AppHost::_WindowMouseWheeled(const til::point coord, const int32_t delta) const til::point offsetPoint = coord - controlOrigin; - if (control.OnMouseWheel(offsetPoint, delta)) + const auto lButtonDown = WI_IsFlagSet(GetKeyState(VK_LBUTTON), KeyPressed); + const auto mButtonDown = WI_IsFlagSet(GetKeyState(VK_MBUTTON), KeyPressed); + const auto rButtonDown = WI_IsFlagSet(GetKeyState(VK_RBUTTON), KeyPressed); + + if (control.OnMouseWheel(offsetPoint, delta, lButtonDown, mButtonDown, rButtonDown)) { // If the element handled the mouse wheel event, don't // continue to iterate over the remaining controls. diff --git a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj index 782d9703990..b940f569e23 100644 --- a/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj +++ b/src/cascadia/WindowsTerminal/WindowsTerminal.vcxproj @@ -33,12 +33,6 @@ $(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;%(AdditionalIncludeDirectories); - - - "$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";%(AdditionalIncludeDirectories); - gdi32.lib;dwmapi.lib;Shcore.lib;UxTheme.lib;%(AdditionalDependencies) @@ -78,7 +72,6 @@ - diff --git a/src/cascadia/WindowsTerminalUniversal/WindowsTerminalUniversal.vcxproj b/src/cascadia/WindowsTerminalUniversal/WindowsTerminalUniversal.vcxproj index 84e99be6046..f001359816c 100644 --- a/src/cascadia/WindowsTerminalUniversal/WindowsTerminalUniversal.vcxproj +++ b/src/cascadia/WindowsTerminalUniversal/WindowsTerminalUniversal.vcxproj @@ -89,12 +89,6 @@ $(OpenConsoleDir)\src\inc;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\Console;$(OpenConsoleDir)\dep\Win32K;$(OpenConsoleDir)\dep\gsl\include;$(OpenConsoleDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;%(AdditionalIncludeDirectories); - - - "$(OpenConsoleDir)src\cascadia\TerminalCore\lib\Generated Files";"$(OpenConsoleDir)src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories); - gdi32.lib;dwmapi.lib;Shcore.lib;%(AdditionalDependencies) @@ -149,7 +143,6 @@ - diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 3d9599bbeef..dcc53ef23ce 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -114,7 +114,7 @@ private: \ // private _setName() method, that the class can internally use to change the // value when it _knows_ it doesn't need to raise the PropertyChanged event // (like when the class is being initialized). -#define OBSERVABLE_GETSET_PROPERTY(type, name, event) \ +#define OBSERVABLE_GETSET_PROPERTY(type, name, event, ...) \ public: \ type name() { return _##name; }; \ void name(const type& value) \ @@ -127,7 +127,7 @@ public: }; \ \ private: \ - const type _##name; \ + const type _##name{ __VA_ARGS__ }; \ void _set##name(const type& value) \ { \ const_cast(_##name) = value; \ diff --git a/src/cascadia/ut_app/JsonTests.cpp b/src/cascadia/ut_app/JsonTests.cpp index 1afbeaeddff..f18579b1d0d 100644 --- a/src/cascadia/ut_app/JsonTests.cpp +++ b/src/cascadia/ut_app/JsonTests.cpp @@ -14,7 +14,7 @@ using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; using namespace winrt::TerminalApp; -using namespace winrt::Microsoft::Terminal::Settings; +using namespace winrt::Microsoft::Terminal::TerminalControl; namespace TerminalAppUnitTests { diff --git a/src/cascadia/ut_app/JsonUtilsTests.cpp b/src/cascadia/ut_app/JsonUtilsTests.cpp index f3f3c7c5b2c..44d2c02fe44 100644 --- a/src/cascadia/ut_app/JsonUtilsTests.cpp +++ b/src/cascadia/ut_app/JsonUtilsTests.cpp @@ -33,6 +33,8 @@ struct ConversionTrait { return value.isInt(); } + + std::string TypeDescription() const { return ""; } }; // Converts a JSON string value to an int and multiplies it by a specified factor @@ -49,6 +51,8 @@ struct CustomConverter { return true; } + + std::string TypeDescription() const { return ""; } }; enum class JsonTestEnum : int @@ -130,7 +134,7 @@ namespace TerminalAppUnitTests //// 1. Bare Value //// //// 1.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValue(object), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(object), DeserializationError, _ReturnTrueForException); //// 1.b. JSON NULL - Zero Value //// std::string zeroValueString{}; @@ -141,7 +145,7 @@ namespace TerminalAppUnitTests //// 2. Optional //// //// 2.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValue>(object), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue>(object), DeserializationError, _ReturnTrueForException); //// 2.b. JSON NULL - nullopt //// VERIFY_ARE_EQUAL(std::nullopt, GetValue>(Json::Value::nullSingleton())); @@ -160,7 +164,7 @@ namespace TerminalAppUnitTests std::string output{ "sentinel" }; // explicitly not the zero value //// 1.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValue(object, outputRedHerring), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(object, outputRedHerring), DeserializationError, _ReturnTrueForException); VERIFY_ARE_EQUAL(5, outputRedHerring); // unchanged //// 1.b. JSON NULL - Unchanged //// @@ -176,7 +180,7 @@ namespace TerminalAppUnitTests std::optional optionalOutput{ "sentinel2" }; // explicitly not nullopt //// 2.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValue(object, optionalOutputRedHerring), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(object, optionalOutputRedHerring), DeserializationError, _ReturnTrueForException); VERIFY_ARE_EQUAL(6, optionalOutputRedHerring); // unchanged //// 2.b. JSON NULL - nullopt //// @@ -202,7 +206,7 @@ namespace TerminalAppUnitTests //// 1. Bare Value //// //// 1.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key), KeyedException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key), DeserializationError, _ReturnTrueForException); //// 1.b. JSON NULL - Zero Value //// std::string zeroValueString{}; @@ -216,7 +220,7 @@ namespace TerminalAppUnitTests //// 2. Optional //// //// 2.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValueForKey>(object, key), KeyedException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValueForKey>(object, key), DeserializationError, _ReturnTrueForException); //// 2.b. JSON NULL - nullopt //// VERIFY_ARE_EQUAL(std::nullopt, GetValueForKey>(object, nullKey)); @@ -245,7 +249,7 @@ namespace TerminalAppUnitTests std::string output{ "sentinel" }; // explicitly not the zero value //// 1.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key, outputRedHerring), KeyedException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key, outputRedHerring), DeserializationError, _ReturnTrueForException); VERIFY_ARE_EQUAL(5, outputRedHerring); // unchanged //// 1.b. JSON NULL - Unchanged //// @@ -267,7 +271,7 @@ namespace TerminalAppUnitTests std::optional optionalOutput{ "sentinel2" }; // explicitly not nullopt //// 2.a. Type Invalid - Exception //// - VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key, optionalOutputRedHerring), KeyedException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key, optionalOutputRedHerring), DeserializationError, _ReturnTrueForException); VERIFY_ARE_EQUAL(6, optionalOutputRedHerring); // unchanged //// 2.b. JSON NULL - nullopt //// @@ -321,12 +325,12 @@ namespace TerminalAppUnitTests TryBasicType(testGuid, testGuidString); - VERIFY_THROWS_SPECIFIC(GetValue({ "NOT_A_GUID" }), TypeMismatchException, _ReturnTrueForException); - VERIFY_THROWS_SPECIFIC(GetValue({ "{too short for a guid but just a bit}" }), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue({ "NOT_A_GUID" }), DeserializationError, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue({ "{too short for a guid but just a bit}" }), DeserializationError, _ReturnTrueForException); VERIFY_THROWS_SPECIFIC(GetValue({ "{proper length string not a guid tho?}" }), std::exception, _ReturnTrueForException); - VERIFY_THROWS_SPECIFIC(GetValue({ "#" }), TypeMismatchException, _ReturnTrueForException); - VERIFY_THROWS_SPECIFIC(GetValue({ "#1234567890" }), TypeMismatchException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue({ "#" }), DeserializationError, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue({ "#1234567890" }), DeserializationError, _ReturnTrueForException); } void JsonUtilsTests::BasicTypeWithCustomConverter() @@ -366,7 +370,7 @@ namespace TerminalAppUnitTests // Unknown value should produce something? Json::Value stringUnknown{ "unknown" }; - VERIFY_THROWS_SPECIFIC(GetValue(stringUnknown), UnexpectedValueException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(stringUnknown), DeserializationError, _ReturnTrueForException); } void JsonUtilsTests::FlagMapper() @@ -401,17 +405,17 @@ namespace TerminalAppUnitTests Json::Value arrayNoneFirst{ Json::arrayValue }; arrayNoneFirst.append({ "none" }); arrayNoneFirst.append({ "first" }); - VERIFY_THROWS_SPECIFIC(GetValue(arrayNoneFirst), UnexpectedValueException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(arrayNoneFirst), DeserializationError, _ReturnTrueForException); // Stacking Any + None (Exception; same as above, different order) Json::Value arrayFirstNone{ Json::arrayValue }; arrayFirstNone.append({ "first" }); arrayFirstNone.append({ "none" }); - VERIFY_THROWS_SPECIFIC(GetValue(arrayFirstNone), UnexpectedValueException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(arrayFirstNone), DeserializationError, _ReturnTrueForException); // Unknown flag value? Json::Value stringUnknown{ "unknown" }; - VERIFY_THROWS_SPECIFIC(GetValue(stringUnknown), UnexpectedValueException, _ReturnTrueForException); + VERIFY_THROWS_SPECIFIC(GetValue(stringUnknown), DeserializationError, _ReturnTrueForException); } void JsonUtilsTests::NestedExceptionDuringKeyParse() @@ -420,20 +424,10 @@ namespace TerminalAppUnitTests Json::Value object{ Json::objectValue }; object[key] = Json::Value{ "string" }; - auto CheckKeyedException = [](const KeyedException& k) { - try - { - k.RethrowInner(); - } - catch (TypeMismatchException&) - { - // This Keyed exception contained the correct inner. - return true; - } - // This Keyed exception did not contain the correct inner. - return false; + auto CheckKeyInException = [](const DeserializationError& k) { + return k.key.has_value(); }; - VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key), KeyedException, CheckKeyedException); + VERIFY_THROWS_SPECIFIC(GetValueForKey(object, key), DeserializationError, CheckKeyInException); } } diff --git a/src/cppwinrt.build.pre.props b/src/cppwinrt.build.pre.props index cd89880fdd6..835797a2d6b 100644 --- a/src/cppwinrt.build.pre.props +++ b/src/cppwinrt.build.pre.props @@ -22,12 +22,20 @@ at the winmd we build to generate type info. In general, cppwinrt projects all want this. --> - + true true true Windows Store + + + false + - \ No newline at end of file + diff --git a/src/types/Environment.cpp b/src/types/Environment.cpp index 106503d8404..010f6c8f8f8 100644 --- a/src/types/Environment.cpp +++ b/src/types/Environment.cpp @@ -9,30 +9,42 @@ using namespace ::Microsoft::Console::Utils; // We cannot use spand or not_null because we're dealing with \0\0-terminated buffers of unknown length #pragma warning(disable : 26481 26429) +// Function Description: +// - Wraps win32's CreateEnvironmentBlock to return a smart pointer. +EnvironmentBlockPtr Microsoft::Console::Utils::CreateEnvironmentBlock() +{ + void* newEnvironmentBlock{ nullptr }; + if (!::CreateEnvironmentBlock(&newEnvironmentBlock, GetCurrentProcessToken(), FALSE)) + { + return nullptr; + } + return EnvironmentBlockPtr{ newEnvironmentBlock }; +} + // Function Description: // - Updates an EnvironmentVariableMapW with the current process's unicode // environment variables ignoring ones already set in the provided map. // Arguments: // - map: The map to populate with the current processes's environment variables. +// - environmentBlock: Optional environment block to use when filling map. If omitted, +// defaults to the current environment. // Return Value: // - S_OK if we succeeded, or an appropriate HRESULT for failing -HRESULT Microsoft::Console::Utils::UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept +HRESULT Microsoft::Console::Utils::UpdateEnvironmentMapW(EnvironmentVariableMapW& map, void* environmentBlock) noexcept try { - LPWCH currentEnvVars{}; - auto freeCurrentEnv = wil::scope_exit([&] { - if (currentEnvVars) - { - FreeEnvironmentStringsW(currentEnvVars); - currentEnvVars = nullptr; - } - }); + wchar_t const* activeEnvironmentBlock{ static_cast(environmentBlock) }; - currentEnvVars = ::GetEnvironmentStringsW(); - RETURN_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars); + wil::unique_environstrings_ptr currentEnvVars; + if (!activeEnvironmentBlock) + { + currentEnvVars.reset(::GetEnvironmentStringsW()); + RETURN_HR_IF_NULL(E_OUTOFMEMORY, currentEnvVars); + activeEnvironmentBlock = currentEnvVars.get(); + } // Each entry is NULL-terminated; block is guaranteed to be double-NULL terminated at a minimum. - for (wchar_t const* lastCh{ currentEnvVars }; *lastCh != '\0'; ++lastCh) + for (wchar_t const* lastCh{ activeEnvironmentBlock }; *lastCh != '\0'; ++lastCh) { // Copy current entry into temporary map. const size_t cchEntry{ ::wcslen(lastCh) }; diff --git a/src/types/inc/Environment.hpp b/src/types/inc/Environment.hpp index 0994a177762..a982db60432 100644 --- a/src/types/inc/Environment.hpp +++ b/src/types/inc/Environment.hpp @@ -21,9 +21,12 @@ namespace Microsoft::Console::Utils } }; + using EnvironmentBlockPtr = wil::unique_any; + [[nodiscard]] EnvironmentBlockPtr CreateEnvironmentBlock(); + using EnvironmentVariableMapW = std::map; - [[nodiscard]] HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map) noexcept; + [[nodiscard]] HRESULT UpdateEnvironmentMapW(EnvironmentVariableMapW& map, void* environmentBlock = nullptr) noexcept; [[nodiscard]] HRESULT EnvironmentMapToEnvironmentStringsW(EnvironmentVariableMapW& map, std::vector& newEnvVars) noexcept; diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 5e22a25127e..05ba610e9d5 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -96,32 +96,4 @@ namespace Microsoft::Console::Utils GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span name); - // Method Description: - // - Base case provided to handle the last argument to CoalesceOptionals() - template - T CoalesceOptionals(const T& base) - { - return base; - } - - // Method Description: - // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt) - template - T CoalesceOptionals(const std::optional& base) - { - static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty"); - return T{}; - } - - // Method Description: - // - Returns the value from the first populated optional, or a base value if none were populated. - template - T CoalesceOptionals(const std::optional& t1, Ts&&... t2) - { - // Initially, I wanted to check "has_value" and short-circuit out so that we didn't - // evaluate value_or for every single optional, but has_value/value emits exception handling - // code that value_or doesn't. Less exception handling is cheaper than calling value_or a - // few more times. - return t1.value_or(CoalesceOptionals(std::forward(t2)...)); - } } diff --git a/src/types/precomp.h b/src/types/precomp.h index b8b77ae4d6c..193b79decf3 100644 --- a/src/types/precomp.h +++ b/src/types/precomp.h @@ -29,6 +29,7 @@ Module Name: // Windows Header Files: #include +#include #include #include #include diff --git a/tools/Get-OSSConhostLog.ps1 b/tools/Get-OSSConhostLog.ps1 new file mode 100644 index 00000000000..4c206effa8e --- /dev/null +++ b/tools/Get-OSSConhostLog.ps1 @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. + +################################ +# This script takes a range of commits and generates +# a commit log with the git2git-excluded file changes +# filtered out. +# +# It also replaces GitHub issue numbers with GH-XXX so +# as to not confuse Git2Git or Azure DevOps. +# Community contributions are tagged with CC- so they +# can be detected later. + +[CmdletBinding()] +Param( + [string]$RevisionRange +) + +Function Test-MicrosoftPerson($email) { + Return $email -like "*@microsoft.com" +} + +# Replaces github PR numbers with GH-XXX or CC-XXX (community contribution) +# and issue numbers with GH-XXX +Function Mangle-CommitMessage($object) { + $Prefix = "GH-" + If (-Not (Test-MicrosoftPerson $object.Email)) { + $Prefix = "CC-" + } + + $s = $object.Subject -Replace "\(#(\d+)\)", "(${Prefix}`$1)" + $s = $s -Replace "#(\d+)","GH-`$1" + $s +} + +Function Get-Git2GitIgnoresAsExcludes() { + $filters = (Get-Content (Join-Path (& git rev-parse --show-toplevel) consolegit2gitfilters.json) | ConvertFrom-Json) + $excludes = $filters.ContainsFilters | ? { $_ -Ne "/." } | % { $_ -Replace "^/","" } + $excludes += $filters.SuffixFilters | % { "**/*$_"; "*$_" } + $excludes += $filters.PrefixFilters | % { "**/$_*"; "$_*" } + $excludes | % { ":(top,exclude)$_" } +} + +$Excludes = Get-Git2GitIgnoresAsExcludes +Write-Verbose "IGNORING: $Excludes" +$Entries = & git log $RevisionRange "--pretty=format:%an%x1C%ae%x1C%s" -- $Excludes | + ConvertFrom-CSV -Delimiter "`u{001C}" -Header Author,Email,Subject + +Write-Verbose ("{0} unfiltered log entries" -f $Entries.Count) + +$Grouped = $Entries | Group Email +$Grouped | % { + $e = $_.Group[0].Email + $p = $_.Group[0].Author + "$p ($($_.Group.Count))" + $_.Group | % { + If ($_.Subject -Imatch "^Merge") { + # Skip merge commits + Return + } + $cm = Mangle-CommitMessage $_ + "* $cm" + } + "" +}