mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-10 00:01:29 +00:00
Compare commits
133 Commits
dev/migrie
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b47e924565 | ||
|
|
34ecd1757f | ||
|
|
93004173ec | ||
|
|
458a140ad7 | ||
|
|
cd68bd0dfd | ||
|
|
c8be59f4a9 | ||
|
|
0026b67dd6 | ||
|
|
00f4382d63 | ||
|
|
57f727c01f | ||
|
|
daa8c44505 | ||
|
|
763dab5d7f | ||
|
|
86fa001dd6 | ||
|
|
507a84664e | ||
|
|
35a9314aa5 | ||
|
|
68887fc94e | ||
|
|
83d35c1191 | ||
|
|
b54429823e | ||
|
|
d7fcc56b90 | ||
|
|
b6927b24e8 | ||
|
|
d33dd1ffce | ||
|
|
da5ec7b2a2 | ||
|
|
ba534aaad6 | ||
|
|
7541b71de8 | ||
|
|
ac9d3dcfbe | ||
|
|
f7b205fa78 | ||
|
|
ff126c5a2f | ||
|
|
91f68e2914 | ||
|
|
245a244f1b | ||
|
|
fc57c82f80 | ||
|
|
c24194a3f3 | ||
|
|
3ab1d724d2 | ||
|
|
e13d1a7b61 | ||
|
|
48543bb3e7 | ||
|
|
91b867102c | ||
|
|
4014100b2d | ||
|
|
3778924aa4 | ||
|
|
c01573b5d3 | ||
|
|
62a12eeb79 | ||
|
|
e457a1a868 | ||
|
|
1c0bbe72d3 | ||
|
|
cf694747a0 | ||
|
|
9444617bc6 | ||
|
|
b80f6de58c | ||
|
|
34bb1cbd01 | ||
|
|
b656fdd34f | ||
|
|
41e9c59879 | ||
|
|
680c7deb2f | ||
|
|
e9548969e4 | ||
|
|
7fe98a0344 | ||
|
|
5b1137ccff | ||
|
|
96e0232603 | ||
|
|
ae8347f336 | ||
|
|
3f1262e4c2 | ||
|
|
b50df20cfe | ||
|
|
7b6958405e | ||
|
|
65269c8311 | ||
|
|
3e5d6e71a2 | ||
|
|
dff8f15efa | ||
|
|
2b27d4ce91 | ||
|
|
172d9a7f64 | ||
|
|
02fd7a0c15 | ||
|
|
acf36d0e4f | ||
|
|
d45cc4c2e1 | ||
|
|
9700598ecb | ||
|
|
124cbd9e47 | ||
|
|
b6f5f2a323 | ||
|
|
9c4950cb32 | ||
|
|
3b53c6956c | ||
|
|
98d0613124 | ||
|
|
e7592ec3d4 | ||
|
|
b7a7aa0bc3 | ||
|
|
0fa286c011 | ||
|
|
12b12d5b07 | ||
|
|
f196285824 | ||
|
|
9fed14a95e | ||
|
|
c33a97955f | ||
|
|
a7d7362b95 | ||
|
|
9293867a06 | ||
|
|
2919d96c21 | ||
|
|
6c4878c8d5 | ||
|
|
90e7c28069 | ||
|
|
de49cf1d0d | ||
|
|
2b4b8dd1bd | ||
|
|
fa0cd8c7ed | ||
|
|
3c044f20cf | ||
|
|
9aea904229 | ||
|
|
f7b5ff322a | ||
|
|
9905bd5f09 | ||
|
|
e0f585251a | ||
|
|
9b1bb134bf | ||
|
|
e851c61777 | ||
|
|
f8ccf64252 | ||
|
|
5f590a5efa | ||
|
|
bf783842f2 | ||
|
|
20bfccefb7 | ||
|
|
9b636edad2 | ||
|
|
cb2cd7e219 | ||
|
|
aaf2395266 | ||
|
|
20fc57ee0f | ||
|
|
058cbd11e7 | ||
|
|
7235996b4d | ||
|
|
bc70a97fd7 | ||
|
|
e557a867ee | ||
|
|
49d008537f | ||
|
|
8bef5eefd5 | ||
|
|
039c80d443 | ||
|
|
c4c3c3116b | ||
|
|
7d503a4352 | ||
|
|
cceb0eaa68 | ||
|
|
713027b5e3 | ||
|
|
fcca88ab25 | ||
|
|
6b2ae625a5 | ||
|
|
a8b4044630 | ||
|
|
f087d03eb2 | ||
|
|
990e06b445 | ||
|
|
08646e5ca3 | ||
|
|
68e0af41a8 | ||
|
|
9b07cb8c71 | ||
|
|
5220738d8e | ||
|
|
0b0161d537 | ||
|
|
683f4e28d3 | ||
|
|
fc7b052461 | ||
|
|
8276b549e8 | ||
|
|
01a04906f3 | ||
|
|
96b9ba99b2 | ||
|
|
d37df8ab05 | ||
|
|
4f46129cb4 | ||
|
|
2485a638cb | ||
|
|
33470ad08e | ||
|
|
c5366cea75 | ||
|
|
477c04ab94 | ||
|
|
b8e6b8e27c | ||
|
|
539a5dc0af |
20
NuGet.Config
20
NuGet.Config
@@ -1,24 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<!-- Add repositories here to the list of available repositories -->
|
||||
|
||||
<!-- Dependencies that we must carry because they're not on public nuget feeds right now. -->
|
||||
<clear />
|
||||
<!-- Dependencies that we can turn on to force override for testing purposes before uploading. -->
|
||||
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
|
||||
|
||||
<!-- Use our own NuGet Feed -->
|
||||
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
|
||||
|
||||
<!-- Temporarily? use the feeds from our friends in MUX for Helix test stuff -->
|
||||
<add key="dotnetfeed" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
|
||||
<add key="dnceng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
|
||||
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/ms/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
|
||||
|
||||
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
|
||||
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
|
||||
<add key="OpenConsole - Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/OpenConsole/nuget/v3/index.json" />-->
|
||||
</packageSources>
|
||||
<disabledPackageSources>
|
||||
<clear />
|
||||
</disabledPackageSources>
|
||||
<config>
|
||||
<add key="repositorypath" value=".\packages" />
|
||||
</config>
|
||||
|
||||
343
OpenConsole.sln
343
OpenConsole.sln
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,8 @@
|
||||
"/res/terminal/",
|
||||
"/doc/specs/",
|
||||
"/doc/cascadia/",
|
||||
"/doc/user-docs/"
|
||||
"/doc/user-docs/",
|
||||
"/src/tools/MonarchPeasantSample/",
|
||||
],
|
||||
"SuffixFilters": [
|
||||
".dbb",
|
||||
@@ -38,5 +39,5 @@
|
||||
".rec",
|
||||
".err",
|
||||
".xlsx"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- Yes, the large majority of the `DEFINE_PROPERTYKEY` defs are the same, it's only the last byte of the guid that changes
|
||||
|
||||
2. Add matching fields to Settings.hpp
|
||||
- add getters, setters, the whole drill.
|
||||
- Add getters, setters, the whole drill.
|
||||
|
||||
3. Add to the propsheet
|
||||
- We need to add it to *reading and writing* the registry from the propsheet, and *reading* the link from the propsheet. Yes, that's weird, but the propsheet is smart enough to re-use ShortcutSerialization::s_SetLinkValues, but not smart enough to do the same with RegistrySerialization.
|
||||
|
||||
14
doc/TAEF.md
14
doc/TAEF.md
@@ -48,3 +48,17 @@ Invoke-OpenConsoleTests
|
||||
```
|
||||
|
||||
`Invoke-OpenConsoleTests` supports a number of options, which you can enumerate by running `Invoke-OpenConsoleTests -?`.
|
||||
|
||||
|
||||
### Debugging Tests
|
||||
|
||||
If you want to debug a test, you can do so by using the TAEF /waitForDebugger flag, such as:
|
||||
|
||||
runut *Tests.dll /name:TextBufferTests::TestInsertCharacter /waitForDebugger
|
||||
|
||||
Replace the test name with the one you want to debug. Then, TAEF will begin executing the test and output something like this:
|
||||
|
||||
TAEF: Waiting for debugger - PID <some PID> @ IP <some IP address>
|
||||
|
||||
You can then attach to that PID in your debugger of choice. In Visual Studio, you can use Debug -> Attach To Process, or you could use WinDbg or whatever you want.
|
||||
Once the debugger attaches, the test will execute and your breakpoints will be hit.
|
||||
|
||||
395
doc/cascadia/AddASetting.md
Normal file
395
doc/cascadia/AddASetting.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# Adding Settings to Windows Terminal
|
||||
|
||||
Adding a setting to Windows Terminal is fairly straightforward. This guide serves as a reference on how to add a setting.
|
||||
|
||||
## 1. Terminal Settings Model
|
||||
|
||||
The Terminal Settings Model (`Microsoft.Terminal.Settings.Model`) is responsible for (de)serializing and exposing settings.
|
||||
|
||||
### `GETSET_SETTING` macro
|
||||
|
||||
The `GETSET_SETTING` macro can be used to implement inheritance for your new setting and store the setting in the settings model. It takes three parameters:
|
||||
- `type`: the type that the setting will be stored as
|
||||
- `name`: the name of the variable for storage
|
||||
- `defaultValue`: the value to use if the user does not define the setting anywhere
|
||||
|
||||
### Adding a Profile setting
|
||||
|
||||
This tutorial will add `CloseOnExitMode CloseOnExit` as a profile setting.
|
||||
|
||||
1. In `Profile.h`, declare/define the setting:
|
||||
|
||||
```c++
|
||||
GETSET_SETTING(CloseOnExitMode, CloseOnExit, CloseOnExitMode::Graceful)
|
||||
```
|
||||
|
||||
2. In `Profile.idl`, expose the setting via WinRT:
|
||||
|
||||
```c++
|
||||
Boolean HasCloseOnExit();
|
||||
void ClearCloseOnExit();
|
||||
CloseOnExitMode CloseOnExit;
|
||||
```
|
||||
|
||||
3. In `Profile.cpp`, add (de)serialization and copy logic:
|
||||
|
||||
```c++
|
||||
// Top of file:
|
||||
// - Add the serialization key
|
||||
static constexpr std::string_view CloseOnExitKey{ "closeOnExit" };
|
||||
|
||||
// CopySettings() or Copy():
|
||||
// - The setting is exposed in the Settings UI
|
||||
profile->_CloseOnExit = source->_CloseOnExit;
|
||||
|
||||
// LayerJson():
|
||||
// - get the value from the JSON
|
||||
JsonUtils::GetValueForKey(json, CloseOnExitKey, _CloseOnExit);
|
||||
|
||||
// ToJson():
|
||||
// - write the value to the JSON
|
||||
JsonUtils::SetValueForKey(json, CloseOnExitKey, _CloseOnExit);
|
||||
```
|
||||
|
||||
- If the setting is not a primitive type, in `TerminalSettingsSerializationHelpers.h` add (de)serialization logic for the accepted values:
|
||||
|
||||
```c++
|
||||
// For enum values...
|
||||
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode)
|
||||
{
|
||||
JSON_MAPPINGS(3) = {
|
||||
pair_type{ "always", ValueType::Always },
|
||||
pair_type{ "graceful", ValueType::Graceful },
|
||||
pair_type{ "never", ValueType::Never },
|
||||
};
|
||||
};
|
||||
|
||||
// For enum flag values...
|
||||
JSON_FLAG_MAPPER(::winrt::Microsoft::Terminal::TerminalControl::CopyFormat)
|
||||
{
|
||||
JSON_MAPPINGS(5) = {
|
||||
pair_type{ "none", AllClear },
|
||||
pair_type{ "html", ValueType::HTML },
|
||||
pair_type{ "rtf", ValueType::RTF },
|
||||
pair_type{ "all", AllSet },
|
||||
};
|
||||
};
|
||||
|
||||
// NOTE: This is also where you can add functionality for...
|
||||
// - overloaded type support (i.e. accept a bool and an enum)
|
||||
// - custom (de)serialization logic (i.e. coordinates)
|
||||
```
|
||||
|
||||
### Adding a Global setting
|
||||
|
||||
Follow the "adding a Profile setting" instructions above, but do it on the `GlobalAppSettings` files.
|
||||
|
||||
### Adding an Action
|
||||
|
||||
This tutorial will add the `openSettings` action.
|
||||
|
||||
1. In `KeyMapping.idl`, declare the action:
|
||||
|
||||
```c++
|
||||
// Add the action to ShortcutAction
|
||||
enum ShortcutAction
|
||||
{
|
||||
OpenSettings
|
||||
}
|
||||
```
|
||||
|
||||
2. In `ActionAndArgs.cpp`, add serialization logic:
|
||||
|
||||
```c++
|
||||
// Top of file:
|
||||
// - Add the serialization key
|
||||
static constexpr std::string_view OpenSettingsKey{ "openSettings" };
|
||||
|
||||
// ActionKeyNamesMap:
|
||||
// - map the new enum to the json key
|
||||
{ OpenSettingsKey, ShortcutAction::OpenSettings },
|
||||
```
|
||||
|
||||
3. If the action should automatically generate a name when it appears in the Command Palette...
|
||||
```c++
|
||||
// In ActionAndArgs.cpp GenerateName() --> GeneratedActionNames
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
|
||||
// In Resources.resw for Microsoft.Terminal.Settings.Model.Lib,
|
||||
// add the generated name
|
||||
// NOTE: Visual Studio presents the resw file as a table.
|
||||
// If you choose to edit the file with a text editor,
|
||||
// the code should look something like this...
|
||||
<data name="OpenSettingsCommandKey" xml:space="preserve">
|
||||
<value>Open settings file</value>
|
||||
</data>
|
||||
```
|
||||
|
||||
4. If the action supports arguments...
|
||||
- In `ActionArgs.idl`, declare the arguments
|
||||
```c++
|
||||
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
|
||||
{
|
||||
// this declares the "target" arg
|
||||
SettingsTarget Target { get; };
|
||||
};
|
||||
```
|
||||
- In `ActionArgs.h`, define the new runtime class
|
||||
```c++
|
||||
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs>
|
||||
{
|
||||
OpenSettingsArgs() = default;
|
||||
|
||||
// adds a getter/setter for your argument, and defines the json key
|
||||
GETSET_PROPERTY(SettingsTarget, Target, SettingsTarget::SettingsFile);
|
||||
static constexpr std::string_view TargetKey{ "target" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<OpenSettingsArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_Target == _Target;
|
||||
}
|
||||
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<OpenSettingsArgs>();
|
||||
JsonUtils::GetValueForKey(json, TargetKey, args->_Target);
|
||||
return { *args, {} };
|
||||
}
|
||||
|
||||
IActionArgs Copy() const
|
||||
{
|
||||
auto copy{ winrt::make_self<OpenSettingsArgs>() };
|
||||
copy->_Target = _Target;
|
||||
return *copy;
|
||||
}
|
||||
};
|
||||
```
|
||||
- In `ActionArgs.cpp`, define `GenerateName()`. This is used to automatically generate a name when it appears in the Command Palette.
|
||||
- In `ActionAndArgs.cpp`, add serialization logic:
|
||||
|
||||
```c++
|
||||
// ActionKeyNamesMap --> argParsers
|
||||
{ ShortcutAction::OpenSettings, OpenSettingsArgs::FromJson },
|
||||
```
|
||||
|
||||
### Adding an Action Argument
|
||||
|
||||
Follow step 3 from the "adding an Action" instructions above, but modify the relevant `ActionArgs` files.
|
||||
|
||||
## 2. Setting Functionality
|
||||
|
||||
Now that the Terminal Settings Model is updated, Windows Terminal can read and write to the settings file. This section covers how to add functionality to your newly created setting.
|
||||
|
||||
### App-level settings
|
||||
|
||||
App-level settings are settings that affect the frame of Windows Terminal. Generally, these tend to be global settings. The `TerminalApp` project is responsible for presenting the frame of Windows Terminal. A few files of interest include:
|
||||
- `TerminalPage`: XAML control responsible for the look and feel of Windows Terminal
|
||||
- `AppLogic`: WinRT class responsible for window-related issues (i.e. the titlebar, focus mode, etc...)
|
||||
|
||||
Both have access to a `CascadiaSettings` object, for you to read the loaded setting and update Windows Terminal appropriately.
|
||||
|
||||
### Terminal-level settings
|
||||
|
||||
Terminal-level settings are settings that affect a shell session. Generally, these tend to be profile settings. The `TerminalApp` project is responsible for packaging this settings from the Terminal Settings Model to the terminal instance. There are two kinds of settings here:
|
||||
- `IControlSettings`:
|
||||
- These are settings that affect the `TerminalControl` (a XAML control that hosts a shell session).
|
||||
- Examples include background image customization, interactivity behavior (i.e. selection), acrylic and font customization.
|
||||
- The `TerminalControl` project has access to these settings via a saved `IControlSettings` member.
|
||||
- `ICoreSettings`:
|
||||
- These are settings that affect the `TerminalCore` (a lower level object that interacts with the text buffer).
|
||||
- Examples include initial size, history size, and cursor customization.
|
||||
- The `TerminalCore` project has access to these settings via a saved `ICoreSettings` member.
|
||||
|
||||
`TerminalApp` packages these settings into a `TerminalSettings : IControlSettings, ICoreSettings` object upon creating a new terminal instance. To do so, you must submit the following changes:
|
||||
- Declare the setting in `IControlSettings.idl` or `ICoreSettings.idl` (whichever is relevant to your setting). If your setting is an enum setting, declare the enum here instead of in the `TerminalSettingsModel` project.
|
||||
- In `TerminalSettings.h`, declare/define the setting...
|
||||
```c++
|
||||
// The GETSET_PROPERTY macro declares/defines a getter setter for the setting.
|
||||
// Like GETSET_SETTING, it takes in a type, name, and defaultValue.
|
||||
GETSET_PROPERTY(bool, UseAcrylic, false);
|
||||
```
|
||||
- In `TerminalSettings.cpp`...
|
||||
- update `_ApplyProfileSettings` for profile settings
|
||||
- update `_ApplyGlobalSettings` for global settings
|
||||
- If additional processing is necessary, that would happen here. For example, `backgroundImageAlignment` is stored as a `ConvergedAlignment` in the Terminal Settings Model, but converted into XAML's separate horizontal and vertical alignment enums for packaging.
|
||||
|
||||
### Actions
|
||||
|
||||
Actions are packaged as an `ActionAndArgs` object, then handled in `TerminalApp`. To add functionality for actions...
|
||||
- In the `ShortcutActionDispatch` files, dispatch an event when the action occurs...
|
||||
```c++
|
||||
// ShortcutActionDispatch.idl
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, Microsoft.Terminal.Settings.Model.ActionEventArgs> OpenSettings;
|
||||
|
||||
// ShortcutActionDispatch.h
|
||||
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs);
|
||||
|
||||
// ShortcutActionDispatch.cpp --> DoAction()
|
||||
// - dispatch the appropriate event
|
||||
case ShortcutAction::OpenSettings:
|
||||
{
|
||||
_OpenSettingsHandlers(*this, eventArgs);
|
||||
break;
|
||||
}
|
||||
```
|
||||
- In `TerminalPage` files, handle the event...
|
||||
```c++
|
||||
// TerminalPage.h
|
||||
// - declare the handler
|
||||
void _HandleOpenSettings(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
|
||||
// TerminalPage.cpp --> _RegisterActionCallbacks()
|
||||
// - register the handler
|
||||
_actionDispatch->OpenSettings({ this, &TerminalPage::_HandleOpenSettings });
|
||||
|
||||
// AppActionHandlers.cpp
|
||||
// - direct the function to the right place and call a helper function
|
||||
void TerminalPage::_HandleOpenSettings(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
// NOTE: this if-statement can be omitted if the action does not support arguments
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<OpenSettingsArgs>())
|
||||
{
|
||||
_LaunchSettings(realArgs.Target());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AppActionHandlers` vary based on the action you want to perform. A few useful helper functions include:
|
||||
- `_GetFocusedTab()`: retrieves the focused tab
|
||||
- `_GetActiveControl()`: retrieves the active terminal control
|
||||
- `_GetTerminalTabImpl()`: tries to cast the given tab as a `TerminalTab` (a tab that hosts a terminal instance)
|
||||
|
||||
|
||||
## 3. Settings UI
|
||||
|
||||
### Exposing Enum Settings
|
||||
If the new setting supports enums, you need to expose a map of the enum and the respective value in the Terminal Settings Model's `EnumMappings`:
|
||||
|
||||
```c++
|
||||
// EnumMappings.idl
|
||||
static Windows.Foundation.Collections.IMap<String, Microsoft.Terminal.Settings.Model.CloseOnExitMode> CloseOnExitMode { get; };
|
||||
|
||||
// EnumMappings.h
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, CloseOnExitMode> CloseOnExitMode();
|
||||
|
||||
// EnumMappings.cpp
|
||||
// - this macro leverages the json enum mapper in TerminalSettingsSerializationHelper to expose
|
||||
// the mapped values across project boundaries
|
||||
DEFINE_ENUM_MAP(Model::CloseOnExitMode, CloseOnExitMode);
|
||||
```
|
||||
|
||||
### Binding and Localizing the Enum Setting
|
||||
|
||||
Find the page in the Settings UI that the new setting fits best in. In this example, we are adding `LaunchMode`.
|
||||
1. In `Launch.idl`, expose the bindable setting...
|
||||
```c++
|
||||
// Expose the current value for the setting
|
||||
IInspectable CurrentLaunchMode;
|
||||
|
||||
// Expose the list of possible values
|
||||
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> LaunchModeList { get; };
|
||||
```
|
||||
|
||||
2. In `Launch.h`, declare the bindable enum setting...
|
||||
```c++
|
||||
// the GETSET_BINDABLE_ENUM_SETTING macro accepts...
|
||||
// - name: the name of the setting
|
||||
// - enumType: the type of the setting
|
||||
// - settingsModelName: how to retrieve the setting (use State() to get access to the settings model)
|
||||
// - settingNameInModel: the name of the setting in the terminal settings model
|
||||
GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode);
|
||||
```
|
||||
|
||||
3. In `Launch.cpp`, populate these functions...
|
||||
```c++
|
||||
// Constructor (after InitializeComponent())
|
||||
// the INITIALIZE_BINDABLE_ENUM_SETTING macro accepts...
|
||||
// - name: the name of the setting
|
||||
// - enumMappingsName: the name from the TerminalSettingsModel's EnumMappings
|
||||
// - enumType: the type for the enum
|
||||
// - resourceSectionAndType: prefix for the localization
|
||||
// - resourceProperty: postfix for the localization
|
||||
INITIALIZE_BINDABLE_ENUM_SETTING(LaunchMode, LaunchMode, LaunchMode, L"Globals_LaunchMode", L"Content");
|
||||
```
|
||||
|
||||
4. In `Resources.resw` for Microsoft.Terminal.Settings.Editor, add the localized text to expose each enum value. Use the following format: `<SettingGroup>_<SettingName><EnumValue>.Content`
|
||||
- `SettingGroup`:
|
||||
- `Globals` for global settings
|
||||
- `Profile` for profile settings
|
||||
- `SettingName`:
|
||||
- the Pascal-case format for the setting type (i.e. `LaunchMode` for `"launchMode"`)
|
||||
- `EnumValue`:
|
||||
- the json key for the setting value, but with the first letter capitalized (i.e. `Focus` for `"focus"`)
|
||||
- The resulting resw key should look something like this `Globals_LaunchModeFocus.Content`
|
||||
- This is the text that will be used in your control
|
||||
|
||||
### Updating the UI
|
||||
|
||||
#### Enum Settings
|
||||
|
||||
Now, create a XAML control in the relevant XAML file. Use the following tips and tricks to style everything appropriately:
|
||||
- Wrap the control in a `ContentPresenter` adhering to the `SettingContainerStyle` style
|
||||
- Bind `SelectedItem` to the relevant `Current<Setting>` (i.e. `CurrentLaunchMode`). Ensure it's a TwoWay binding
|
||||
- Bind `ItemsSource` to `<Setting>List` (i.e. `LaunchModeList`)
|
||||
- Set the ItemTemplate to the `Enum<ControlType>Template` (i.e. `EnumRadioButtonTemplate` for radio buttons)
|
||||
- Set the style to the appropriate one in `CommonResources.xaml`
|
||||
|
||||
```xml
|
||||
<!--Launch Mode-->
|
||||
<ContentPresenter Style="{StaticResource SettingContainerStyle}">
|
||||
<muxc:RadioButtons x:Uid="Globals_LaunchMode"
|
||||
SelectedItem="{x:Bind CurrentLaunchMode, Mode="TwoWay"}"
|
||||
ItemsSource="{x:Bind LaunchModeList}"
|
||||
ItemTemplate="{StaticResource EnumRadioButtonTemplate}"
|
||||
Style="{StaticResource RadioButtonsSettingStyle}"/>
|
||||
</ContentPresenter>
|
||||
```
|
||||
|
||||
To add any localized text, add a `x:Uid`, and access the relevant property via the Resources.resw file. For example, `Globals_LaunchMode.Header` sets the header for this control. You can also set the tooltip text like this:
|
||||
`Globals_DefaultProfile.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip`.
|
||||
|
||||
#### Non-Enum Settings
|
||||
|
||||
Continue to reference `CommonResources.xaml` for appropriate styling and wrap the control with a similar `ContentPresenter`. However, instead of binding to the `Current<Setting>` and `<Setting>List`, bind directly to the setting via the state. Binding a setting like `altGrAliasing` should look something like this:
|
||||
```xml
|
||||
<!--AltGr Aliasing-->
|
||||
<ContentPresenter Style="{StaticResource SettingContainerStyle}">
|
||||
<CheckBox x:Uid="Profile_AltGrAliasing"
|
||||
IsChecked="{x:Bind State.Profile.AltGrAliasing, Mode=TwoWay}"
|
||||
Style="{StaticResource CheckBoxSettingStyle}"/>
|
||||
</ContentPresenter>
|
||||
```
|
||||
|
||||
#### Profile Settings
|
||||
|
||||
If you are specifically adding a Profile setting, in addition to the steps above, you need to make the setting observable by modifying the `Profiles` files...
|
||||
```c++
|
||||
// Profiles.idl --> ProfileViewModel
|
||||
// - this declares the setting as observable using the type and the name of the setting
|
||||
OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit);
|
||||
|
||||
// Profiles.h --> ProfileViewModel
|
||||
// - this defines the setting as observable off of the _profile object
|
||||
OBSERVABLE_PROJECTED_SETTING(_profile, CloseOnExit);
|
||||
|
||||
// Profiles.h --> ProfileViewModel
|
||||
// - if the setting cannot be inherited by another profile (aka missing the Clear() function), use the following macro instead:
|
||||
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
|
||||
```
|
||||
|
||||
The `ProfilePageNavigationState` holds a `ProfileViewModel`, which wraps the `Profile` object from the Terminal Settings Model. The `ProfileViewModel` makes all of the profile settings observable.
|
||||
|
||||
### Actions
|
||||
|
||||
Actions are not yet supported in the Settings UI.
|
||||
@@ -93,6 +93,8 @@
|
||||
"scrollDownPage",
|
||||
"scrollUp",
|
||||
"scrollUpPage",
|
||||
"scrollToBottom",
|
||||
"scrollToTop",
|
||||
"sendInput",
|
||||
"setColorScheme",
|
||||
"setTabColor",
|
||||
@@ -369,6 +371,13 @@
|
||||
"splitMode": {
|
||||
"default": "duplicate",
|
||||
"description": "Control how the pane splits. Only accepts \"duplicate\" which will duplicate the focused pane's profile into a new pane."
|
||||
},
|
||||
"size": {
|
||||
"default": 0.5,
|
||||
"description": "Specify how large the new pane should be, as a fraction of the current pane's size. 1.0 would be 'all of the current pane', and 0.0 is 'None of the parent'. Accepts floating point values from 0-1 (default 0.5).",
|
||||
"maximum": 1,
|
||||
"minimum": 0,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,6 +658,10 @@
|
||||
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
|
||||
"type": "string"
|
||||
},
|
||||
"startupActions": {
|
||||
"description": "Sets the list of actions to apply if no command line is provided. Uses the same format as command line arguments",
|
||||
"type": "string"
|
||||
},
|
||||
"disabledProfileSources": {
|
||||
"description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.",
|
||||
"items": {
|
||||
@@ -922,9 +935,10 @@
|
||||
},
|
||||
"cursorShape": {
|
||||
"default": "bar",
|
||||
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
|
||||
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"doubleUnderscore\" ( ‗ )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
|
||||
"enum": [
|
||||
"bar",
|
||||
"doubleUnderscore",
|
||||
"emptyBox",
|
||||
"filledBox",
|
||||
"underscore",
|
||||
|
||||
302
doc/specs/#2871 - Pane Navigation/#2871 - Pane Navigation.md
Normal file
302
doc/specs/#2871 - Pane Navigation/#2871 - Pane Navigation.md
Normal file
@@ -0,0 +1,302 @@
|
||||
---
|
||||
author: Mike Griese @zadjii-msft
|
||||
created on: 2020-11-23
|
||||
last updated: 2020-12-15
|
||||
issue id: #2871
|
||||
---
|
||||
|
||||
# Focus Pane Actions
|
||||
|
||||
## Abstract
|
||||
|
||||
Currently, the Terminal only allows users to navigate through panes
|
||||
_directionally_. However, we might also want to allow a user to navigate through
|
||||
panes in most recently used order ("MRU" order), or to navigate directly to a
|
||||
specific pane. This spec proposes some additional actions in order to enable
|
||||
these sorts of scenarios.
|
||||
|
||||
## Background
|
||||
|
||||
### Inspiration
|
||||
|
||||
`tmux` allows the user to navigate through panes using its `select-pane`
|
||||
command. The `select-pane` command works in the following way:
|
||||
|
||||
```
|
||||
select-pane [-DLlMmRU] [-T title] [-t target-pane]
|
||||
|
||||
Make pane target-pane the active pane in window target-window, or set its
|
||||
style (with -P). If one of -D, -L, -R, or -U is used, respectively the
|
||||
pane below, to the left, to the right, or above the target pane is used.
|
||||
-l is the same as using the last-pane command.
|
||||
|
||||
-m and -M are used to set and clear the marked pane. There is one marked
|
||||
pane at a time, setting a new marked pane clears the last. The marked pane
|
||||
is the default target for -s to join-pane, swap-pane and swap-window.
|
||||
```
|
||||
_from `man tmux`_.
|
||||
|
||||
The Terminal currently allows the user to navigate through panes with the
|
||||
`moveFocus` action, which only accepts a `direction` to move in.
|
||||
|
||||
Additionally, the Terminal allows movement between tabs with the `nextTab` and
|
||||
`prevTab` actions, who move between tabs either in-order or in MRU order.
|
||||
Furthermore, these actions may or may not display the "tab switcher" user
|
||||
interface, based on the value of `tabSwitcherMode`.
|
||||
|
||||
### User Stories
|
||||
|
||||
* **Scenario 1**: A user who wants to be able to split the window into 4 equal
|
||||
corners from the commandline. Currently this isn't possible, because the user
|
||||
cannot move focus during the startup actions - `split-pane` actions always end
|
||||
up splitting the current leaf in the tree of panes. (see [#5464])
|
||||
* **Scenario 2**: A user who wants to quickly navigate to the previous pane they
|
||||
had opened. (see [#2871])
|
||||
* **Scenario 3**: A user who wants to bind a keybinding like <kbd>alt+1</kbd>,
|
||||
<kbd>alt+2</kbd>, etc to immediately focus the first, second, etc. pane in a
|
||||
tab. (see [#5803])
|
||||
|
||||
### Future Considerations
|
||||
|
||||
There's been talk of updating the advanced tab switcher to also display panes,
|
||||
in addition to just tabs. This would allow users to navigate through the ATS
|
||||
directly to a pane, and see all the panes in a tab. Currently, `tabSwitcherMode`
|
||||
changes the behavior of `nextTab`, `prevTab` - should we just build the
|
||||
`paneSwitcherMode` directly into the action we end up designing?
|
||||
|
||||
## Solution Design
|
||||
|
||||
Does using the pane switcher with a theoretical `focusPane(target=id)` action
|
||||
even make sense? Certainly not! That's like `switchToTab(index=id)`, the user
|
||||
already knows which tab they want to go to, there's no reason to pop an
|
||||
ephemeral UI in front of them.
|
||||
|
||||
Similarly, it almost certainly doesn't make sense to display the pane switcher
|
||||
while moving focus directionally. Consider moving focus with a key bound to the
|
||||
arrow keys. Displaying another UI in front of them while moving focus with the
|
||||
arrow keys would be confusing.
|
||||
|
||||
Addressing Scenario 1 is relatively easy. So long as we add any of the proposed
|
||||
actions, including the existing `moveFocus` action as a subcommand that can be
|
||||
passed to `wt.exe`, then the user should be able to navigate through the panes
|
||||
they've created with the startup commandline, and build the tree of panes
|
||||
however they see fit.
|
||||
|
||||
Scenario 2 is more complicated, because MRU switching is always more
|
||||
complicated. Without a UI of some sort, there's no way to switch to another pane
|
||||
in the MRU order without also updating the MRU order as you go. So this would
|
||||
almost certainly necessitate a "pane switcher", like the tab switcher.
|
||||
|
||||
|
||||
### Proposal A: Add next, prev to moveFocus
|
||||
|
||||
* `moveFocus(direction="up|down|left|right|next|prev")`
|
||||
|
||||
* **Pros**:
|
||||
- Definitely gets the "MRU Pane Switching" scenario working
|
||||
* **Cons**:
|
||||
- Doesn't really address any of the other scenarios
|
||||
- How will it play with pane switching in the UI?
|
||||
- MRU switching without a dialog to track & display the MRU stack doesn't
|
||||
really work - this only allows to the user to navigate to the most recently
|
||||
used pane, or through all the panes in least-recently-used order. This is
|
||||
because switching to the MRU pane _will update the MRU pane_.
|
||||
|
||||
❌ This proposal is no longer being considered.
|
||||
|
||||
### Proposal B: focusNextPane, focusPrevPane with order, useSwitcher args
|
||||
|
||||
```json
|
||||
// Focus pane 1
|
||||
// - This is sensible, no arguments here
|
||||
{ "command": { "action": "focusPane", "id": 1 } },
|
||||
|
||||
// Focus the next MRU pane
|
||||
// - Without the switcher, this can only go one pane deep in the MRU stack
|
||||
// - presumably once there's a pane switcher, it would default to enabled?
|
||||
{ "command": { "action": "focusNextPane", "order": "mru" } },
|
||||
|
||||
// Focus the prev inOrder pane
|
||||
// - this seems straightforward
|
||||
{ "command": { "action": "focusPrevPane", "order": "inOrder" } },
|
||||
|
||||
// Focus the next pane, in mru order, explicitly disable the switcher
|
||||
// - The user opted in to only being able to MRU switch one deep. That's fine, that's what they want.
|
||||
{ "command": { "action": "focusNextPane", "order": "mru", "useSwitcher": false} },
|
||||
|
||||
// Focus the prev inOrder pane, explicitly with the switcher
|
||||
// - Maybe they disabled the switcher globally, but what it on for this action?
|
||||
{ "command": { "action": "focusPrevPane", "order": "inOrder", "useSwitcher": true } },
|
||||
```
|
||||
_From [discussion in the implementation
|
||||
PR](https://github.com/microsoft/terminal/pull/8183#issuecomment-729672645)_
|
||||
|
||||
Boiled down, that's three actions:
|
||||
* `focusPane(target=id)`
|
||||
* `focusNextPane(order="inOrder|mru", useSwitcher=true|false)`
|
||||
* `focusPrevPane(order="inOrder|mru", useSwitcher=true|false)`
|
||||
|
||||
* **Pros**:
|
||||
- Everything is explicit, including the option to use the pane switcher (when
|
||||
available)
|
||||
- Adds support for in-order pane switching
|
||||
- No "conditional parameters" - where providing one argument makes other
|
||||
arguments invalid or ambiguous.
|
||||
* **Cons**:
|
||||
- Doesn't really address any of the other scenarios
|
||||
- What does the "next most-recently-used tab" even mean? How is it different
|
||||
than "previous most-recently-used tab"? Semantically, these are the same
|
||||
thing!
|
||||
- No one's even asked for in-order pane switching. Is that a UX that even
|
||||
really makes sense?
|
||||
|
||||
❌ This proposal is no longer being considered.
|
||||
|
||||
> 👉 **NOTE**: At this point, we stopped considering navigating in both MRU
|
||||
> "directions", since both the next and prev MRU pane are the same thing. We're
|
||||
> now using "last" to mean "the previous MRU pane".
|
||||
|
||||
### Proposal C: One actions, combine the args
|
||||
|
||||
* `moveFocus(target=id|"up|down|left|right|last")`
|
||||
|
||||
* **Pros**:
|
||||
- Absolutely the least complicated action to author. There's only one
|
||||
parameter, `target`.
|
||||
- No "conditional parameters".
|
||||
* **Cons**:
|
||||
- How do we express this in the Settings UI? Mixed-type enums work fine for
|
||||
the font weight, where each enum value has a distinct integer value it maps
|
||||
to, but in this case, using `id` is entirely different from the other
|
||||
directional values
|
||||
|
||||
❌ This proposal is no longer being considered.
|
||||
|
||||
### Proposal D: Two actions
|
||||
|
||||
* `focusPane(target=id)`
|
||||
* `moveFocus(direction="up|down|left|right|last")`
|
||||
|
||||
* **Pros**:
|
||||
- Each action does explicitly one thing.
|
||||
* **Cons**:
|
||||
- two actions for _similar_ behavior
|
||||
- This now forks the "Direction" enum into "MoveFocusDirection" and
|
||||
"ResizeDirection" (because `resizePane(last)` doesn't make any sense).
|
||||
|
||||
This proposal doesn't really have any special consideration for the pane
|
||||
switcher UX. Neither of these actions would summon the pane switcher UX.
|
||||
|
||||
### Proposal E: Three actions
|
||||
|
||||
* `focusPane(target=id)`
|
||||
* `moveFocus(direction="up|down|left|right")`
|
||||
* `focusLastPane(usePaneSwitcher=false|true)`
|
||||
|
||||
In this design, neither `focusPane` nor `moveFocus` will summon the pane
|
||||
switcher UI (even once it's added). However, the `focusLastPane` one _could_,
|
||||
and subsequent keypresses could pop you through the MRU stack, while it's
|
||||
visible? The pane switcher could then display the panes for the tab in MRU
|
||||
order, and the user could just use the arrow keys to navigate the list if they
|
||||
so choose.
|
||||
|
||||
* **Pros**:
|
||||
- Each action does explicitly one thing.
|
||||
- Design accounts for future pane switcher UX
|
||||
* **Cons**:
|
||||
- Three separate actions for similar behavior
|
||||
|
||||
❌ This proposal is no longer being considered.
|
||||
|
||||
### Proposal F: It's literally just tmux
|
||||
|
||||
_Also known as the "one action to rule them all" proposal_
|
||||
|
||||
`focusPane(target=id, direction="up|down|left|right|last")`
|
||||
|
||||
Previously, this design was avoided, because what does `focusPane(target=4,
|
||||
direction=down)` do? Does it focus pane 4, or does it move focus down?
|
||||
|
||||
`tmux` solves this in one action by just doing both!
|
||||
|
||||
```
|
||||
Make pane target-pane the active pane ... If one of -D, -L, -R, or -U is used,
|
||||
respectively the pane below, to the left, to the right, or above the target pane
|
||||
is used.
|
||||
```
|
||||
_from `man tmux`_.
|
||||
|
||||
So `focusPane(target=1, direction=up)` will attempt to focus the pane above pane
|
||||
1. This action would not summon the pane switcher UX, even for
|
||||
`focusPane(direction=last)`
|
||||
|
||||
* **Pros**:
|
||||
- Fewest redundant actions
|
||||
* **Cons**:
|
||||
- Is this intuitive? That combining the params would do both, with `target`
|
||||
happening "first"?
|
||||
- Assumes that there will be a separate action added in the future for "Open
|
||||
the pane switcher (with some given ordering)"
|
||||
|
||||
|
||||
> 👉 **NOTE**: At this point, the author considered "Do we even want a separate
|
||||
> action to engage the tab switcher with panes expanded?" Perhaps panes being
|
||||
> visible in the tab switcher is just part fo the tab switcher's behavior. Maybe
|
||||
> there shouldn't be a separate "open the tab switcher with the panes expanded
|
||||
> to the pane I'm currently on, and the panes listed in MRU order" action.
|
||||
|
||||
❌ This proposal is no longer being considered.
|
||||
|
||||
## Conclusion
|
||||
|
||||
After much discussion as a team, we decided that **Proposal D** would be the
|
||||
best option. We felt that there wasn't a need to add any extra configuration to
|
||||
invoke the "pane switcher" as anything different than the "tab switcher". The
|
||||
"pane switcher" should really just exist as a part of the functionality of the
|
||||
advanced tab switcher, not as it's own thing.
|
||||
|
||||
Additionally, we concurred that the new "direction" value should be `prev`, not
|
||||
`last`, for consistency's sake.
|
||||
|
||||
## UI/UX Design
|
||||
|
||||
The only real UX being added with the agreed upon design is allowing the user to
|
||||
execute an action to move to the previously active pane within a single tab. No
|
||||
additional UX (including the pane switcher) is being prescribed in this spec at
|
||||
this time.
|
||||
|
||||
## Potential Issues
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><strong>Compatibility</strong></td>
|
||||
<td>
|
||||
|
||||
We've only adding a single enum value to an existing enum. Since we're not
|
||||
changing the meaning of any of the existing values, we do not expect any
|
||||
compatibility issues there. Additionally, we're not changing the default value
|
||||
of the `direction` param of the `moveFocus` action, so there are no further
|
||||
compatibility concerns there. Furthermore, no additional parameters are being
|
||||
added to the `moveFocus` action that would potentially give it a different
|
||||
meaning.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
In the current design, there's no way to move through all the panes with a
|
||||
single keybinding. For example, if a user wanted to bind <kbd>Alt+]</kbd> to
|
||||
move to the "next" pane, and <kbd>Alt+[</kbd> to move to the "previous" one.
|
||||
These movements would necessarily need to be in-order traversals, since there's
|
||||
no way of doing multiple MRU steps.
|
||||
|
||||
Fortunately, no one's really asked for traversing the panes in-order, so we're
|
||||
not really worried about this. Otherwise, it would maybe make sense for `last`
|
||||
to be the "previous MRU pane", and reserve `next`/`prev` for in-order traversal.
|
||||
|
||||
|
||||
[#2871]: https://github.com/microsoft/terminal/issues/2871
|
||||
[#5464]: https://github.com/microsoft/terminal/issues/5464
|
||||
[#5803]: https://github.com/microsoft/terminal/issues/5803
|
||||
|
||||
|
||||
BIN
res/Cascadia.ttf
BIN
res/Cascadia.ttf
Binary file not shown.
Binary file not shown.
@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
|
||||
|
||||
### Fonts Included
|
||||
|
||||
* Cascadia Code, Cascadia Mono (2009.21)
|
||||
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb
|
||||
* Cascadia Code, Cascadia Mono (2102.25)
|
||||
* from microsoft/cascadia-code@911dc421f333e3b72b97381d16fee5b71eb48f04
|
||||
|
||||
@@ -11,10 +11,16 @@
|
||||
// - attr - the default text attribute
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
// Note: will throw exception if unable to allocate memory for text attribute storage
|
||||
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
|
||||
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
|
||||
{
|
||||
_list.push_back(TextAttributeRun(cchRowWidth, attr));
|
||||
try
|
||||
{
|
||||
_list.emplace_back(TextAttributeRun(cchRowWidth, attr));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
FAIL_FAST_CAUGHT_EXCEPTION();
|
||||
}
|
||||
_cchRowWidth = cchRowWidth;
|
||||
}
|
||||
|
||||
@@ -25,7 +31,7 @@ ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
|
||||
void ATTR_ROW::Reset(const TextAttribute attr)
|
||||
{
|
||||
_list.clear();
|
||||
_list.push_back(TextAttributeRun(_cchRowWidth, attr));
|
||||
_list.emplace_back(TextAttributeRun(_cchRowWidth, attr));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -178,15 +184,15 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
|
||||
// Routine Description:
|
||||
// - Finds the hyperlink IDs present in this row and returns them
|
||||
// Return value:
|
||||
// - An unordered set containing the hyperlink IDs present in this row
|
||||
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
|
||||
// - The hyperlink IDs present in this row
|
||||
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
|
||||
{
|
||||
std::unordered_set<uint16_t> ids;
|
||||
std::vector<uint16_t> ids;
|
||||
for (const auto& run : _list)
|
||||
{
|
||||
if (run.GetAttributes().IsHyperlink())
|
||||
{
|
||||
ids.emplace(run.GetAttributes().GetHyperlinkId());
|
||||
ids.emplace_back(run.GetAttributes().GetHyperlinkId());
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
@@ -402,7 +408,7 @@ void ATTR_ROW::ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAtt
|
||||
// The original run was 3 long. The insertion run was 1 long. We need 1 more for the
|
||||
// fact that an existing piece of the run was split in half (to hold the latter half).
|
||||
const size_t cNewRun = _list.size() + newAttrs.size() + 1;
|
||||
std::vector<TextAttributeRun> newRun;
|
||||
decltype(_list) newRun;
|
||||
newRun.reserve(cNewRun);
|
||||
|
||||
// We will start analyzing from the beginning of our existing run.
|
||||
@@ -595,8 +601,7 @@ std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribut
|
||||
{
|
||||
if (runs.empty() || runs.back().GetAttributes() != attr)
|
||||
{
|
||||
const TextAttributeRun run(1, attr);
|
||||
runs.push_back(run);
|
||||
runs.emplace_back(TextAttributeRun(1, attr));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -28,9 +28,16 @@ class ATTR_ROW final
|
||||
public:
|
||||
using const_iterator = typename AttrRowIterator;
|
||||
|
||||
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr);
|
||||
ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
|
||||
noexcept;
|
||||
|
||||
void Reset(const TextAttribute attr);
|
||||
~ATTR_ROW() = default;
|
||||
|
||||
ATTR_ROW(const ATTR_ROW&) = default;
|
||||
ATTR_ROW& operator=(const ATTR_ROW&) = default;
|
||||
ATTR_ROW(ATTR_ROW&&)
|
||||
noexcept = default;
|
||||
ATTR_ROW& operator=(ATTR_ROW&&) noexcept = default;
|
||||
|
||||
TextAttribute GetAttrByColumn(const size_t column) const;
|
||||
TextAttribute GetAttrByColumn(const size_t column,
|
||||
@@ -41,7 +48,7 @@ public:
|
||||
size_t FindAttrIndex(const size_t index,
|
||||
size_t* const pApplies) const;
|
||||
|
||||
std::unordered_set<uint16_t> GetHyperlinks();
|
||||
std::vector<uint16_t> GetHyperlinks();
|
||||
|
||||
bool SetAttrToEnd(const UINT iStart, const TextAttribute attr);
|
||||
void ReplaceAttrs(const TextAttribute& toBeReplacedAttr, const TextAttribute& replaceWith) noexcept;
|
||||
@@ -63,12 +70,16 @@ public:
|
||||
|
||||
friend bool operator==(const ATTR_ROW& a, const ATTR_ROW& b) noexcept;
|
||||
friend class AttrRowIterator;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
std::vector<TextAttributeRun> _list;
|
||||
void Reset(const TextAttribute attr);
|
||||
|
||||
boost::container::small_vector<TextAttributeRun, 1> _list;
|
||||
size_t _cchRowWidth;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
friend class CommonState;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -39,19 +39,6 @@ bool AttrRowIterator::operator!=(const AttrRowIterator& it) const noexcept
|
||||
return !(*this == it);
|
||||
}
|
||||
|
||||
AttrRowIterator& AttrRowIterator::operator++() noexcept
|
||||
{
|
||||
_increment(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AttrRowIterator AttrRowIterator::operator++(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_increment(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
AttrRowIterator& AttrRowIterator::operator+=(const ptrdiff_t& movement)
|
||||
{
|
||||
if (!_exceeded)
|
||||
@@ -74,19 +61,6 @@ AttrRowIterator& AttrRowIterator::operator-=(const ptrdiff_t& movement)
|
||||
return this->operator+=(-movement);
|
||||
}
|
||||
|
||||
AttrRowIterator& AttrRowIterator::operator--() noexcept
|
||||
{
|
||||
_decrement(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
AttrRowIterator AttrRowIterator::operator--(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_decrement(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
const TextAttribute* AttrRowIterator::operator->() const
|
||||
{
|
||||
THROW_HR_IF(E_BOUNDS, _exceeded);
|
||||
|
||||
@@ -38,20 +38,38 @@ public:
|
||||
bool operator==(const AttrRowIterator& it) const noexcept;
|
||||
bool operator!=(const AttrRowIterator& it) const noexcept;
|
||||
|
||||
AttrRowIterator& operator++() noexcept;
|
||||
AttrRowIterator operator++(int) noexcept;
|
||||
AttrRowIterator& operator++() noexcept
|
||||
{
|
||||
_increment(1);
|
||||
return *this;
|
||||
}
|
||||
AttrRowIterator operator++(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_increment(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
AttrRowIterator& operator+=(const ptrdiff_t& movement);
|
||||
AttrRowIterator& operator-=(const ptrdiff_t& movement);
|
||||
|
||||
AttrRowIterator& operator--() noexcept;
|
||||
AttrRowIterator operator--(int) noexcept;
|
||||
AttrRowIterator& operator--() noexcept
|
||||
{
|
||||
_decrement(1);
|
||||
return *this;
|
||||
}
|
||||
AttrRowIterator operator--(int) noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
_decrement(1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
const TextAttribute* operator->() const;
|
||||
const TextAttribute& operator*() const;
|
||||
|
||||
private:
|
||||
std::vector<TextAttributeRun>::const_iterator _run;
|
||||
boost::container::small_vector_base<TextAttributeRun>::const_iterator _run;
|
||||
const ATTR_ROW* _pAttrRow;
|
||||
size_t _currentAttributeIndex; // index of TextAttribute within the current TextAttributeRun
|
||||
bool _exceeded;
|
||||
|
||||
@@ -15,57 +15,14 @@
|
||||
// Return Value:
|
||||
// - instantiated object
|
||||
// Note: will through if unable to allocate char/attribute buffers
|
||||
CharRow::CharRow(size_t rowWidth, ROW* const pParent) :
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 26447) // small_vector's constructor says it can throw but it should not given how we use it. This suppresses this error for the AuditMode build.
|
||||
CharRow::CharRow(size_t rowWidth, ROW* const pParent) noexcept :
|
||||
_data(rowWidth, value_type()),
|
||||
_pParent{ FAIL_FAST_IF_NULL(pParent) }
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the wrap status for the current row
|
||||
// Arguments:
|
||||
// - wrapForced - True if the row ran out of space and we forced to wrap to the next row. False otherwise.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CharRow::SetWrapForced(const bool wrapForced) noexcept
|
||||
{
|
||||
_wrapForced = wrapForced;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the wrap status for the current row
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if the row ran out of space and we were forced to wrap to the next row. False otherwise.
|
||||
bool CharRow::WasWrapForced() const noexcept
|
||||
{
|
||||
return _wrapForced;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets the double byte padding for the current row
|
||||
// Arguments:
|
||||
// - fWrapWasForced - True if the row ran out of space for a double byte character and we padded out the row. False otherwise.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CharRow::SetDoubleBytePadded(const bool doubleBytePadded) noexcept
|
||||
{
|
||||
_doubleBytePadded = doubleBytePadded;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Gets the double byte padding status for the current row.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - True if the row didn't have space for a double byte character and we were padded out the row. False otherwise.
|
||||
bool CharRow::WasDoubleBytePadded() const noexcept
|
||||
{
|
||||
return _doubleBytePadded;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
|
||||
// Routine Description:
|
||||
// - gets the size of the row, in glyph cells
|
||||
@@ -90,9 +47,6 @@ void CharRow::Reset() noexcept
|
||||
{
|
||||
cell.Reset();
|
||||
}
|
||||
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -141,7 +95,7 @@ typename CharRow::const_iterator CharRow::cend() const noexcept
|
||||
// - The calculated left boundary of the internal string.
|
||||
size_t CharRow::MeasureLeft() const noexcept
|
||||
{
|
||||
std::vector<value_type>::const_iterator it = _data.cbegin();
|
||||
const_iterator it = _data.cbegin();
|
||||
while (it != _data.cend() && it->IsSpace())
|
||||
{
|
||||
++it;
|
||||
@@ -155,9 +109,9 @@ size_t CharRow::MeasureLeft() const noexcept
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The calculated right boundary of the internal string.
|
||||
size_t CharRow::MeasureRight() const noexcept
|
||||
size_t CharRow::MeasureRight() const
|
||||
{
|
||||
std::vector<value_type>::const_reverse_iterator it = _data.crbegin();
|
||||
const_reverse_iterator it = _data.crbegin();
|
||||
while (it != _data.crend() && it->IsSpace())
|
||||
{
|
||||
++it;
|
||||
|
||||
@@ -49,27 +49,21 @@ class CharRow final
|
||||
public:
|
||||
using glyph_type = typename wchar_t;
|
||||
using value_type = typename CharRowCell;
|
||||
using iterator = typename std::vector<value_type>::iterator;
|
||||
using const_iterator = typename std::vector<value_type>::const_iterator;
|
||||
using iterator = typename boost::container::small_vector_base<value_type>::iterator;
|
||||
using const_iterator = typename boost::container::small_vector_base<value_type>::const_iterator;
|
||||
using const_reverse_iterator = typename boost::container::small_vector_base<value_type>::const_reverse_iterator;
|
||||
using reference = typename CharRowCellReference;
|
||||
|
||||
CharRow(size_t rowWidth, ROW* const pParent);
|
||||
CharRow(size_t rowWidth, ROW* const pParent) noexcept;
|
||||
|
||||
void SetWrapForced(const bool wrap) noexcept;
|
||||
bool WasWrapForced() const noexcept;
|
||||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept;
|
||||
bool WasDoubleBytePadded() const noexcept;
|
||||
size_t size() const noexcept;
|
||||
void Reset() noexcept;
|
||||
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
|
||||
size_t MeasureLeft() const noexcept;
|
||||
size_t MeasureRight() const noexcept;
|
||||
void ClearCell(const size_t column);
|
||||
size_t MeasureRight() const;
|
||||
bool ContainsText() const noexcept;
|
||||
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
|
||||
DbcsAttribute& DbcsAttrAt(const size_t column);
|
||||
void ClearGlyph(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
const DelimiterClass DelimiterClassAt(const size_t column, const std::wstring_view wordDelimiters) const;
|
||||
|
||||
@@ -80,9 +74,11 @@ public:
|
||||
// iterators
|
||||
iterator begin() noexcept;
|
||||
const_iterator cbegin() const noexcept;
|
||||
const_iterator begin() const noexcept { return cbegin(); }
|
||||
|
||||
iterator end() noexcept;
|
||||
const_iterator cend() const noexcept;
|
||||
const_iterator end() const noexcept { return cend(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
@@ -91,29 +87,21 @@ public:
|
||||
void UpdateParent(ROW* const pParent);
|
||||
|
||||
friend CharRowCellReference;
|
||||
friend constexpr bool operator==(const CharRow& a, const CharRow& b) noexcept;
|
||||
friend class ROW;
|
||||
|
||||
private:
|
||||
void Reset() noexcept;
|
||||
void ClearCell(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
protected:
|
||||
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
|
||||
bool _wrapForced;
|
||||
|
||||
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
|
||||
bool _doubleBytePadded;
|
||||
|
||||
// storage for glyph data and dbcs attributes
|
||||
std::vector<value_type> _data;
|
||||
boost::container::small_vector<value_type, 120> _data;
|
||||
|
||||
// ROW that this CharRow belongs to
|
||||
ROW* _pParent;
|
||||
};
|
||||
|
||||
constexpr bool operator==(const CharRow& a, const CharRow& b) noexcept
|
||||
{
|
||||
return (a._wrapForced == b._wrapForced &&
|
||||
a._doubleBytePadded == b._doubleBytePadded &&
|
||||
a._data == b._data);
|
||||
}
|
||||
|
||||
template<typename InputIt1, typename InputIt2>
|
||||
void OverwriteColumns(InputIt1 startChars, InputIt1 endChars, InputIt2 startAttrs, CharRow::iterator outIt)
|
||||
{
|
||||
|
||||
@@ -8,18 +8,6 @@
|
||||
// default glyph value, used for resetting the character data portion of a cell
|
||||
static constexpr wchar_t DefaultValue = UNICODE_SPACE;
|
||||
|
||||
CharRowCell::CharRowCell() noexcept :
|
||||
_wch{ DefaultValue },
|
||||
_attr{}
|
||||
{
|
||||
}
|
||||
|
||||
CharRowCell::CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept :
|
||||
_wch{ wch },
|
||||
_attr{ attr }
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - "erases" the glyph. really sets it back to the default "empty" value
|
||||
void CharRowCell::EraseChars() noexcept
|
||||
|
||||
@@ -17,6 +17,7 @@ Author(s):
|
||||
#pragma once
|
||||
|
||||
#include "DbcsAttribute.hpp"
|
||||
#include "unicode.hpp"
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
// currently CharRowCell's fields use 3 bytes of memory, leaving the 4th byte in unused. this leads
|
||||
@@ -27,8 +28,13 @@ Author(s):
|
||||
class CharRowCell final
|
||||
{
|
||||
public:
|
||||
CharRowCell() noexcept;
|
||||
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept;
|
||||
CharRowCell() noexcept = default;
|
||||
CharRowCell(const wchar_t wch, const DbcsAttribute attr) noexcept
|
||||
:
|
||||
_wch(wch),
|
||||
_attr(attr)
|
||||
{
|
||||
}
|
||||
|
||||
void EraseChars() noexcept;
|
||||
void Reset() noexcept;
|
||||
@@ -44,8 +50,8 @@ public:
|
||||
friend constexpr bool operator==(const CharRowCell& a, const CharRowCell& b) noexcept;
|
||||
|
||||
private:
|
||||
wchar_t _wch;
|
||||
DbcsAttribute _attr;
|
||||
wchar_t _wch{ UNICODE_SPACE };
|
||||
DbcsAttribute _attr{};
|
||||
};
|
||||
|
||||
#if (defined(_M_IX86) || defined(_M_AMD64))
|
||||
|
||||
@@ -32,8 +32,8 @@ public:
|
||||
}
|
||||
|
||||
~CharRowCellReference() = default;
|
||||
CharRowCellReference(const CharRowCellReference&) = default;
|
||||
CharRowCellReference(CharRowCellReference&&) = default;
|
||||
CharRowCellReference(const CharRowCellReference&) noexcept = default;
|
||||
CharRowCellReference(CharRowCellReference&&) noexcept = default;
|
||||
|
||||
void operator=(const CharRowCellReference&) = delete;
|
||||
void operator=(CharRowCellReference&&) = delete;
|
||||
|
||||
@@ -16,50 +16,17 @@
|
||||
// - pParent - the text buffer that this row belongs to
|
||||
// Return Value:
|
||||
// - constructed object
|
||||
ROW::ROW(const SHORT rowId, const short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) :
|
||||
ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent) noexcept :
|
||||
_id{ rowId },
|
||||
_rowWidth{ gsl::narrow<size_t>(rowWidth) },
|
||||
_charRow{ gsl::narrow<size_t>(rowWidth), this },
|
||||
_attrRow{ gsl::narrow<UINT>(rowWidth), fillAttribute },
|
||||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
_attrRow{ rowWidth, fillAttribute },
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
_pParent{ pParent }
|
||||
{
|
||||
}
|
||||
|
||||
size_t ROW::size() const noexcept
|
||||
{
|
||||
return _rowWidth;
|
||||
}
|
||||
|
||||
const CharRow& ROW::GetCharRow() const noexcept
|
||||
{
|
||||
return _charRow;
|
||||
}
|
||||
|
||||
CharRow& ROW::GetCharRow() noexcept
|
||||
{
|
||||
return _charRow;
|
||||
}
|
||||
|
||||
const ATTR_ROW& ROW::GetAttrRow() const noexcept
|
||||
{
|
||||
return _attrRow;
|
||||
}
|
||||
|
||||
ATTR_ROW& ROW::GetAttrRow() noexcept
|
||||
{
|
||||
return _attrRow;
|
||||
}
|
||||
|
||||
SHORT ROW::GetId() const noexcept
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
void ROW::SetId(const SHORT id) noexcept
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets all properties of the ROW to default values
|
||||
// Arguments:
|
||||
@@ -68,6 +35,8 @@ void ROW::SetId(const SHORT id) noexcept
|
||||
// - <none>
|
||||
bool ROW::Reset(const TextAttribute Attr)
|
||||
{
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
_charRow.Reset();
|
||||
try
|
||||
{
|
||||
@@ -87,7 +56,7 @@ bool ROW::Reset(const TextAttribute Attr)
|
||||
// - width - the new width, in cells
|
||||
// Return Value:
|
||||
// - S_OK if successful, otherwise relevant error
|
||||
[[nodiscard]] HRESULT ROW::Resize(const size_t width)
|
||||
[[nodiscard]] HRESULT ROW::Resize(const unsigned short width)
|
||||
{
|
||||
RETURN_IF_FAILED(_charRow.Resize(width));
|
||||
try
|
||||
@@ -113,25 +82,6 @@ void ROW::ClearColumn(const size_t column)
|
||||
_charRow.ClearCell(column);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - gets the text of the row as it would be shown on the screen
|
||||
// Return Value:
|
||||
// - wstring containing text for the row
|
||||
std::wstring ROW::GetText() const
|
||||
{
|
||||
return _charRow.GetText();
|
||||
}
|
||||
|
||||
RowCellIterator ROW::AsCellIter(const size_t startIndex) const
|
||||
{
|
||||
return AsCellIter(startIndex, size() - startIndex);
|
||||
}
|
||||
|
||||
RowCellIterator ROW::AsCellIter(const size_t startIndex, const size_t count) const
|
||||
{
|
||||
return RowCellIterator(*this, startIndex, count);
|
||||
}
|
||||
|
||||
UnicodeStorage& ROW::GetUnicodeStorage() noexcept
|
||||
{
|
||||
return _pParent->GetUnicodeStorage();
|
||||
@@ -213,7 +163,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
else if (fillingLastColumn && it->DbcsAttr().IsLeading())
|
||||
{
|
||||
_charRow.ClearCell(currentIndex);
|
||||
_charRow.SetDoubleBytePadded(true);
|
||||
SetDoubleBytePadded(true);
|
||||
}
|
||||
// Otherwise, copy the data given and increment the iterator.
|
||||
else
|
||||
@@ -231,7 +181,7 @@ OutputCellIterator ROW::WriteCells(OutputCellIterator it, const size_t index, co
|
||||
if (wrap.has_value() && fillingLastColumn)
|
||||
{
|
||||
// set wrap status on the row to parameter's value.
|
||||
_charRow.SetWrapForced(wrap.value());
|
||||
SetWrapForced(*wrap);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -24,7 +24,6 @@ Revision History:
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
#include "RowCellIterator.hpp"
|
||||
#include "UnicodeStorage.hpp"
|
||||
|
||||
class TextBuffer;
|
||||
@@ -32,36 +31,39 @@ class TextBuffer;
|
||||
class ROW final
|
||||
{
|
||||
public:
|
||||
ROW(const SHORT rowId, const short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent);
|
||||
ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute fillAttribute, TextBuffer* const pParent)
|
||||
noexcept;
|
||||
|
||||
size_t size() const noexcept;
|
||||
size_t size() const noexcept { return _rowWidth; }
|
||||
|
||||
const CharRow& GetCharRow() const noexcept;
|
||||
CharRow& GetCharRow() noexcept;
|
||||
void SetWrapForced(const bool wrap) noexcept { _wrapForced = wrap; }
|
||||
bool WasWrapForced() const noexcept { return _wrapForced; }
|
||||
|
||||
const ATTR_ROW& GetAttrRow() const noexcept;
|
||||
ATTR_ROW& GetAttrRow() noexcept;
|
||||
void SetDoubleBytePadded(const bool doubleBytePadded) noexcept { _doubleBytePadded = doubleBytePadded; }
|
||||
bool WasDoubleBytePadded() const noexcept { return _doubleBytePadded; }
|
||||
|
||||
SHORT GetId() const noexcept;
|
||||
void SetId(const SHORT id) noexcept;
|
||||
const CharRow& GetCharRow() const noexcept { return _charRow; }
|
||||
CharRow& GetCharRow() noexcept { return _charRow; }
|
||||
|
||||
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
|
||||
|
||||
SHORT GetId() const noexcept { return _id; }
|
||||
void SetId(const SHORT id) noexcept { _id = id; }
|
||||
|
||||
bool Reset(const TextAttribute Attr);
|
||||
[[nodiscard]] HRESULT Resize(const size_t width);
|
||||
[[nodiscard]] HRESULT Resize(const unsigned short width);
|
||||
|
||||
void ClearColumn(const size_t column);
|
||||
std::wstring GetText() const;
|
||||
|
||||
RowCellIterator AsCellIter(const size_t startIndex) const;
|
||||
RowCellIterator AsCellIter(const size_t startIndex, const size_t count) const;
|
||||
std::wstring GetText() const { return _charRow.GetText(); }
|
||||
|
||||
UnicodeStorage& GetUnicodeStorage() noexcept;
|
||||
const UnicodeStorage& GetUnicodeStorage() const noexcept;
|
||||
|
||||
OutputCellIterator WriteCells(OutputCellIterator it, const size_t index, const std::optional<bool> wrap = std::nullopt, std::optional<size_t> limitRight = std::nullopt);
|
||||
|
||||
friend bool operator==(const ROW& a, const ROW& b) noexcept;
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend constexpr bool operator==(const ROW& a, const ROW& b) noexcept;
|
||||
friend class RowTests;
|
||||
#endif
|
||||
|
||||
@@ -69,15 +71,19 @@ private:
|
||||
CharRow _charRow;
|
||||
ATTR_ROW _attrRow;
|
||||
SHORT _id;
|
||||
size_t _rowWidth;
|
||||
unsigned short _rowWidth;
|
||||
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
|
||||
bool _wrapForced;
|
||||
// Occurs when the user runs out of text to support a double byte character and we're forced to the next line
|
||||
bool _doubleBytePadded;
|
||||
TextBuffer* _pParent; // non ownership pointer
|
||||
};
|
||||
|
||||
inline bool operator==(const ROW& a, const ROW& b) noexcept
|
||||
#ifdef UNIT_TESTING
|
||||
constexpr bool operator==(const ROW& a, const ROW& b) noexcept
|
||||
{
|
||||
return (a._charRow == b._charRow &&
|
||||
a._attrRow == b._attrRow &&
|
||||
a._rowWidth == b._rowWidth &&
|
||||
a._pParent == b._pParent &&
|
||||
// comparison is only used in the tests; this should suffice.
|
||||
return (a._pParent == b._pParent &&
|
||||
a._id == b._id);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "RowCellIterator.hpp"
|
||||
#include "Row.hpp"
|
||||
|
||||
#include "../../types/inc/convert.hpp"
|
||||
#include "../../types/inc/Utf16Parser.hpp"
|
||||
|
||||
RowCellIterator::RowCellIterator(const ROW& row, const size_t start, const size_t length) :
|
||||
_row(row),
|
||||
_start(start),
|
||||
_length(length),
|
||||
_pos(start),
|
||||
_view(s_GenerateView(row, start))
|
||||
{
|
||||
}
|
||||
|
||||
RowCellIterator::operator bool() const noexcept
|
||||
{
|
||||
// In lieu of using start and end, this custom iterator type simply becomes bool false
|
||||
// when we run out of items to iterate over.
|
||||
return _pos < (_start + _length);
|
||||
}
|
||||
|
||||
bool RowCellIterator::operator==(const RowCellIterator& it) const noexcept
|
||||
{
|
||||
return _row == it._row &&
|
||||
_start == it._start &&
|
||||
_length == it._length &&
|
||||
_pos == it._pos;
|
||||
}
|
||||
bool RowCellIterator::operator!=(const RowCellIterator& it) const noexcept
|
||||
{
|
||||
return !(*this == it);
|
||||
}
|
||||
|
||||
RowCellIterator& RowCellIterator::operator+=(const ptrdiff_t& movement) noexcept
|
||||
{
|
||||
_pos += movement;
|
||||
|
||||
return (*this);
|
||||
}
|
||||
|
||||
RowCellIterator& RowCellIterator::operator++() noexcept
|
||||
{
|
||||
return this->operator+=(1);
|
||||
}
|
||||
|
||||
RowCellIterator RowCellIterator::operator++(int) noexcept
|
||||
{
|
||||
auto temp(*this);
|
||||
operator++();
|
||||
return temp;
|
||||
}
|
||||
|
||||
RowCellIterator RowCellIterator::operator+(const ptrdiff_t& movement) noexcept
|
||||
{
|
||||
auto temp(*this);
|
||||
temp += movement;
|
||||
return temp;
|
||||
}
|
||||
|
||||
const OutputCellView& RowCellIterator::operator*() const noexcept
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
const OutputCellView* RowCellIterator::operator->() const noexcept
|
||||
{
|
||||
return &_view;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Member function to update the view to the current position in the buffer with
|
||||
// the data held on this object.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void RowCellIterator::_RefreshView()
|
||||
{
|
||||
_view = s_GenerateView(_row, _pos);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Static function to create a view.
|
||||
// - It's pulled out statically so it can be used during construction with just the given
|
||||
// variables (so OutputCellView doesn't need an empty default constructor)
|
||||
// - This will infer the width of the glyph and apply the appropriate attributes to the view.
|
||||
// Arguments:
|
||||
// - view - View representing characters corresponding to a single glyph
|
||||
// - attr - Color attributes to apply to the text
|
||||
// Return Value:
|
||||
// - Object representing the view into this cell
|
||||
OutputCellView RowCellIterator::s_GenerateView(const ROW& row,
|
||||
const size_t pos)
|
||||
{
|
||||
const auto& charRow = row.GetCharRow();
|
||||
|
||||
const auto glyph = charRow.GlyphAt(pos);
|
||||
const auto dbcsAttr = charRow.DbcsAttrAt(pos);
|
||||
|
||||
const auto textAttr = row.GetAttrRow().GetAttrByColumn(pos);
|
||||
const auto behavior = TextAttributeBehavior::Stored;
|
||||
|
||||
return OutputCellView(glyph, dbcsAttr, textAttr, behavior);
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- RowCellIterator.hpp
|
||||
|
||||
Abstract:
|
||||
- Read-only view into cells already in the input buffer.
|
||||
- This is done for performance reasons (avoid heap allocs and copies).
|
||||
|
||||
Author:
|
||||
- Michael Niksa (MiNiksa) 12-Oct-2018
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OutputCellView.hpp"
|
||||
class ROW;
|
||||
|
||||
class RowCellIterator final
|
||||
{
|
||||
public:
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = OutputCellView;
|
||||
using difference_type = ptrdiff_t;
|
||||
using pointer = OutputCellView*;
|
||||
using reference = OutputCellView&;
|
||||
|
||||
RowCellIterator(const ROW& row, const size_t start, const size_t length);
|
||||
~RowCellIterator() = default;
|
||||
|
||||
RowCellIterator& operator=(const RowCellIterator& it) = delete;
|
||||
|
||||
operator bool() const noexcept;
|
||||
|
||||
bool operator==(const RowCellIterator& it) const noexcept;
|
||||
bool operator!=(const RowCellIterator& it) const noexcept;
|
||||
|
||||
RowCellIterator& operator+=(const ptrdiff_t& movement) noexcept;
|
||||
RowCellIterator& operator++() noexcept;
|
||||
RowCellIterator operator++(int) noexcept;
|
||||
RowCellIterator operator+(const ptrdiff_t& movement) noexcept;
|
||||
|
||||
const OutputCellView& operator*() const noexcept;
|
||||
const OutputCellView* operator->() const noexcept;
|
||||
|
||||
private:
|
||||
const ROW& _row;
|
||||
const size_t _start;
|
||||
const size_t _length;
|
||||
|
||||
size_t _pos;
|
||||
OutputCellView _view;
|
||||
|
||||
void _RefreshView();
|
||||
static OutputCellView s_GenerateView(const ROW& row, const size_t pos);
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "TextAttributeRun.hpp"
|
||||
|
||||
TextAttributeRun::TextAttributeRun() noexcept :
|
||||
_cchLength(0)
|
||||
{
|
||||
SetAttributes(TextAttribute(0));
|
||||
}
|
||||
|
||||
TextAttributeRun::TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
|
||||
_cchLength(cchLength)
|
||||
{
|
||||
SetAttributes(attr);
|
||||
}
|
||||
|
||||
size_t TextAttributeRun::GetLength() const noexcept
|
||||
{
|
||||
return _cchLength;
|
||||
}
|
||||
|
||||
void TextAttributeRun::SetLength(const size_t cchLength) noexcept
|
||||
{
|
||||
_cchLength = cchLength;
|
||||
}
|
||||
|
||||
void TextAttributeRun::IncrementLength() noexcept
|
||||
{
|
||||
_cchLength++;
|
||||
}
|
||||
|
||||
void TextAttributeRun::DecrementLength() noexcept
|
||||
{
|
||||
_cchLength--;
|
||||
}
|
||||
|
||||
const TextAttribute& TextAttributeRun::GetAttributes() const noexcept
|
||||
{
|
||||
return _attributes;
|
||||
}
|
||||
|
||||
void TextAttributeRun::SetAttributes(const TextAttribute textAttribute) noexcept
|
||||
{
|
||||
_attributes = textAttribute;
|
||||
}
|
||||
@@ -25,20 +25,24 @@ Revision History:
|
||||
class TextAttributeRun final
|
||||
{
|
||||
public:
|
||||
TextAttributeRun() noexcept;
|
||||
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept;
|
||||
TextAttributeRun() = default;
|
||||
TextAttributeRun(const size_t cchLength, const TextAttribute attr) noexcept :
|
||||
_cchLength(gsl::narrow<unsigned int>(cchLength))
|
||||
{
|
||||
SetAttributes(attr);
|
||||
}
|
||||
|
||||
size_t GetLength() const noexcept;
|
||||
void SetLength(const size_t cchLength) noexcept;
|
||||
void IncrementLength() noexcept;
|
||||
void DecrementLength() noexcept;
|
||||
size_t GetLength() const noexcept { return _cchLength; }
|
||||
void SetLength(const size_t cchLength) noexcept { _cchLength = gsl::narrow<unsigned int>(cchLength); }
|
||||
void IncrementLength() noexcept { _cchLength++; }
|
||||
void DecrementLength() noexcept { _cchLength--; }
|
||||
|
||||
const TextAttribute& GetAttributes() const noexcept;
|
||||
void SetAttributes(const TextAttribute textAttribute) noexcept;
|
||||
const TextAttribute& GetAttributes() const noexcept { return _attributes; }
|
||||
void SetAttributes(const TextAttribute textAttribute) noexcept { _attributes = textAttribute; }
|
||||
|
||||
private:
|
||||
size_t _cchLength;
|
||||
TextAttribute _attributes;
|
||||
unsigned int _cchLength{ 0 };
|
||||
TextAttribute _attributes{ 0 };
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
friend class AttrRowTests;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>bufferout</RootNamespace>
|
||||
<ProjectName>BufferOut</ProjectName>
|
||||
<TargetName>ConBufferOut</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
@@ -18,11 +18,9 @@
|
||||
<ClCompile Include="..\OutputCellRect.cpp" />
|
||||
<ClCompile Include="..\OutputCellView.cpp" />
|
||||
<ClCompile Include="..\Row.cpp" />
|
||||
<ClCompile Include="..\RowCellIterator.cpp" />
|
||||
<ClCompile Include="..\search.cpp" />
|
||||
<ClCompile Include="..\TextColor.cpp" />
|
||||
<ClCompile Include="..\TextAttribute.cpp" />
|
||||
<ClCompile Include="..\TextAttributeRun.cpp" />
|
||||
<ClCompile Include="..\textBuffer.cpp" />
|
||||
<ClCompile Include="..\textBufferCellIterator.cpp" />
|
||||
<ClCompile Include="..\textBufferTextIterator.cpp" />
|
||||
@@ -45,7 +43,6 @@
|
||||
<ClInclude Include="..\OutputCellRect.hpp" />
|
||||
<ClInclude Include="..\OutputCellView.hpp" />
|
||||
<ClInclude Include="..\Row.hpp" />
|
||||
<ClInclude Include="..\RowCellIterator.hpp" />
|
||||
<ClInclude Include="..\search.h" />
|
||||
<ClInclude Include="..\TextColor.h" />
|
||||
<ClInclude Include="..\TextAttribute.h" />
|
||||
|
||||
@@ -37,10 +37,8 @@ SOURCES= \
|
||||
..\OutputCellRect.cpp \
|
||||
..\OutputCellView.cpp \
|
||||
..\Row.cpp \
|
||||
..\RowCellIterator.cpp \
|
||||
..\TextColor.cpp \
|
||||
..\TextAttribute.cpp \
|
||||
..\TextAttributeRun.cpp \
|
||||
..\textBuffer.cpp \
|
||||
..\textBufferCellIterator.cpp \
|
||||
..\textBufferTextIterator.cpp \
|
||||
|
||||
@@ -42,6 +42,7 @@ TextBuffer::TextBuffer(const COORD screenBufferSize,
|
||||
_currentPatternId{ 0 }
|
||||
{
|
||||
// initialize ROWs
|
||||
_storage.reserve(static_cast<size_t>(screenBufferSize.Y));
|
||||
for (size_t i = 0; i < static_cast<size_t>(screenBufferSize.Y); ++i)
|
||||
{
|
||||
_storage.emplace_back(static_cast<SHORT>(i), screenBufferSize.X, _currentAttributes, this);
|
||||
@@ -253,7 +254,7 @@ bool TextBuffer::_AssertValidDoubleByteSequence(const DbcsAttribute dbcsAttribut
|
||||
// Erase previous character into an N type.
|
||||
try
|
||||
{
|
||||
prevRow.GetCharRow().ClearCell(coordPrevPosition.X);
|
||||
prevRow.ClearColumn(coordPrevPosition.X);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -295,8 +296,8 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
|
||||
if (GetCursor().GetPosition().X == sBufferWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
CharRow& charRow = GetRowByOffset(GetCursor().GetPosition().Y).GetCharRow();
|
||||
charRow.SetDoubleBytePadded(true);
|
||||
auto& row = GetRowByOffset(GetCursor().GetPosition().Y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
fSuccess = IncrementCursor();
|
||||
@@ -479,7 +480,7 @@ void TextBuffer::_AdjustWrapOnCurrentRow(const bool fSet)
|
||||
const UINT uiCurrentRowOffset = GetCursor().GetPosition().Y;
|
||||
|
||||
// Set the wrap status as appropriate
|
||||
GetRowByOffset(uiCurrentRowOffset).GetCharRow().SetWrapForced(fSet);
|
||||
GetRowByOffset(uiCurrentRowOffset).SetWrapForced(fSet);
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
@@ -809,8 +810,7 @@ void TextBuffer::Reset()
|
||||
|
||||
for (auto& row : _storage)
|
||||
{
|
||||
row.GetCharRow().Reset();
|
||||
row.GetAttrRow().Reset(attr);
|
||||
row.Reset(attr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,11 +837,10 @@ void TextBuffer::Reset()
|
||||
const SHORT TopRowIndex = (GetFirstRowIndex() + TopRow) % currentSize.Y;
|
||||
|
||||
// rotate rows until the top row is at index 0
|
||||
const ROW& newTopRow = _storage.at(TopRowIndex);
|
||||
while (&newTopRow != &_storage.front())
|
||||
for (int i = 0; i < TopRowIndex; i++)
|
||||
{
|
||||
_storage.push_back(std::move(_storage.front()));
|
||||
_storage.pop_front();
|
||||
_storage.emplace_back(std::move(_storage.front()));
|
||||
_storage.erase(_storage.begin());
|
||||
}
|
||||
|
||||
_SetFirstRowIndex(0);
|
||||
@@ -1230,9 +1229,15 @@ void TextBuffer::_PruneHyperlinks()
|
||||
// If the buffer does not contain the same reference, we can remove that hyperlink from our map
|
||||
// This way, obsolete hyperlink references are cleared from our hyperlink map instead of hanging around
|
||||
// Get all the hyperlink references in the row we're erasing
|
||||
auto firstRowRefs = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
|
||||
if (!firstRowRefs.empty())
|
||||
const auto hyperlinks = _storage.at(_firstRow).GetAttrRow().GetHyperlinks();
|
||||
|
||||
if (!hyperlinks.empty())
|
||||
{
|
||||
// Move to unordered set so we can use hashed lookup of IDs instead of linear search.
|
||||
// Only make it an unordered set now because set always heap allocates but vector
|
||||
// doesn't when the set is empty (saving an allocation in the common case of no links.)
|
||||
std::unordered_set<uint16_t> firstRowRefs{ hyperlinks.cbegin(), hyperlinks.cend() };
|
||||
|
||||
const auto total = TotalRowCount();
|
||||
// Loop through all the rows in the buffer except the first row -
|
||||
// we have found all hyperlink references in the first row and put them in refs,
|
||||
@@ -1254,12 +1259,12 @@ void TextBuffer::_PruneHyperlinks()
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now delete obsolete references from our map
|
||||
for (auto hyperlinkReference : firstRowRefs)
|
||||
{
|
||||
RemoveHyperlinkFromMap(hyperlinkReference);
|
||||
// Now delete obsolete references from our map
|
||||
for (auto hyperlinkReference : firstRowRefs)
|
||||
{
|
||||
RemoveHyperlinkFromMap(hyperlinkReference);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1582,7 +1587,7 @@ const TextBuffer::TextAndColor TextBuffer::GetText(const bool includeCRLF,
|
||||
}
|
||||
|
||||
// We apply formatting to rows if the row was NOT wrapped or formatting of wrapped rows is allowed
|
||||
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).GetCharRow().WasWrapForced();
|
||||
const bool shouldFormatRow = formatWrappedRows || !GetRowByOffset(iRow).WasWrapForced();
|
||||
|
||||
if (trimTrailingWhitespace)
|
||||
{
|
||||
@@ -2063,7 +2068,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// included.)
|
||||
// As such, adjust the "right" to be the width of the row
|
||||
// to capture all these spaces
|
||||
if (charRow.WasWrapForced())
|
||||
if (row.WasWrapForced())
|
||||
{
|
||||
iRight = cOldColsTotal;
|
||||
|
||||
@@ -2072,7 +2077,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// piece of padding because of a double byte LEADING
|
||||
// character, then remove one from the "right" to
|
||||
// leave this padding out of the copy process.
|
||||
if (charRow.WasDoubleBytePadded())
|
||||
if (row.WasDoubleBytePadded())
|
||||
{
|
||||
iRight--;
|
||||
}
|
||||
@@ -2136,7 +2141,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
// Only do so if we were not forced to wrap. If we did
|
||||
// force a word wrap, then the existing line break was
|
||||
// only because we ran out of space.
|
||||
if (iRight < cOldColsTotal && !charRow.WasWrapForced())
|
||||
if (iRight < cOldColsTotal && !row.WasWrapForced())
|
||||
{
|
||||
if (iRight == cOldCursorPos.X && iOldRow == cOldCursorPos.Y)
|
||||
{
|
||||
@@ -2180,7 +2185,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
const COORD coordNewCursor = newCursor.GetPosition();
|
||||
if (coordNewCursor.X == 0 && coordNewCursor.Y > 0)
|
||||
{
|
||||
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).GetCharRow().WasWrapForced())
|
||||
if (newBuffer.GetRowByOffset(gsl::narrow_cast<size_t>(coordNewCursor.Y) - 1).WasWrapForced())
|
||||
{
|
||||
hr = newBuffer.NewlineCursor() ? hr : E_OUTOFMEMORY;
|
||||
}
|
||||
@@ -2213,7 +2218,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
|
||||
// If the last row of the new buffer wrapped, there's going to be one less newline needed,
|
||||
// because the cursor is already on the next line
|
||||
if (newBuffer.GetRowByOffset(cNewLastChar.Y).GetCharRow().WasWrapForced())
|
||||
if (newBuffer.GetRowByOffset(cNewLastChar.Y).WasWrapForced())
|
||||
{
|
||||
iNewlines = std::max(iNewlines - 1, 0);
|
||||
}
|
||||
@@ -2221,7 +2226,7 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
||||
{
|
||||
// if this buffer didn't wrap, but the old one DID, then the d(columns) of the
|
||||
// old buffer will be one more than in this buffer, so new need one LESS.
|
||||
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).GetCharRow().WasWrapForced())
|
||||
if (oldBuffer.GetRowByOffset(cOldLastChar.Y).WasWrapForced())
|
||||
{
|
||||
iNewlines = std::max(iNewlines - 1, 0);
|
||||
}
|
||||
@@ -2410,8 +2415,8 @@ PointTree TextBuffer::GetPatterns(const size_t firstRow, const size_t lastRow) c
|
||||
// all the text into one string and find the patterns in that string
|
||||
for (auto i = firstRow; i <= lastRow; ++i)
|
||||
{
|
||||
auto row = GetRowByOffset(i);
|
||||
concatAll += row.GetCharRow().GetText();
|
||||
auto& row = GetRowByOffset(i);
|
||||
concatAll += row.GetText();
|
||||
}
|
||||
|
||||
// for each pattern we know of, iterate through the string
|
||||
|
||||
@@ -49,6 +49,8 @@ filling in the last row, and updating the screen.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cursor.h"
|
||||
#include "Row.hpp"
|
||||
#include "TextAttribute.hpp"
|
||||
@@ -190,7 +192,7 @@ public:
|
||||
private:
|
||||
void _UpdateSize();
|
||||
Microsoft::Console::Types::Viewport _size;
|
||||
std::deque<ROW> _storage;
|
||||
std::vector<ROW> _storage;
|
||||
Cursor _cursor;
|
||||
|
||||
SHORT _firstRow; // indexes top row (not necessarily 0)
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "CommonState.hpp"
|
||||
|
||||
#include "globals.h"
|
||||
#include "../buffer/out/textBuffer.hpp"
|
||||
|
||||
#include "input.h"
|
||||
#include "../textBuffer.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
@@ -170,64 +165,54 @@ class AttrRowTests
|
||||
_Inout_ std::unique_ptr<TextAttributeRun[]>& outAttrRun,
|
||||
_Out_ size_t* const cOutAttrRun)
|
||||
{
|
||||
NTSTATUS status = STATUS_SUCCESS;
|
||||
RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, cRowLength == 0);
|
||||
|
||||
if (cRowLength == 0)
|
||||
// first count up the deltas in the array
|
||||
size_t cDeltas = 1;
|
||||
|
||||
const TextAttribute* pPrevAttr = &rgAttrs[0];
|
||||
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
status = STATUS_BUFFER_TOO_SMALL;
|
||||
}
|
||||
const TextAttribute* pCurAttr = &rgAttrs[i];
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
// first count up the deltas in the array
|
||||
size_t cDeltas = 1;
|
||||
|
||||
const TextAttribute* pPrevAttr = &rgAttrs[0];
|
||||
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
if (*pCurAttr != *pPrevAttr)
|
||||
{
|
||||
const TextAttribute* pCurAttr = &rgAttrs[i];
|
||||
|
||||
if (*pCurAttr != *pPrevAttr)
|
||||
{
|
||||
cDeltas++;
|
||||
}
|
||||
|
||||
pPrevAttr = pCurAttr;
|
||||
cDeltas++;
|
||||
}
|
||||
|
||||
// This whole situation was too complicated with a one off holder for one row run
|
||||
// new method:
|
||||
// delete the old buffer
|
||||
// make a new buffer, one run + one run for each change
|
||||
// set the values for each run one run index at a time
|
||||
pPrevAttr = pCurAttr;
|
||||
}
|
||||
|
||||
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
|
||||
status = NT_TESTNULL(attrRun.get());
|
||||
if (NT_SUCCESS(status))
|
||||
// This whole situation was too complicated with a one off holder for one row run
|
||||
// new method:
|
||||
// delete the old buffer
|
||||
// make a new buffer, one run + one run for each change
|
||||
// set the values for each run one run index at a time
|
||||
|
||||
std::unique_ptr<TextAttributeRun[]> attrRun = std::make_unique<TextAttributeRun[]>(cDeltas);
|
||||
RETURN_HR_IF_NULL(E_OUTOFMEMORY, attrRun);
|
||||
|
||||
TextAttributeRun* pCurrentRun = attrRun.get();
|
||||
pCurrentRun->SetAttributes(rgAttrs[0]);
|
||||
pCurrentRun->SetLength(1);
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
if (pCurrentRun->GetAttributes() == rgAttrs[i])
|
||||
{
|
||||
TextAttributeRun* pCurrentRun = attrRun.get();
|
||||
pCurrentRun->SetAttributes(rgAttrs[0]);
|
||||
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pCurrentRun++;
|
||||
pCurrentRun->SetAttributes(rgAttrs[i]);
|
||||
pCurrentRun->SetLength(1);
|
||||
for (size_t i = 1; i < cRowLength; i++)
|
||||
{
|
||||
if (pCurrentRun->GetAttributes() == rgAttrs[i])
|
||||
{
|
||||
pCurrentRun->SetLength(pCurrentRun->GetLength() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
pCurrentRun++;
|
||||
pCurrentRun->SetAttributes(rgAttrs[i]);
|
||||
pCurrentRun->SetLength(1);
|
||||
}
|
||||
}
|
||||
attrRun.swap(outAttrRun);
|
||||
*cOutAttrRun = cDeltas;
|
||||
}
|
||||
}
|
||||
attrRun.swap(outAttrRun);
|
||||
*cOutAttrRun = cDeltas;
|
||||
|
||||
return HRESULT_FROM_NT(status);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
NoThrowString LogRunElement(_In_ TextAttributeRun& run)
|
||||
@@ -235,6 +220,24 @@ class AttrRowTests
|
||||
return NoThrowString().Format(L"%wc%d", run.GetAttributes().GetLegacyAttributes(), run.GetLength());
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
boost::container::small_vector_base<TextAttributeRun>& chain)
|
||||
{
|
||||
NoThrowString str(pwszPrefix);
|
||||
|
||||
if (chain.size() > 0)
|
||||
{
|
||||
str.Append(LogRunElement(chain[0]));
|
||||
|
||||
for (size_t i = 1; i < chain.size(); i++)
|
||||
{
|
||||
str.AppendFormat(L"->%s", (const wchar_t*)(LogRunElement(chain[i])));
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(str);
|
||||
}
|
||||
|
||||
void LogChain(_In_ PCWSTR pwszPrefix,
|
||||
std::vector<TextAttributeRun>& chain)
|
||||
{
|
||||
@@ -707,10 +710,6 @@ class AttrRowTests
|
||||
|
||||
TEST_METHOD(TestResize)
|
||||
{
|
||||
CommonState state;
|
||||
state.PrepareGlobalFont();
|
||||
state.PrepareGlobalScreenBuffer();
|
||||
|
||||
pSingle->Resize(240);
|
||||
pChain->Resize(240);
|
||||
|
||||
@@ -728,8 +727,5 @@ class AttrRowTests
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(pSingle->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
VERIFY_THROWS_SPECIFIC(pChain->Resize(0), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_INVALIDARG; });
|
||||
|
||||
state.CleanupGlobalScreenBuffer();
|
||||
state.CleanupGlobalFont();
|
||||
}
|
||||
};
|
||||
856
src/buffer/out/ut_textbuffer/ReflowTests.cpp
Normal file
856
src/buffer/out/ut_textbuffer/ReflowTests.cpp
Normal file
@@ -0,0 +1,856 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "../textBuffer.hpp"
|
||||
#include "../../renderer/inc/DummyRenderTarget.hpp"
|
||||
#include "../../types/inc/Utf16Parser.hpp"
|
||||
#include "../../types/inc/GlyphWidth.hpp"
|
||||
|
||||
#include <IDataSource.h>
|
||||
|
||||
template<>
|
||||
class WEX::TestExecution::VerifyOutputTraits<wchar_t>
|
||||
{
|
||||
public:
|
||||
static WEX::Common::NoThrowString ToString(const wchar_t& wch)
|
||||
{
|
||||
return WEX::Common::NoThrowString().Format(L"'%c'", wch);
|
||||
}
|
||||
};
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct TestRow
|
||||
{
|
||||
std::wstring_view text;
|
||||
bool wrap;
|
||||
};
|
||||
|
||||
struct TestBuffer
|
||||
{
|
||||
COORD size;
|
||||
std::vector<TestRow> rows;
|
||||
COORD cursor;
|
||||
};
|
||||
|
||||
struct TestCase
|
||||
{
|
||||
std::wstring_view name;
|
||||
std::vector<TestBuffer> buffers;
|
||||
};
|
||||
|
||||
static constexpr auto true_due_to_exact_wrap_bug{ true };
|
||||
|
||||
static const TestCase testCases[] = {
|
||||
TestCase{
|
||||
L"No reflow required",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"AB ", false },
|
||||
{ L"$ ", false },
|
||||
{ L"CD ", false },
|
||||
{ L"EFG ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"AB ", false },
|
||||
{ L"$ ", false },
|
||||
{ L"CD ", false },
|
||||
{ L"EFG ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 4, 5 },
|
||||
{
|
||||
{ L"AB ", false },
|
||||
{ L"$ ", false },
|
||||
{ L"CD ", false },
|
||||
{ L"EFG ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor remains in buffer, no circling, no original wrap",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
{ L"$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"ABCDE", true },
|
||||
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 1, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // grow width back to original
|
||||
{
|
||||
{ L"ABCDEF", true_due_to_exact_wrap_bug },
|
||||
{ L"$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 7, 5 }, // grow width wider than original
|
||||
{
|
||||
{ L"ABCDEF$", true_due_to_exact_wrap_bug }, // EXACT WRAP BUG: $ should be alone on next line
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 6, 0 } // cursor on $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor remains in buffer, no circling, with original wrap",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
{ L"G$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 1, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"ABCDE", true },
|
||||
{ L"FG$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 2, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // grow width back to original
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
{ L"G$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 1, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 7, 5 }, // grow width wider than original
|
||||
{
|
||||
{ L"ABCDEFG", true },
|
||||
{ L"$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS line padded with spaces (to wrap)",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"AB ", true }, // AB $ CD is one long wrapped line
|
||||
{ L"$ ", true },
|
||||
{ L"CD ", false },
|
||||
{ L"EFG ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 7, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"AB $", true },
|
||||
{ L" CD", true_due_to_exact_wrap_bug },
|
||||
{ L" ", false },
|
||||
{ L"EFG ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 6, 0 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 8, 5 },
|
||||
{
|
||||
{ L"AB $ ", true },
|
||||
{ L" CD ", false }, // Goes to false because we hit the end of ..CD
|
||||
{ L"EFG ", false }, // [BUG] EFG moves up due to exact wrap bug above
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 6, 0 } // cursor on $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"DBCS, cursor remains in buffer, no circling, with original wrap",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
//--0123456--
|
||||
{ L"カタカ", true }, // KA TA KA
|
||||
{ L"ナ$ ", false }, // NA
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 2, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
//--012345--
|
||||
{ L"カタ ", true }, // KA TA [FORCED SPACER]
|
||||
{ L"カナ$", true_due_to_exact_wrap_bug }, // KA NA
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 4, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // grow width back to original
|
||||
{
|
||||
//--0123456--
|
||||
{ L"カタカ", true }, // KA TA KA
|
||||
{ L"ナ$ ", false }, // NA
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 2, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 7, 5 }, // grow width wider than original (by one; no visible change!)
|
||||
{
|
||||
//--0123456--
|
||||
{ L"カタカ ", true }, // KA TA KA [FORCED SPACER]
|
||||
{ L"ナ$ ", false }, // NA
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 2, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 8, 5 }, // grow width enough to fit second DBCS
|
||||
{
|
||||
//--01234567--
|
||||
{ L"カタカナ", true }, // KA TA KA NA
|
||||
{ L"$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor remains in buffer, with circling, no original wrap",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
{ L"$ ", false },
|
||||
{ L"GHIJKL", false },
|
||||
{ L"MNOPQR", false },
|
||||
{ L"STUVWX", false },
|
||||
},
|
||||
{ 0, 1 } // cursor on $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"F$ ", false },
|
||||
{ L"GHIJK", true }, // [BUG] We should see GHIJK\n L\n MNOPQ\n R\n
|
||||
{ L"LMNOP", true }, // The wrapping here is irregular
|
||||
{ L"QRSTU", true },
|
||||
{ L"VWX ", false },
|
||||
},
|
||||
{ 1, 1 } // [BUG] cursor moves to 1,1 instead of sticking with the $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // going back to 6,5, the data lost has been destroyed
|
||||
{
|
||||
//{ L"F$ ", false }, // [BUG] this line is erroneously destroyed too!
|
||||
{ L"GHIJKL", true },
|
||||
{ L"MNOPQR", true },
|
||||
{ L"STUVWX", true },
|
||||
{ L" ", false },
|
||||
{ L" ", false }, // [BUG] this line is added
|
||||
},
|
||||
{ 1, 1 }, // [BUG] cursor does not follow [H], it sticks at 1,1
|
||||
},
|
||||
TestBuffer{
|
||||
{ 7, 5 }, // a number of errors are carried forward from the previous buffer
|
||||
{
|
||||
{ L"GHIJKLM", true },
|
||||
{ L"NOPQRST", true },
|
||||
{ L"UVWX ", false }, // [BUG] This line loses wrap for some reason
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 0, 1 }, // [BUG] The cursor moves to 0, 1 now, sticking with the [N] from before
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
// The cursor is not found during character insertion.
|
||||
// Instead, it is found off the right edge of the text. This triggers
|
||||
// a separate cursor found codepath in the original algorithm.
|
||||
L"SBCS, cursor off rightmost char in non-wrapped line",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
{ L"$ ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 1, 1 } // cursor *after* $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"ABCDE", true },
|
||||
{ L"F$ ", false }, // [BUG] EXACT WRAP. $ should be alone on next line.
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 2, 1 } // cursor follows space after $ to next line
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off rightmost char in wrapped line, which is then pushed off bottom",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
{ L"GHIJKL", true },
|
||||
{ L"MNOPQR", true },
|
||||
{ L"STUVWX", true },
|
||||
{ L"YZ0 $ ", false },
|
||||
},
|
||||
{ 5, 4 } // cursor *after* $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"FGHIJ", true },
|
||||
{ L"KLMNO", true },
|
||||
{ L"PQRST", true },
|
||||
{ L"UVWXY", true },
|
||||
{ L"Z0 $ ", false },
|
||||
},
|
||||
{ 4, 4 } // cursor follows space after $ to newly introduced bottom line
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off in space to far right of text (end of buffer content)",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
// v cursor
|
||||
{ L"$ ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"ABCDE", true },
|
||||
{ L"F$ ", true }, // [BUG] This line is marked wrapped, and I do not know why
|
||||
// v cursor
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 1, 2 } // cursor stays same linear distance from $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // grow back to original size
|
||||
{
|
||||
{ L"ABCDEF", true_due_to_exact_wrap_bug },
|
||||
// v cursor [BUG] cursor does not retain linear distance from $
|
||||
{ L"$ ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 4, 1 } // cursor stays same linear distance from $
|
||||
},
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off in space to far right of text (middle of buffer content)",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
// v cursor
|
||||
{ L"$ ", false },
|
||||
// ^ cursor
|
||||
{ L"BLAH ", false },
|
||||
{ L"BLAH ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 5, 5 }, // reduce width by 1
|
||||
{
|
||||
{ L"ABCDE", true },
|
||||
{ L"F$ ", false },
|
||||
{ L"BLAH ", false },
|
||||
{ L"BLAH ", true }, // [BUG] this line wraps, no idea why
|
||||
// v cursor [BUG] cursor erroneously moved to end of all content
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 0, 4 } },
|
||||
TestBuffer{
|
||||
{ 6, 5 }, // grow back to original size
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
{ L"$ ", false },
|
||||
{ L"BLAH ", false },
|
||||
// v cursor [BUG] cursor is pulled up to previous line because it was marked wrapped
|
||||
{ L"BLAH ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 5, 3 } },
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
// Shrinking the buffer this much forces a multi-line wrap before the cursor
|
||||
L"SBCS, cursor off in space to far right of text (end of buffer content), aggressive shrink",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
// v cursor
|
||||
{ L"$ ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
{ L" ", false },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 2, 5 }, // reduce width aggressively
|
||||
{
|
||||
{ L"CD", true },
|
||||
{ L"EF", true },
|
||||
{ L"$ ", true },
|
||||
{ L" ", true },
|
||||
// v cursor
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 1, 4 } },
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off in space to far right of text (end of buffer content), fully wrapped, aggressive shrink",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
// v cursor
|
||||
{ L"$ ", true },
|
||||
// ^ cursor
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 2, 5 }, // reduce width aggressively
|
||||
{
|
||||
{ L"EF", true },
|
||||
{ L"$ ", true },
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
// v cursor [BUG] cursor does not maintain linear distance from $
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 1, 4 } },
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off in space to far right of text (middle of buffer content), fully wrapped, aggressive shrink",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", true },
|
||||
// v cursor
|
||||
{ L"$ ", true },
|
||||
// ^ cursor
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" Q", true },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 2, 5 }, // reduce width aggressively
|
||||
{
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" Q", true },
|
||||
// v cursor [BUG] cursor jumps to end of world
|
||||
{ L" ", false }, // POTENTIAL [BUG] a whole new blank line is added for the cursor
|
||||
// ^ cursor
|
||||
},
|
||||
{ 1, 4 } },
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
L"SBCS, cursor off in space to far right of text (middle of buffer content), partially wrapped, aggressive shrink",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
// v cursor
|
||||
{ L"$ ", false },
|
||||
// ^ cursor
|
||||
{ L" ", false },
|
||||
{ L" ", true },
|
||||
{ L" Q", true },
|
||||
},
|
||||
{ 5, 1 } // cursor in space far after $
|
||||
},
|
||||
TestBuffer{
|
||||
{ 2, 5 }, // reduce width aggressively
|
||||
{
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" Q", true },
|
||||
// v cursor [BUG] cursor jumps to different place than fully wrapped case
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 0, 4 } },
|
||||
},
|
||||
},
|
||||
TestCase{
|
||||
// This triggers the cursor being walked forward w/ newlines to maintain
|
||||
// distance from the last char in the buffer
|
||||
L"SBCS, cursor at end of buffer, otherwise same as previous test",
|
||||
{
|
||||
TestBuffer{
|
||||
{ 6, 5 },
|
||||
{
|
||||
{ L"ABCDEF", false },
|
||||
{ L"$ ", false },
|
||||
{ L" Q", true },
|
||||
{ L" ", true },
|
||||
// v cursor
|
||||
{ L" ", true },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 5, 4 } // cursor at end of buffer
|
||||
},
|
||||
TestBuffer{
|
||||
{ 2, 5 }, // reduce width aggressively
|
||||
{
|
||||
{ L" ", true },
|
||||
{ L" ", true },
|
||||
{ L" Q", true },
|
||||
{ L" ", false },
|
||||
// v cursor [BUG] cursor loses linear distance from Q; is this important?
|
||||
{ L" ", false },
|
||||
// ^ cursor
|
||||
},
|
||||
{ 0, 4 } },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#pragma region TAEF hookup for the test case array above
|
||||
struct ArrayIndexTaefAdapterRow : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataRow>
|
||||
{
|
||||
HRESULT RuntimeClassInitialize(const size_t index)
|
||||
{
|
||||
_index = index;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestData(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
const auto indexString{ wil::str_printf<std::wstring>(L"%zu", _index) };
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto indexBstr{ wil::make_bstr(indexString.c_str()) };
|
||||
(void)SafeArrayPutElement(safeArray, &index, indexBstr.release());
|
||||
*ppData = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadataNames(SAFEARRAY** ppMetadataNames) override
|
||||
{
|
||||
*ppMetadataNames = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetMetadata(BSTR /*pszName*/, SAFEARRAY** ppData) override
|
||||
{
|
||||
*ppData = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetName(BSTR* ppszRowName) override
|
||||
{
|
||||
*ppszRowName = nullptr;
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index;
|
||||
};
|
||||
|
||||
struct ArrayIndexTaefAdapterSource : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom | Microsoft::WRL::InhibitFtmBase>, IDataSource>
|
||||
{
|
||||
STDMETHODIMP Advance(IDataRow** ppDataRow) override
|
||||
{
|
||||
if (_index < std::extent<decltype(testCases)>::value)
|
||||
{
|
||||
Microsoft::WRL::MakeAndInitialize<ArrayIndexTaefAdapterRow>(ppDataRow, _index++);
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppDataRow = nullptr;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP Reset() override
|
||||
{
|
||||
_index = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataNames(SAFEARRAY** names) override
|
||||
{
|
||||
auto safeArray{ SafeArrayCreateVector(VT_BSTR, 0, 1) };
|
||||
LONG index{ 0 };
|
||||
auto dataNameBstr{ wil::make_bstr(L"index") };
|
||||
(void)SafeArrayPutElement(safeArray, &index, dataNameBstr.release());
|
||||
*names = safeArray;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP GetTestDataType(BSTR /*name*/, BSTR* type) override
|
||||
{
|
||||
*type = nullptr;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _index{ 0 };
|
||||
};
|
||||
#pragma endregion
|
||||
}
|
||||
|
||||
extern "C" HRESULT __declspec(dllexport) __cdecl ReflowTestDataSource(IDataSource** ppDataSource, void*)
|
||||
{
|
||||
auto source{ Microsoft::WRL::Make<ArrayIndexTaefAdapterSource>() };
|
||||
return source.CopyTo(ppDataSource);
|
||||
}
|
||||
|
||||
class ReflowTests
|
||||
{
|
||||
TEST_CLASS(ReflowTests);
|
||||
|
||||
static DummyRenderTarget target;
|
||||
static std::unique_ptr<TextBuffer> _textBufferFromTestBuffer(const TestBuffer& testBuffer)
|
||||
{
|
||||
auto buffer = std::make_unique<TextBuffer>(testBuffer.size, TextAttribute{ 0x7 }, 0, target);
|
||||
|
||||
size_t i{};
|
||||
for (const auto& testRow : testBuffer.rows)
|
||||
{
|
||||
auto& row{ buffer->GetRowByOffset(i) };
|
||||
|
||||
auto& charRow{ row.GetCharRow() };
|
||||
row.SetWrapForced(testRow.wrap);
|
||||
|
||||
size_t j{};
|
||||
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
|
||||
{
|
||||
// Yes, we're about to manually create a buffer. It is unpleasant.
|
||||
const auto ch{ til::at(testRow.text, j) };
|
||||
it->Char() = ch;
|
||||
if (IsGlyphFullWidth(ch))
|
||||
{
|
||||
it->DbcsAttr().SetLeading();
|
||||
it++;
|
||||
it->Char() = ch;
|
||||
it->DbcsAttr().SetTrailing();
|
||||
}
|
||||
else
|
||||
{
|
||||
it->DbcsAttr().SetSingle();
|
||||
}
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
buffer->GetCursor().SetPosition(testBuffer.cursor);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static std::unique_ptr<TextBuffer> _textBufferByReflowingTextBuffer(TextBuffer& originalBuffer, const COORD newSize)
|
||||
{
|
||||
auto buffer = std::make_unique<TextBuffer>(newSize, TextAttribute{ 0x7 }, 0, target);
|
||||
TextBuffer::Reflow(originalBuffer, *buffer, std::nullopt, std::nullopt);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void _compareTextBufferAgainstTestBuffer(const TextBuffer& buffer, const TestBuffer& testBuffer)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(testBuffer.cursor, buffer.GetCursor().GetPosition());
|
||||
VERIFY_ARE_EQUAL(testBuffer.size, buffer.GetSize().Dimensions());
|
||||
|
||||
size_t i{};
|
||||
for (const auto& testRow : testBuffer.rows)
|
||||
{
|
||||
NoThrowString indexString;
|
||||
const auto& row{ buffer.GetRowByOffset(i) };
|
||||
|
||||
const auto& charRow{ row.GetCharRow() };
|
||||
|
||||
indexString.Format(L"[Row %d]", i);
|
||||
VERIFY_ARE_EQUAL(testRow.wrap, row.WasWrapForced(), indexString);
|
||||
|
||||
size_t j{};
|
||||
for (auto it{ charRow.begin() }; it != charRow.end(); ++it)
|
||||
{
|
||||
indexString.Format(L"[Cell %d, %d; Text line index %d]", it - charRow.begin(), i, j);
|
||||
// Yes, we're about to manually create a buffer. It is unpleasant.
|
||||
const auto ch{ til::at(testRow.text, j) };
|
||||
if (IsGlyphFullWidth(ch))
|
||||
{
|
||||
// Char is full width in test buffer, so
|
||||
// ensure that real buffer is LEAD, TRAIL (ch)
|
||||
VERIFY_IS_TRUE(it->DbcsAttr().IsLeading(), indexString);
|
||||
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
|
||||
|
||||
it++;
|
||||
VERIFY_IS_TRUE(it->DbcsAttr().IsTrailing(), indexString);
|
||||
}
|
||||
else
|
||||
{
|
||||
VERIFY_IS_TRUE(it->DbcsAttr().IsSingle(), indexString);
|
||||
}
|
||||
|
||||
VERIFY_ARE_EQUAL(ch, it->Char(), indexString);
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(TestReflowCases)
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"DataSource", L"Export:ReflowTestDataSource")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
WEX::TestExecution::DisableVerifyExceptions disableVerifyExceptions{};
|
||||
WEX::TestExecution::SetVerifyOutput verifyOutputScope{ WEX::TestExecution::VerifyOutputSettings::LogOnlyFailures };
|
||||
|
||||
unsigned int i{};
|
||||
TestData::TryGetValue(L"index", i); // index is produced by the ArrayIndexTaefAdapterSource above
|
||||
const auto& testCase{ testCases[i] };
|
||||
Log::Comment(NoThrowString().Format(L"[%zu.0] Test case \"%.*s\"", i, testCase.name.size(), testCase.name.data()));
|
||||
|
||||
// Create initial text buffer from Buffer 0
|
||||
auto textBuffer{ _textBufferFromTestBuffer(testCase.buffers.front()) };
|
||||
for (size_t bufferIndex{ 1 }; bufferIndex < testCase.buffers.size(); ++bufferIndex)
|
||||
{
|
||||
const auto& testBuffer{ til::at(testCase.buffers, bufferIndex) };
|
||||
Log::Comment(NoThrowString().Format(L"[%zu.%zu] Resizing to %dx%d", i, bufferIndex, testBuffer.size.X, testBuffer.size.Y));
|
||||
|
||||
auto newBuffer{ _textBufferByReflowingTextBuffer(*textBuffer, testBuffer.size) };
|
||||
|
||||
// All future operations are based on the new buffer
|
||||
std::swap(textBuffer, newBuffer);
|
||||
|
||||
_compareTextBufferAgainstTestBuffer(*textBuffer, testBuffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
DummyRenderTarget ReflowTests::target{};
|
||||
@@ -10,6 +10,8 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AttrRowTests.cpp" />
|
||||
<ClCompile Include="ReflowTests.cpp" />
|
||||
<ClCompile Include="TextColorTests.cpp" />
|
||||
<ClCompile Include="TextAttributeTests.cpp" />
|
||||
<ClCompile Include="UnicodeStorageTests.cpp" />
|
||||
@@ -18,6 +20,9 @@
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\types\lib\types.vcxproj">
|
||||
<Project>{18d09a24-8240-42d6-8cb6-236eee820263}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\lib\bufferout.vcxproj">
|
||||
<Project>{0cf235bd-2da0-407e-90ee-c467e8bbc714}</Project>
|
||||
</ProjectReference>
|
||||
|
||||
@@ -14,12 +14,15 @@ DLLDEF =
|
||||
|
||||
SOURCES = \
|
||||
$(SOURCES) \
|
||||
AttrRowTests.cpp \
|
||||
ReflowTests.cpp \
|
||||
TextColorTests.cpp \
|
||||
TextAttributeTests.cpp \
|
||||
DefaultResource.rc \
|
||||
|
||||
TARGETLIBS = \
|
||||
$(CONSOLE_OBJ_PATH)\buffer\out\lib\$(O)\ConBufferOut.lib \
|
||||
$(CONSOLE_OBJ_PATH)\types\lib\$(O)\ConTypes.lib \
|
||||
$(TARGETLIBS) \
|
||||
|
||||
# -------------------------------------
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\wap-common.build.pre.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<!-- This is necessary so the build system doesn't think we're a .NET project... -->
|
||||
<TargetRuntime>Native</TargetRuntime>
|
||||
|
||||
<TargetPlatformVersion>10.0.18362.0</TargetPlatformVersion>
|
||||
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
|
||||
<!--
|
||||
@@ -17,6 +16,10 @@
|
||||
<AppxOSMaxVersionTestedReplaceManifestVersion>false</AppxOSMaxVersionTestedReplaceManifestVersion>
|
||||
<OCExecutionAliasName Condition="'$(WindowsTerminalBranding)'==''">wtd</OCExecutionAliasName>
|
||||
<OCExecutionAliasName Condition="'$(OCExecutionAliasName)'==''">wt</OCExecutionAliasName>
|
||||
<!-- VS 16.8 causes WAP projects to accidentally package System.Core.dll (from the CLR).
|
||||
This has been fixed in VS 16.9 using the property below. It's safe for us to include
|
||||
it here, even after 16.9 comes out. -->
|
||||
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>CA5CAD1A-224A-4171-B13A-F16E576FDD12</ProjectGuid>
|
||||
|
||||
@@ -82,14 +82,9 @@
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
<!-- Due to a bug in the OS, this doesn't actually work right -
|
||||
we'll get a nullptr in our implementation. So this is disabled
|
||||
temporarily. See MSFT:24623699 for more details.
|
||||
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
-->
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
|
||||
@@ -82,15 +82,9 @@
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
<!-- Due to a bug in the OS, this doesn't actually work right -
|
||||
we'll get a nullptr in our implementation. So this is disabled
|
||||
temporarily. See MSFT:24623699 for more details.
|
||||
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
-->
|
||||
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
|
||||
@@ -83,14 +83,9 @@
|
||||
<desktop5:ItemType Type="Directory">
|
||||
<desktop5:Verb Id="Command1" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
<!-- Due to a bug in the OS, this doesn't actually work right -
|
||||
we'll get a nullptr in our implementation. So this is disabled
|
||||
temporarily. See MSFT:24623699 for more details.
|
||||
|
||||
<desktop5:ItemType Type="Directory\Background">
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
<desktop5:Verb Id="Command2" Clsid="9f156763-7844-4dc4-b2b1-901f640f5155" />
|
||||
</desktop5:ItemType>
|
||||
-->
|
||||
</desktop4:FileExplorerContextMenus>
|
||||
</desktop4:Extension>
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{2658e9b0-bd84-48b7-bb5e-3d18f4df07ba}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>Microsoft.Terminal.CommandlineArgs</RootNamespace>
|
||||
<ProjectName>CommandlineArgs</ProjectName>
|
||||
<TargetName>CommandlineArgs</TargetName>
|
||||
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
<ClCompile>
|
||||
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
|
||||
on being compiled with RTTI (/GR). -->
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="AppCommandlineArgs.h" />
|
||||
<ClInclude Include="Commandline.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Cpp Files ======================== -->
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="Commandline.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<!-- ========================= idl Files ======================== -->
|
||||
<ItemGroup>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<!--
|
||||
the packaging project won't recurse through our dependencies, you have to
|
||||
make sure that if you add a cppwinrt dependency to any of these projects,
|
||||
you also update all the consumers
|
||||
-->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
|
||||
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<!-- For whatever reason, we can't include the TerminalControl and
|
||||
TerminalSettings projects' winmds via project references. So we'll have to
|
||||
manually include the winmds as References below -->
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- This is a hack to get the ARM64 CI build working. See
|
||||
https://github.com/Microsoft/msbuild/issues/3746 - it looks like MsBuild
|
||||
just has a bug in it.-->
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>Warning</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Manually add references to each of our dependent winmds. Mark them as
|
||||
private=false and CopyLocalSatelliteAssemblies=false, so that we don't
|
||||
propagate them upwards (which can make referencing this project result in
|
||||
duplicate type definitions)-->
|
||||
|
||||
<!-- Despite this lib only _really_ depending on the settings model, we need
|
||||
to reference Connection, Control, and MUX here, because they have types that
|
||||
TSM depends on, and the midl compiler will be mad if it can't find them. -->
|
||||
|
||||
<Reference Include="Microsoft.Terminal.TerminalConnection">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Terminal.TerminalControl">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)TerminalControl\Microsoft.Terminal.TerminalControl.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Terminal.Settings.Model">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)Microsoft.Terminal.Settings.Model\Microsoft.Terminal.Settings.Model.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<AdditionalIncludeDirectories>..;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
|
||||
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<!-- ========================= Globals ======================== -->
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.5.0-prerelease.201202003\build\native\Microsoft.UI.Xaml.targets'))" />
|
||||
</Target>
|
||||
<!-- <Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" /> -->
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
@@ -1,53 +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
|
||||
#define NOMCX
|
||||
#define NOHELP
|
||||
#define NOCOMM
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#define BLOCK_TIL
|
||||
#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 <wil/cppwinrt.h>
|
||||
|
||||
#include <unknwn.h>
|
||||
|
||||
#include <hstring.h>
|
||||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
// You might think "we're not doing any UI in this lib", and you'd be right.
|
||||
// However, we need this for Windows.UI.Color
|
||||
#include <winrt/windows.ui.h>
|
||||
|
||||
// Including TraceLogging essentials for the binary
|
||||
#include <TraceLoggingProvider.h>
|
||||
#include <winmeta.h>
|
||||
TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
|
||||
#include <telemetry/ProjectTelemetry.h>
|
||||
#include <TraceLoggingActivity.h>
|
||||
|
||||
#include <shellapi.h>
|
||||
#include <shobjidl_core.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.Settings.Model.h>
|
||||
|
||||
#include <CLI11/CLI11.hpp>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
@@ -35,6 +35,7 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(CanLayerColorScheme);
|
||||
TEST_METHOD(LayerColorSchemeProperties);
|
||||
TEST_METHOD(LayerColorSchemesOnArray);
|
||||
TEST_METHOD(UpdateSchemeReferences);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
@@ -290,4 +291,81 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(ARGB(0, 7, 7, 7), scheme2->_Background);
|
||||
}
|
||||
}
|
||||
|
||||
void ColorSchemeTests::UpdateSchemeReferences()
|
||||
{
|
||||
const std::string settingsString{ R"json({
|
||||
"defaultProfile": "Inherited reference",
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
"list": [
|
||||
{
|
||||
"name": "Explicit scheme reference",
|
||||
"colorScheme": "Scheme 1"
|
||||
},
|
||||
{
|
||||
"name": "Explicit reference; hidden",
|
||||
"colorScheme": "Scheme 1",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "Inherited reference"
|
||||
},
|
||||
{
|
||||
"name": "Different reference",
|
||||
"colorScheme": "Scheme 2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"schemes": [
|
||||
{ "name": "Scheme 1" },
|
||||
{ "name": "Scheme 2" },
|
||||
{ "name": "Scheme 1 (renamed)" }
|
||||
]
|
||||
})json" };
|
||||
|
||||
auto settings{ winrt::make_self<CascadiaSettings>(false) };
|
||||
settings->_ParseJsonString(settingsString, false);
|
||||
settings->_ApplyDefaultsFromUserSettings();
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
// update all references to "Scheme 1"
|
||||
const auto newName{ L"Scheme 1 (renamed)" };
|
||||
settings->UpdateColorSchemeReferences(L"Scheme 1", newName);
|
||||
|
||||
// verify profile defaults
|
||||
Log::Comment(L"Profile Defaults");
|
||||
VERIFY_ARE_EQUAL(newName, settings->ProfileDefaults().ColorSchemeName());
|
||||
VERIFY_IS_TRUE(settings->ProfileDefaults().HasColorSchemeName());
|
||||
|
||||
// verify all other profiles
|
||||
const auto& profiles{ settings->AllProfiles() };
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(0) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(1) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(2) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(newName, prof.ColorSchemeName());
|
||||
VERIFY_IS_FALSE(prof.HasColorSchemeName());
|
||||
}
|
||||
{
|
||||
const auto& prof{ profiles.GetAt(3) };
|
||||
Log::Comment(prof.Name().c_str());
|
||||
VERIFY_ARE_EQUAL(L"Scheme 2", prof.ColorSchemeName());
|
||||
VERIFY_IS_TRUE(prof.HasColorSchemeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(ManyCommandsSameAction);
|
||||
TEST_METHOD(LayerCommand);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
TEST_METHOD(TestSplitPaneBadSize);
|
||||
TEST_METHOD(TestResourceKeyName);
|
||||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
@@ -147,7 +148,8 @@ namespace SettingsModelLocalTests
|
||||
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "name": "command4", "command": { "action": "splitPane" } },
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } }
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "name": "command6", "command": { "action": "splitPane", "size": 0.25 } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
@@ -156,7 +158,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(4u, commands.Size());
|
||||
VERIFY_ARE_EQUAL(5u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"command1");
|
||||
@@ -167,6 +169,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command2");
|
||||
@@ -177,6 +180,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command4");
|
||||
@@ -187,6 +191,7 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command5");
|
||||
@@ -197,8 +202,51 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5, realArgs.SplitSize());
|
||||
}
|
||||
{
|
||||
auto command = commands.Lookup(L"command6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestSplitPaneBadSize()
|
||||
{
|
||||
const std::string commands0String{ R"([
|
||||
{ "name": "command1", "command": { "action": "splitPane", "size": 0.25 } },
|
||||
{ "name": "command2", "command": { "action": "splitPane", "size": 1.0 } },
|
||||
{ "name": "command3", "command": { "action": "splitPane", "size": 0 } },
|
||||
{ "name": "command4", "command": { "action": "splitPane", "size": 50 } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
IMap<winrt::hstring, Command> commands = winrt::single_threaded_map<winrt::hstring, Command>();
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(3u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.Size());
|
||||
|
||||
{
|
||||
auto command = commands.Lookup(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.25, realArgs.SplitSize());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestResourceKeyName()
|
||||
{
|
||||
// This test checks looking up a name from a resource key.
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace SettingsModelLocalTests
|
||||
TEST_METHOD(TestCommandsAndKeybindings);
|
||||
|
||||
TEST_METHOD(TestNestedCommandWithoutName);
|
||||
TEST_METHOD(TestNestedCommandWithBadSubCommands);
|
||||
TEST_METHOD(TestUnbindNestedCommand);
|
||||
TEST_METHOD(TestRebindNestedCommand);
|
||||
|
||||
@@ -1376,7 +1377,7 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
{
|
||||
"name": "parent",
|
||||
"commands": [
|
||||
"commands": [
|
||||
{ "command": { "action": "setColorScheme", "colorScheme": "invalidScheme" } }
|
||||
]
|
||||
}
|
||||
@@ -1403,11 +1404,11 @@ namespace SettingsModelLocalTests
|
||||
},
|
||||
{
|
||||
"name": "grandparent",
|
||||
"commands": [
|
||||
"commands": [
|
||||
{
|
||||
"name": "parent",
|
||||
"commands": [
|
||||
{
|
||||
{
|
||||
"command": { "action": "setColorScheme", "colorScheme": "invalidScheme" }
|
||||
}
|
||||
]
|
||||
@@ -1909,7 +1910,8 @@ namespace SettingsModelLocalTests
|
||||
"keybindings": [
|
||||
{ "command": { "action": "splitPane", "split":"auto" }, "keys": [ "ctrl+alt+t", "ctrl+a" ] },
|
||||
{ "command": { "action": "moveFocus" }, "keys": [ "ctrl+a" ] },
|
||||
{ "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] }
|
||||
{ "command": { "action": "resizePane" }, "keys": [ "ctrl+b" ] },
|
||||
{ "name": "invalid nested", "commands":[ { "name" : "hello" }, { "name" : "world" } ] }
|
||||
]
|
||||
})" };
|
||||
|
||||
@@ -1918,18 +1920,20 @@ namespace SettingsModelLocalTests
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size());
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings->_globals->_keybindingsWarnings.size());
|
||||
VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size());
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(1));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_globals->_keybindingsWarnings.at(2));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_globals->_keybindingsWarnings.at(3));
|
||||
|
||||
settings->_ValidateKeybindings();
|
||||
|
||||
VERIFY_ARE_EQUAL(4u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(5u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_warnings.GetAt(1));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(2));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.GetAt(3));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(4));
|
||||
}
|
||||
|
||||
void DeserializationTests::ValidateExecuteCommandlineWarning()
|
||||
@@ -2316,6 +2320,53 @@ namespace SettingsModelLocalTests
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestNestedCommandWithBadSubCommands()
|
||||
{
|
||||
// This test tests a nested command without a name specified. This type
|
||||
// of command should just be ignored, since we can't auto-generate names
|
||||
// for nested commands, they _must_ have names specified.
|
||||
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "nested command",
|
||||
"commands": [
|
||||
{
|
||||
"name": "child1"
|
||||
},
|
||||
{
|
||||
"name": "child2"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
"schemes": [ { "name": "Campbell" } ] // This is included here to prevent settings validation errors.
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
|
||||
auto settings = winrt::make_self<implementation::CascadiaSettings>();
|
||||
settings->_ParseJsonString(settingsJson, false);
|
||||
settings->LayerJson(settings->_userSettings);
|
||||
auto commands = settings->_globals->Commands();
|
||||
settings->_ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings->_warnings.Size());
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0));
|
||||
VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1));
|
||||
VERIFY_ARE_EQUAL(0u, commands.Size());
|
||||
}
|
||||
|
||||
void DeserializationTests::TestUnbindNestedCommand()
|
||||
{
|
||||
// Test that layering a command with `"commands": null` set will unbind a command that already exists.
|
||||
|
||||
@@ -226,6 +226,7 @@ namespace SettingsModelLocalTests
|
||||
const std::string settingsString{ R"({
|
||||
"$schema": "https://aka.ms/terminal-profiles-schema",
|
||||
"defaultProfile": "{61c54bbd-1111-5271-96e7-009a87ff44bf}",
|
||||
"disabledProfileSources": [ "Windows.Terminal.Wsl" ],
|
||||
|
||||
"profiles": {
|
||||
"defaults": {
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(ParseSplitPaneIntoArgs);
|
||||
TEST_METHOD(ParseComboCommandlineIntoArgs);
|
||||
TEST_METHOD(ParseFocusTabArgs);
|
||||
TEST_METHOD(ParseMoveFocusArgs);
|
||||
TEST_METHOD(ParseArgumentsWithParsingTerminators);
|
||||
|
||||
TEST_METHOD(ParseNoCommandIsNewTab);
|
||||
@@ -61,6 +62,8 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(TestLaunchMode);
|
||||
TEST_METHOD(TestLaunchModeWithNoCommand);
|
||||
|
||||
TEST_METHOD(TestMultipleSplitPaneSizes);
|
||||
|
||||
private:
|
||||
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
|
||||
const size_t expectedSubcommands,
|
||||
@@ -76,6 +79,23 @@ namespace TerminalAppLocalTests
|
||||
appArgs.ValidateStartupCommands();
|
||||
}
|
||||
|
||||
void _buildCommandlinesExpectFailureHelper(AppCommandlineArgs& appArgs,
|
||||
const size_t expectedSubcommands,
|
||||
std::vector<const wchar_t*>& rawCommands)
|
||||
{
|
||||
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
|
||||
VERIFY_ARE_EQUAL(expectedSubcommands, commandlines.size());
|
||||
for (auto& cmdBlob : commandlines)
|
||||
{
|
||||
const auto result = appArgs.ParseCommand(cmdBlob);
|
||||
VERIFY_ARE_NOT_EQUAL(0, result);
|
||||
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Exit Message:\n%hs",
|
||||
appArgs._exitMessage.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void _logCommandline(std::vector<const wchar_t*>& rawCommands)
|
||||
{
|
||||
std::wstring buffer;
|
||||
@@ -995,6 +1015,124 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ParseMoveFocusArgs()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mf` instead of `move-focus`");
|
||||
const wchar_t* subcommand = useShortForm ? L"mf" : L"move-focus";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Just the subcommand, without a direction, should fail."));
|
||||
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.FocusDirection());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.FocusDirection());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.FocusDirection());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.FocusDirection());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"move-focus with an invalid direction should fail."));
|
||||
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.FocusDirection());
|
||||
|
||||
actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::MoveFocus, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
myArgs = actionAndArgs.Args().try_as<MoveFocusArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.FocusDirection());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::ValidateFirstCommandIsNewTab()
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
@@ -1325,4 +1463,119 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineTest::TestMultipleSplitPaneSizes()
|
||||
{
|
||||
BEGIN_TEST_METHOD_PROPERTIES()
|
||||
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
|
||||
END_TEST_METHOD_PROPERTIES()
|
||||
|
||||
INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `sp` instead of `split-pane`");
|
||||
const wchar_t* subcommand = useShortForm ? L"sp" : L"split-pane";
|
||||
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
// The one we actually want to test here is the SplitPane action we created
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3" };
|
||||
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
// The one we actually want to test here is the SplitPane action we created
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3", L";", subcommand };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
{
|
||||
// The one we actually want to test here is the SplitPane action we created
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
auto actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.5f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
}
|
||||
{
|
||||
AppCommandlineArgs appArgs{};
|
||||
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"-s", L".3", L";", subcommand, L"-s", L".7" };
|
||||
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
|
||||
|
||||
// The first action is going to always be a new-tab action
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
|
||||
|
||||
{
|
||||
// The one we actually want to test here is the SplitPane action we created
|
||||
auto actionAndArgs = appArgs._startupActions.at(1);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.3f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
{
|
||||
auto actionAndArgs = appArgs._startupActions.at(2);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
|
||||
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(myArgs);
|
||||
VERIFY_ARE_EQUAL(SplitState::Automatic, myArgs.SplitStyle());
|
||||
VERIFY_ARE_EQUAL(0.7f, myArgs.SplitSize());
|
||||
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,7 +486,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
@@ -504,7 +504,7 @@ namespace TerminalAppLocalTests
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
|
||||
|
||||
@@ -14,16 +14,6 @@
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
<ClCompile>
|
||||
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
|
||||
on being compiled with RTTI (/GR). We need this in this project, because
|
||||
we're including the CLI11 header via the CommandlineArgs project. -->
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Monarch.h">
|
||||
@@ -84,46 +74,9 @@
|
||||
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\CommandlineArgs\CommandlineArgs.vcxproj" >
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
<!-- Make sure to set ReferenceOutputAssembly=false, or we'll try and
|
||||
MDMERGE with MUX from the CommandlineArgs output, and get duplicate type
|
||||
errors everywhere. -->
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
|
||||
<!-- This is going to turn into a disaster. We still need to reference MUX
|
||||
to get mdmerge happy, but we can't roll that up without rolling up the DLL
|
||||
and now we'll get duplicate types in WT.vcxproj. GAH. We'll need to have
|
||||
Remoting accept a callback to say "I encountered a commandline, someone,
|
||||
deal with it?", then have apphost register for that callback. So the flow
|
||||
is monarch->AppHost->AppLogic -->
|
||||
|
||||
</ProjectReference>
|
||||
<!-- For whatever reason, we can't include the TerminalControl and
|
||||
TerminalSettings projects' winmds via project references. So we'll have to
|
||||
manually include the winmds as References below -->
|
||||
|
||||
|
||||
<Reference Include="Microsoft.Terminal.TerminalConnection">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Terminal.TerminalControl">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)TerminalControl\Microsoft.Terminal.TerminalControl.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Terminal.Settings.Model">
|
||||
<HintPath>$(OpenConsoleCommonOutDir)Microsoft.Terminal.Settings.Model\Microsoft.Terminal.Settings.Model.winmd</HintPath>
|
||||
<IsWinMDFile>true</IsWinMDFile>
|
||||
<Private>false</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!-- ====================== Compiler & Linker Flags ===================== -->
|
||||
<ItemDefinitionGroup>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Monarch.h"
|
||||
#include "CommandlineArgs.h"
|
||||
#include "../CommandlineArgs/AppCommandlineArgs.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
@@ -44,7 +46,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
// TODO:projects/5 This is terrible. There's gotta be a better way
|
||||
// of finding the first opening in a non-consecutive map of int->object
|
||||
auto providedID = peasant.GetID();
|
||||
const auto providedID = peasant.GetID();
|
||||
|
||||
if (providedID == 0)
|
||||
{
|
||||
@@ -64,6 +66,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// Add an event listener to the peasant's WindowActivated event.
|
||||
peasant.WindowActivated({ this, &Monarch::_peasantWindowActivated });
|
||||
|
||||
// TODO:projects/5 Wait on the peasant's PID, and remove them from the
|
||||
// map if they die. This won't work great in tests though, with fake
|
||||
// PIDs.
|
||||
|
||||
return newPeasantsId;
|
||||
}
|
||||
|
||||
@@ -96,23 +102,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// - the peasant if it exists in our map, otherwise null
|
||||
Remoting::IPeasant Monarch::_getPeasant(uint64_t peasantID)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
auto maybeThePeasant = peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
if (maybeThePeasant)
|
||||
{
|
||||
maybeThePeasant.GetPID();
|
||||
}
|
||||
return maybeThePeasant;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
// Remove the peasant from the list of peasants
|
||||
_peasants.erase(peasantID);
|
||||
return nullptr;
|
||||
}
|
||||
auto peasantSearch = _peasants.find(peasantID);
|
||||
return peasantSearch == _peasants.end() ? nullptr : peasantSearch->second;
|
||||
}
|
||||
|
||||
void Monarch::_setMostRecentPeasant(const uint64_t peasantID)
|
||||
@@ -140,9 +131,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// always want to create a new window, so we'll just return true. This
|
||||
// will tell the caller that we didn't handle the commandline, and they
|
||||
// should open a new window to deal with it themselves.
|
||||
|
||||
CommandlineArgs::AppCommandlineArgs argParser;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Monarch.g.h"
|
||||
#include "Peasant.h"
|
||||
#include "../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// {06171993-7eb1-4f3e-85f5-8bdd7386cce3}
|
||||
constexpr GUID Monarch_clsid{
|
||||
// We sure different GUIDs here depending on whether we're running a Release,
|
||||
// Preview, or Dev build. This ensures that different installs don't
|
||||
// accidentally talk to one another.
|
||||
//
|
||||
// * Release: {06171993-7eb1-4f3e-85f5-8bdd7386cce3}
|
||||
// * Preview: {04221993-7eb1-4f3e-85f5-8bdd7386cce3}
|
||||
// * Dev: {08302020-7eb1-4f3e-85f5-8bdd7386cce3}
|
||||
constexpr GUID Monarch_clsid
|
||||
{
|
||||
#if defined(WT_BRANDING_RELEASE)
|
||||
0x06171993,
|
||||
0x7eb1,
|
||||
0x4f3e,
|
||||
{ 0x85, 0xf5, 0x8b, 0xdd, 0x73, 0x86, 0xcc, 0xe3 }
|
||||
#elif defined(WT_BRANDING_PREVIEW)
|
||||
0x04221993,
|
||||
#else
|
||||
0x08302020,
|
||||
#endif
|
||||
0x7eb1,
|
||||
0x4f3e,
|
||||
{
|
||||
0x85, 0xf5, 0x8b, 0xdd, 0x73, 0x86, 0xcc, 0xe3
|
||||
}
|
||||
};
|
||||
|
||||
enum class WindowingBehavior : uint64_t
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "Peasant.idl";
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "Monarch.h"
|
||||
@@ -8,11 +11,6 @@
|
||||
// per-process. Once the first monarch is created, we'll stash it in g_weak.
|
||||
// Future callers who try to instantiate a Monarch will get the one that's
|
||||
// already been made.
|
||||
//
|
||||
// I'm sure there's a better way to do this with WRL, but I'm not familiar
|
||||
// enough with WRL to know for sure.
|
||||
|
||||
winrt::weak_ref<winrt::Microsoft::Terminal::Remoting::implementation::Monarch> g_weak{ nullptr };
|
||||
|
||||
struct MonarchFactory : winrt::implements<MonarchFactory, ::IClassFactory>
|
||||
{
|
||||
@@ -20,24 +18,26 @@ struct MonarchFactory : winrt::implements<MonarchFactory, ::IClassFactory>
|
||||
|
||||
HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) noexcept
|
||||
{
|
||||
static winrt::weak_ref<winrt::Microsoft::Terminal::Remoting::implementation::Monarch> g_weak{ nullptr };
|
||||
|
||||
*result = nullptr;
|
||||
if (outer)
|
||||
{
|
||||
return CLASS_E_NOAGGREGATION;
|
||||
}
|
||||
|
||||
if (!g_weak)
|
||||
// Lock the ref immediately. We don't want it freed from out beneath us
|
||||
auto strong = g_weak.get();
|
||||
if (!strong)
|
||||
{
|
||||
// Create a new Monarch instance
|
||||
auto strong = winrt::make_self<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
|
||||
|
||||
strong = winrt::make_self<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
|
||||
g_weak = (*strong).get_weak();
|
||||
return strong.as(iid, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We already instantiated one Monarch, let's just return that one!
|
||||
auto strong = g_weak.get();
|
||||
return strong.as(iid, result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Peasant.h"
|
||||
#include "CommandlineArgs.h"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Peasant.g.h"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Remoting
|
||||
{
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "WindowManager.h"
|
||||
#include "MonarchFactory.h"
|
||||
@@ -15,12 +18,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
WindowManager::WindowManager()
|
||||
{
|
||||
_monarchWaitInterrupt.create();
|
||||
|
||||
// Register with COM as a server for the Monarch class
|
||||
_registerAsMonarch();
|
||||
// Instantiate an instance of the Monarch. This may or may not be in-proc!
|
||||
_createMonarchAndCallbacks();
|
||||
_createMonarch();
|
||||
}
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
@@ -31,12 +32,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// monarch!
|
||||
CoRevokeClassObject(_registrationHostClass);
|
||||
_registrationHostClass = 0;
|
||||
_monarchWaitInterrupt.SetEvent();
|
||||
|
||||
if (_electionThread.joinable())
|
||||
{
|
||||
_electionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
@@ -55,12 +50,6 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// instance, and tell that peasant to handle that commandline.
|
||||
_createOurPeasant();
|
||||
|
||||
// TODO:MG Spawn a thread to wait on the monarch, and handle the election
|
||||
if (!isKing)
|
||||
{
|
||||
_createPeasantThread();
|
||||
}
|
||||
|
||||
_peasant.ExecuteCommandline(args);
|
||||
}
|
||||
// Otherwise, we'll do _nothing_.
|
||||
@@ -83,7 +72,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
void WindowManager::_createMonarch()
|
||||
{
|
||||
// Heads up! This only works because we're using
|
||||
// "metadata-based-marshalling" for our WinRT types. THat means the OS is
|
||||
// "metadata-based-marshalling" for our WinRT types. That means the OS is
|
||||
// using the .winmd file we generate to figure out the proxy/stub
|
||||
// definitions for our types automatically. This only works in the following
|
||||
// cases:
|
||||
@@ -94,25 +83,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
void WindowManager::_createMonarchAndCallbacks()
|
||||
{
|
||||
_createMonarch();
|
||||
const auto isKing = _areWeTheKing();
|
||||
if (!isKing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Here, we're the king!
|
||||
|
||||
// Wait, don't. Let's just have the monarch try/catch any accesses to
|
||||
// peasants. If the peasant dies, then it can't get the peasant's
|
||||
// anything. In that case, _remove it_.
|
||||
}
|
||||
|
||||
bool WindowManager::_areWeTheKing()
|
||||
{
|
||||
auto kingPID = _monarch.GetPID();
|
||||
auto ourPID = GetCurrentProcessId();
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
const auto ourPID{ GetCurrentProcessId() };
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
@@ -122,89 +96,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
_peasant = *p;
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// TODO:projects/5 Spawn a thread to wait on the monarch, and handle the election
|
||||
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
bool WindowManager::_electionNight2020()
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
|
||||
// Tell the new monarch who we are. We might be that monarch!
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
if (_areWeTheKing())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WindowManager::_createPeasantThread()
|
||||
{
|
||||
// If we catch an exception trying to get at the monarch ever, we can
|
||||
// set the _monarchWaitInterrupt, and use that to trigger a new
|
||||
// election. Though, we wouldn't be able to retry the function that
|
||||
// caused the exception in the first place...
|
||||
|
||||
_electionThread = std::thread([this] {
|
||||
_waitOnMonarchThread();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::_waitOnMonarchThread()
|
||||
{
|
||||
HANDLE waits[2];
|
||||
waits[1] = _monarchWaitInterrupt.get();
|
||||
|
||||
bool exitRequested = false;
|
||||
while (!exitRequested)
|
||||
{
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(_monarch.GetPID())) };
|
||||
// TODO:MG If we fail to open the monarch, then they don't exist
|
||||
// anymore! Go straight to an election.
|
||||
//
|
||||
// TODO:MG At any point in all this, the current monarch might die.
|
||||
// We go straight to a new election, right? Worst case, eventually,
|
||||
// we'll become the new monarch.
|
||||
//
|
||||
// if (hMonarch.get() == nullptr)
|
||||
// {
|
||||
// const auto gle = GetLastError();
|
||||
// return false;
|
||||
// }
|
||||
waits[0] = hMonarch.get();
|
||||
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled
|
||||
// Connect to the new monarch, which might be us!
|
||||
// If we become the monarch, then we'll return true and exit this thread.
|
||||
exitRequested = _electionNight2020();
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled
|
||||
exitRequested = true;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
printf("Wait timed out. This should be impossible.\n");
|
||||
exitRequested = true;
|
||||
break;
|
||||
|
||||
// Return value is invalid.
|
||||
default:
|
||||
{
|
||||
auto gle = GetLastError();
|
||||
printf("WaitForMultipleObjects returned: %d\n", waitResult);
|
||||
printf("Wait error: %d\n", gle);
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Remoting::Peasant WindowManager::CurrentWindow()
|
||||
{
|
||||
return _peasant;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WindowManager.g.h"
|
||||
@@ -23,19 +26,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
winrt::Microsoft::Terminal::Remoting::Monarch _monarch{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
wil::unique_event _monarchWaitInterrupt;
|
||||
|
||||
std::thread _electionThread;
|
||||
|
||||
void _registerAsMonarch();
|
||||
void _createMonarch();
|
||||
void _createMonarchAndCallbacks();
|
||||
bool _areWeTheKing();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant();
|
||||
|
||||
bool _electionNight2020();
|
||||
void _createPeasantThread();
|
||||
void _waitOnMonarchThread();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>User32.lib;WindowsApp.lib;shell32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<!-- TSM Lib contains a DllMain that we need to force the use of. -->
|
||||
<!-- Our lib contains a DllMain that we need to force the use of. -->
|
||||
<AdditionalOptions Condition="'$(Platform)'=='Win32'">/INCLUDE:_DllMain@12</AdditionalOptions>
|
||||
<AdditionalOptions Condition="'$(Platform)'!='Win32'">/INCLUDE:DllMain</AdditionalOptions>
|
||||
</Link>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.201017.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -44,7 +44,5 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <winrt/Microsoft.Terminal.Settings.Model.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "pch.h"
|
||||
#include "OpenTerminalHere.h"
|
||||
#include <ShlObj.h>
|
||||
|
||||
// TODO GH#6112: Localize these strings
|
||||
static constexpr std::wstring_view VerbDisplayName{ L"Open in Windows Terminal" };
|
||||
@@ -117,14 +118,29 @@ static std::wstring _getExePath()
|
||||
HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
|
||||
IBindCtx* /*pBindContext*/)
|
||||
{
|
||||
DWORD count;
|
||||
psiItemArray->GetCount(&count);
|
||||
|
||||
winrt::com_ptr<IShellItem> psi;
|
||||
RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put()));
|
||||
|
||||
wil::unique_cotaskmem_string pszName;
|
||||
RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName));
|
||||
|
||||
if (psiItemArray == nullptr)
|
||||
{
|
||||
// get the current path from explorer.exe
|
||||
const auto path = this->_GetPathFromExplorer();
|
||||
|
||||
// no go, unable to get a reasonable path
|
||||
if (path.empty())
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
pszName = wil::make_cotaskmem_string(path.c_str(), path.length());
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD count;
|
||||
psiItemArray->GetCount(&count);
|
||||
|
||||
winrt::com_ptr<IShellItem> psi;
|
||||
RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put()));
|
||||
RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName));
|
||||
}
|
||||
|
||||
{
|
||||
wil::unique_process_information _piClient;
|
||||
@@ -214,3 +230,103 @@ HRESULT OpenTerminalHere::EnumSubCommands(IEnumExplorerCommand** ppEnum)
|
||||
*ppEnum = nullptr;
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
std::wstring OpenTerminalHere::_GetPathFromExplorer() const
|
||||
{
|
||||
using namespace std;
|
||||
using namespace winrt;
|
||||
|
||||
wstring path;
|
||||
HRESULT hr = NOERROR;
|
||||
|
||||
auto hwnd = ::GetForegroundWindow();
|
||||
if (hwnd == nullptr)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
TCHAR szName[MAX_PATH] = { 0 };
|
||||
::GetClassName(hwnd, szName, MAX_PATH);
|
||||
if (0 == StrCmp(szName, L"WorkerW") ||
|
||||
0 == StrCmp(szName, L"Progman"))
|
||||
{
|
||||
//special folder: desktop
|
||||
hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
path = szName;
|
||||
return path;
|
||||
}
|
||||
|
||||
if (0 != StrCmp(szName, L"CabinetWClass"))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
com_ptr<IShellWindows> shell;
|
||||
try
|
||||
{
|
||||
shell = create_instance<IShellWindows>(CLSID_ShellWindows, CLSCTX_ALL);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
//look like try_create_instance is not available no more
|
||||
}
|
||||
|
||||
if (shell == nullptr)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
com_ptr<IDispatch> disp;
|
||||
wil::unique_variant variant;
|
||||
variant.vt = VT_I4;
|
||||
|
||||
com_ptr<IWebBrowserApp> browser;
|
||||
// look for correct explorer window
|
||||
for (variant.intVal = 0;
|
||||
shell->Item(variant, disp.put()) == S_OK;
|
||||
variant.intVal++)
|
||||
{
|
||||
com_ptr<IWebBrowserApp> tmp;
|
||||
if (FAILED(disp->QueryInterface(tmp.put())))
|
||||
{
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
continue;
|
||||
}
|
||||
|
||||
HWND tmpHWND = NULL;
|
||||
hr = tmp->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&tmpHWND));
|
||||
if (hwnd == tmpHWND)
|
||||
{
|
||||
browser = tmp;
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
break; //found
|
||||
}
|
||||
|
||||
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
||||
}
|
||||
|
||||
if (browser != nullptr)
|
||||
{
|
||||
wil::unique_bstr url;
|
||||
hr = browser->get_LocationURL(&url);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
wstring sUrl(url.get(), SysStringLen(url.get()));
|
||||
DWORD size = MAX_PATH;
|
||||
hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
path = szName;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ struct __declspec(uuid("9f156763-7844-4dc4-b2b1-901f640f5155"))
|
||||
STDMETHODIMP GetCanonicalName(GUID* pguidCommandName);
|
||||
STDMETHODIMP EnumSubCommands(IEnumExplorerCommand** ppEnum);
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
std::wstring _GetPathFromExplorer() const;
|
||||
};
|
||||
|
||||
CoCreatableClass(OpenTerminalHere);
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
<!-- sets a bunch of Windows Universal properties -->
|
||||
<OpenConsoleUniversalApp>true</OpenConsoleUniversalApp>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<Link>
|
||||
<AdditionalDependencies>User32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="OpenTerminalHere.h" />
|
||||
@@ -40,7 +43,6 @@
|
||||
<None Include="packages.config" />
|
||||
<None Include="WindowsTerminalShellExt.def" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<!--
|
||||
@@ -56,7 +58,5 @@
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.201017.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
@@ -17,6 +17,7 @@ using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
@@ -121,7 +122,11 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else if (const auto& realArgs = args.ActionArgs().try_as<SplitPaneArgs>())
|
||||
{
|
||||
_SplitPane(realArgs.SplitStyle(), realArgs.SplitMode(), realArgs.TerminalArgs());
|
||||
_SplitPane(realArgs.SplitStyle(),
|
||||
realArgs.SplitMode(),
|
||||
// This is safe, we're already filtering so the value is (0, 1)
|
||||
::base::saturated_cast<float>(realArgs.SplitSize()),
|
||||
realArgs.TerminalArgs());
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
@@ -129,23 +134,20 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
// Don't do anything if there's only one pane. It's already zoomed.
|
||||
if (activeTab->GetLeafPaneCount() > 1)
|
||||
{
|
||||
// Don't do anything if there's only one pane. It's already zoomed.
|
||||
if (activeTab && activeTab->GetLeafPaneCount() > 1)
|
||||
{
|
||||
// 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();
|
||||
// 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();
|
||||
|
||||
// Togging the zoom on the tab will cause the tab to inform us of
|
||||
// the new root Content for this tab.
|
||||
activeTab->ToggleZoom();
|
||||
}
|
||||
// Togging the zoom on the tab will cause the tab to inform us of
|
||||
// the new root Content for this tab.
|
||||
activeTab->ToggleZoom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,19 +344,16 @@ namespace winrt::TerminalApp::implementation
|
||||
args.Handled(false);
|
||||
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
|
||||
{
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
if (auto activeControl = activeTab->GetActiveTerminalControl())
|
||||
{
|
||||
if (auto activeControl = activeTab->GetActiveTerminalControl())
|
||||
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
|
||||
{
|
||||
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
|
||||
{
|
||||
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
controlSettings->ApplyColorScheme(scheme);
|
||||
activeControl.UpdateSettings(*controlSettings);
|
||||
args.Handled(true);
|
||||
}
|
||||
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
|
||||
controlSettings->ApplyColorScheme(scheme);
|
||||
activeControl.UpdateSettings(*controlSettings);
|
||||
args.Handled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,18 +370,15 @@ namespace winrt::TerminalApp::implementation
|
||||
tabColor = realArgs.TabColor();
|
||||
}
|
||||
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
if (tabColor)
|
||||
{
|
||||
if (tabColor)
|
||||
{
|
||||
activeTab->SetRuntimeTabColor(tabColor.Value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetRuntimeTabColor();
|
||||
}
|
||||
activeTab->SetRuntimeTabColor(tabColor.Value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetRuntimeTabColor();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
@@ -391,12 +387,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
{
|
||||
activeTab->ActivateColorPicker();
|
||||
}
|
||||
activeTab->ActivateColorPicker();
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
@@ -411,18 +404,15 @@ namespace winrt::TerminalApp::implementation
|
||||
title = realArgs.Title();
|
||||
}
|
||||
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
if (title.has_value())
|
||||
{
|
||||
if (title.has_value())
|
||||
{
|
||||
activeTab->SetTabText(title.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabText();
|
||||
}
|
||||
activeTab->SetTabText(title.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
activeTab->ResetTabText();
|
||||
}
|
||||
}
|
||||
args.Handled(true);
|
||||
@@ -431,12 +421,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleOpenTabRenamer(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
{
|
||||
activeTab->ActivateTabRenamer();
|
||||
}
|
||||
activeTab->ActivateTabRenamer();
|
||||
}
|
||||
args.Handled(true);
|
||||
}
|
||||
@@ -534,13 +521,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPage::_HandleOpenTabSearch(const IInspectable& /*sender*/,
|
||||
const ActionEventArgs& args)
|
||||
{
|
||||
// Tab search is always in-order.
|
||||
CommandPalette().SetTabs(_tabs, true);
|
||||
|
||||
auto opt = _GetFocusedTabIndex();
|
||||
uint32_t startIdx = opt.value_or(0);
|
||||
|
||||
CommandPalette().EnableTabSwitcherMode(true, startIdx);
|
||||
CommandPalette().SetTabs(_tabs, _mruTabs);
|
||||
CommandPalette().EnableTabSearchMode();
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
|
||||
args.Handled(true);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppLogic.h"
|
||||
#include "AppCommandlineArgs.h"
|
||||
#include "../types/inc/utils.hpp"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace CommandlineArgs;
|
||||
using namespace TerminalApp;
|
||||
|
||||
// Either a ; at the start of a line, or a ; preceded by any non-\ char.
|
||||
const std::wregex AppCommandlineArgs::_commandDelimiterRegex{ LR"(^;|[^\\];)" };
|
||||
@@ -187,6 +189,7 @@ void AppCommandlineArgs::_buildParser()
|
||||
_buildNewTabParser();
|
||||
_buildSplitPaneParser();
|
||||
_buildFocusTabParser();
|
||||
_buildMoveFocusParser();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -245,6 +248,10 @@ void AppCommandlineArgs::_buildSplitPaneParser()
|
||||
_splitVertical,
|
||||
RS_A(L"CmdSplitPaneVerticalArgDesc"));
|
||||
subcommand._verticalOption->excludes(subcommand._horizontalOption);
|
||||
auto* sizeOpt = subcommand.subcommand->add_option("-s,--size",
|
||||
_splitPaneSize,
|
||||
RS_A(L"CmdSplitPaneSizeArgDesc"));
|
||||
sizeOpt->check(CLI::Range(0.01f, 0.99f));
|
||||
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
@@ -272,7 +279,7 @@ void AppCommandlineArgs::_buildSplitPaneParser()
|
||||
style = SplitState::Vertical;
|
||||
}
|
||||
}
|
||||
SplitPaneArgs args{ style, terminalArgs };
|
||||
SplitPaneArgs args{ style, _splitPaneSize, terminalArgs };
|
||||
splitPaneActionAndArgs.Args(args);
|
||||
_startupActions.push_back(splitPaneActionAndArgs);
|
||||
});
|
||||
@@ -335,6 +342,54 @@ void AppCommandlineArgs::_buildFocusTabParser()
|
||||
setupSubcommand(_focusTabShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds the `move-focus` subcommand and related options to the commandline parser.
|
||||
// - Additionally adds the `mf` subcommand, which is just a shortened version of `move-focus`
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppCommandlineArgs::_buildMoveFocusParser()
|
||||
{
|
||||
_moveFocusCommand = _app.add_subcommand("move-focus", RS_A(L"CmdMoveFocusDesc"));
|
||||
_moveFocusShort = _app.add_subcommand("mf", RS_A(L"CmdMFDesc"));
|
||||
|
||||
auto setupSubcommand = [this](auto* subcommand) {
|
||||
std::map<std::string, FocusDirection> map = {
|
||||
{ "left", FocusDirection::Left },
|
||||
{ "right", FocusDirection::Right },
|
||||
{ "up", FocusDirection::Up },
|
||||
{ "down", FocusDirection::Down }
|
||||
};
|
||||
|
||||
auto* directionOpt = subcommand->add_option("direction",
|
||||
_moveFocusDirection,
|
||||
RS_A(L"CmdMoveFocusDirectionArgDesc"));
|
||||
|
||||
directionOpt->transform(CLI::CheckedTransformer(map, CLI::ignore_case));
|
||||
directionOpt->required();
|
||||
// When ParseCommand is called, if this subcommand was provided, this
|
||||
// callback function will be triggered on the same thread. We can be sure
|
||||
// that `this` will still be safe - this function just lets us know this
|
||||
// command was parsed.
|
||||
subcommand->callback([&, this]() {
|
||||
if (_moveFocusDirection != FocusDirection::None)
|
||||
{
|
||||
MoveFocusArgs args{ _moveFocusDirection };
|
||||
|
||||
ActionAndArgs actionAndArgs{};
|
||||
actionAndArgs.Action(ShortcutAction::MoveFocus);
|
||||
actionAndArgs.Args(args);
|
||||
|
||||
_startupActions.push_back(std::move(actionAndArgs));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setupSubcommand(_moveFocusCommand);
|
||||
setupSubcommand(_moveFocusShort);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
|
||||
// that subcommand to support all the properties in a NewTerminalArgs.
|
||||
@@ -442,6 +497,8 @@ bool AppCommandlineArgs::_noCommandsProvided()
|
||||
*_newTabShort.subcommand ||
|
||||
*_focusTabCommand ||
|
||||
*_focusTabShort ||
|
||||
*_moveFocusCommand ||
|
||||
*_moveFocusShort ||
|
||||
*_newPaneShort.subcommand ||
|
||||
*_newPaneCommand.subcommand);
|
||||
}
|
||||
@@ -464,11 +521,13 @@ void AppCommandlineArgs::_resetStateToDefault()
|
||||
|
||||
_splitVertical = false;
|
||||
_splitHorizontal = false;
|
||||
_splitPaneSize = 0.5f;
|
||||
|
||||
_focusTabIndex = -1;
|
||||
_focusNextTab = false;
|
||||
_focusPrevTab = false;
|
||||
|
||||
_moveFocusDirection = FocusDirection::None;
|
||||
// DON'T clear _launchMode here! This will get called once for every
|
||||
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
|
||||
// the "global" fullscreen flag (-F).
|
||||
@@ -685,7 +744,7 @@ std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> AppComman
|
||||
// - 0 if the commandline was successfully parsed
|
||||
int AppCommandlineArgs::ParseArgs(winrt::array_view<const winrt::hstring>& args)
|
||||
{
|
||||
auto commands = ::CommandlineArgs::AppCommandlineArgs::BuildCommands(args);
|
||||
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
|
||||
|
||||
for (auto& cmdBlob : commands)
|
||||
{
|
||||
@@ -2,7 +2,6 @@
|
||||
// Licensed under the MIT license.
|
||||
#pragma once
|
||||
|
||||
#include <CLI11/CLI11.hpp>
|
||||
#include "Commandline.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
@@ -13,12 +12,12 @@ namespace TerminalAppLocalTests
|
||||
};
|
||||
#endif
|
||||
|
||||
namespace CommandlineArgs
|
||||
namespace TerminalApp
|
||||
{
|
||||
class AppCommandlineArgs;
|
||||
};
|
||||
|
||||
class CommandlineArgs::AppCommandlineArgs final
|
||||
class TerminalApp::AppCommandlineArgs final
|
||||
{
|
||||
public:
|
||||
static constexpr std::string_view NixHelpFlag{ "-?" };
|
||||
@@ -75,6 +74,9 @@ private:
|
||||
NewPaneSubcommand _newPaneShort;
|
||||
CLI::App* _focusTabCommand;
|
||||
CLI::App* _focusTabShort;
|
||||
CLI::App* _moveFocusCommand;
|
||||
CLI::App* _moveFocusShort;
|
||||
|
||||
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
|
||||
|
||||
std::string _profileName;
|
||||
@@ -82,11 +84,14 @@ private:
|
||||
std::string _startingTitle;
|
||||
std::string _startingTabColor;
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::Model::FocusDirection _moveFocusDirection{ winrt::Microsoft::Terminal::Settings::Model::FocusDirection::None };
|
||||
|
||||
// _commandline will contain the command line with which we'll be spawning a new terminal
|
||||
std::vector<std::string> _commandline;
|
||||
|
||||
bool _splitVertical{ false };
|
||||
bool _splitHorizontal{ false };
|
||||
float _splitPaneSize{ 0.5f };
|
||||
|
||||
int _focusTabIndex{ -1 };
|
||||
bool _focusNextTab{ false };
|
||||
@@ -106,6 +111,7 @@ private:
|
||||
void _buildNewTabParser();
|
||||
void _buildSplitPaneParser();
|
||||
void _buildFocusTabParser();
|
||||
void _buildMoveFocusParser();
|
||||
bool _noCommandsProvided();
|
||||
void _resetStateToDefault();
|
||||
int _handleExit(const CLI::App& command, const CLI::Error& e);
|
||||
@@ -17,7 +17,7 @@ using namespace winrt::Windows::System;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace ::CommandlineArgs;
|
||||
using namespace ::TerminalApp;
|
||||
|
||||
namespace winrt
|
||||
{
|
||||
@@ -42,7 +42,10 @@ static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadWar
|
||||
USES_RESOURCE(L"LegacyGlobalsProperty"),
|
||||
USES_RESOURCE(L"FailedToParseCommandJson"),
|
||||
USES_RESOURCE(L"FailedToWriteToSettings"),
|
||||
USES_RESOURCE(L"InvalidColorSchemeInCmd")
|
||||
USES_RESOURCE(L"InvalidColorSchemeInCmd"),
|
||||
USES_RESOURCE(L"InvalidSplitSize"),
|
||||
USES_RESOURCE(L"FailedToParseStartupActions"),
|
||||
USES_RESOURCE(L"FailedToParseSubCommands"),
|
||||
};
|
||||
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
|
||||
USES_RESOURCE(L"NoProfilesText"),
|
||||
@@ -262,6 +265,14 @@ namespace winrt::TerminalApp::implementation
|
||||
_settings.GlobalSettings().ShowTabsInTitlebar(false);
|
||||
}
|
||||
|
||||
// Pay attention, that even if some command line arguments were parsed (like launch mode),
|
||||
// we will not use the startup actions from settings.
|
||||
// While this simplifies the logic, we might want to reconsider this behavior in the future.
|
||||
if (!_hasCommandLineArguments && _hasSettingsStartupActions)
|
||||
{
|
||||
_root->SetStartupActions(_settingsAppArgs.GetStartupActions());
|
||||
}
|
||||
|
||||
_root->SetSettings(_settings, false);
|
||||
_root->Loaded({ this, &AppLogic::_OnLoaded });
|
||||
_root->Initialized([this](auto&&, auto&&) {
|
||||
@@ -425,8 +436,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Make sure the lines of text wrap
|
||||
warningsTextBlock.TextWrapping(TextWrapping::Wrap);
|
||||
|
||||
const auto warnings = _settings.Warnings();
|
||||
for (const auto& warning : warnings)
|
||||
for (const auto& warning : _warnings)
|
||||
{
|
||||
// Try looking up the warning message key for each warning.
|
||||
const auto warningText = _GetWarningText(warning);
|
||||
@@ -715,7 +725,34 @@ namespace winrt::TerminalApp::implementation
|
||||
return E_INVALIDARG;
|
||||
}
|
||||
|
||||
hr = _settings.Warnings().Size() == 0 ? S_OK : S_FALSE;
|
||||
_warnings.clear();
|
||||
for (uint32_t i = 0; i < _settings.Warnings().Size(); i++)
|
||||
{
|
||||
_warnings.push_back(_settings.Warnings().GetAt(i));
|
||||
}
|
||||
|
||||
_hasSettingsStartupActions = false;
|
||||
const auto startupActions = _settings.GlobalSettings().StartupActions();
|
||||
if (!startupActions.empty())
|
||||
{
|
||||
_settingsAppArgs.FullResetState();
|
||||
|
||||
ExecuteCommandlineArgs args{ _settings.GlobalSettings().StartupActions() };
|
||||
auto result = _settingsAppArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
_hasSettingsStartupActions = true;
|
||||
|
||||
// Validation also injects new-tab command if implicit new-tab was provided.
|
||||
_settingsAppArgs.ValidateStartupCommands();
|
||||
}
|
||||
else
|
||||
{
|
||||
_warnings.push_back(SettingsLoadWarnings::FailedToParseStartupActions);
|
||||
}
|
||||
}
|
||||
|
||||
hr = _warnings.empty() ? S_OK : S_FALSE;
|
||||
}
|
||||
catch (const winrt::hresult_error& e)
|
||||
{
|
||||
@@ -805,7 +842,8 @@ namespace winrt::TerminalApp::implementation
|
||||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
if (!(event == wil::FolderChangeEvent::Modified ||
|
||||
event == wil::FolderChangeEvent::RenameNewName))
|
||||
event == wil::FolderChangeEvent::RenameNewName ||
|
||||
event == wil::FolderChangeEvent::Removed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1104,6 +1142,9 @@ namespace winrt::TerminalApp::implementation
|
||||
const auto result = _appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
// If the size of the arguments list is 1,
|
||||
// then it contains only the executable name and no other arguments.
|
||||
_hasCommandLineArguments = args.size() > 1;
|
||||
_appArgs.ValidateStartupCommands();
|
||||
_root->SetStartupActions(_appArgs.GetStartupActions());
|
||||
}
|
||||
@@ -1113,7 +1154,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
int32_t AppLogic::ExecuteCommandline(array_view<const winrt::hstring> args)
|
||||
{
|
||||
::CommandlineArgs::AppCommandlineArgs appArgs;
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
auto result = appArgs.ParseArgs(args);
|
||||
if (result == 0)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
std::atomic<bool> _settingsReloadQueued{ false };
|
||||
|
||||
::CommandlineArgs::AppCommandlineArgs _appArgs;
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
|
||||
int _ParseArgs(winrt::array_view<const hstring>& args);
|
||||
|
||||
void _ShowLoadErrorsDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey, HRESULT settingsLoadedResult);
|
||||
@@ -107,6 +108,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _ApplyTheme(const Windows::UI::Xaml::ElementTheme& newTheme);
|
||||
|
||||
bool _hasCommandLineArguments{ false };
|
||||
bool _hasSettingsStartupActions{ false };
|
||||
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings;
|
||||
|
||||
// These are events that are handled by the TerminalPage, but are
|
||||
// exposed through the AppLogic. This macro is used to forward the event
|
||||
// directly to them.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<StackPanel>
|
||||
<StackPanel XYFocusKeyboardNavigation="Enabled">
|
||||
<VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="4" HorizontalAlignment="Center" Margin="0, 3, 0, 0">
|
||||
<VariableSizedWrapGrid.Resources>
|
||||
<Style TargetType="Rectangle">
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_currentNestedCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_allCommands = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_tabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_mruTabActions = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
_commandLineHistory = winrt::single_threaded_vector<winrt::TerminalApp::FilteredCommand>();
|
||||
|
||||
_switchToMode(CommandPaletteMode::ActionMode);
|
||||
@@ -51,6 +52,12 @@ namespace winrt::TerminalApp::implementation
|
||||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
if (_filteredActionsView().Items().Size() == 0 && _filteredActions.Size() > 0)
|
||||
{
|
||||
// Force immediate binding update so we can select an item
|
||||
Bindings->Update();
|
||||
}
|
||||
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
_searchBox().Visibility(Visibility::Collapsed);
|
||||
@@ -66,7 +73,6 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
TraceLoggingWrite(
|
||||
@@ -112,7 +118,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void CommandPalette::SelectNextItem(const bool moveDown)
|
||||
{
|
||||
const auto selected = _filteredActionsView().SelectedIndex();
|
||||
auto selected = _filteredActionsView().SelectedIndex();
|
||||
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
||||
|
||||
// Do not try to select an item if
|
||||
@@ -237,6 +243,9 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
// 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,
|
||||
@@ -245,55 +254,38 @@ namespace winrt::TerminalApp::implementation
|
||||
// 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::TabSwitchMode && key == VirtualKey::Tab)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode && _keymap)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
const auto action = _keymap.TryLookup(kc);
|
||||
if (action)
|
||||
{
|
||||
SelectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectNextItem(true);
|
||||
e.Handled(true);
|
||||
if (action.Action() == ShortcutAction::PrevTab)
|
||||
{
|
||||
SelectNextItem(false);
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
else if (action.Action() == ShortcutAction::NextTab)
|
||||
{
|
||||
SelectNextItem(true);
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (key == VirtualKey::Home)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
ScrollToTop();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
else if (key == VirtualKey::End)
|
||||
{
|
||||
auto const state = CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control);
|
||||
if (WI_IsFlagSet(state, CoreVirtualKeyStates::Down))
|
||||
{
|
||||
ScrollToBottom();
|
||||
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
|
||||
// commands in both Action mode and Commandline mode.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
|
||||
if (key == VirtualKey::Up)
|
||||
if (key == VirtualKey::Home && ctrlDown)
|
||||
{
|
||||
ScrollToTop();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::End && ctrlDown)
|
||||
{
|
||||
ScrollToBottom();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
SelectNextItem(false);
|
||||
@@ -339,41 +331,23 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Back)
|
||||
else if (key == VirtualKey::Back && _searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
// If the last filter text was empty, and we're backspacing from
|
||||
// that state, then the user "backspaced" the virtual '>' we're
|
||||
// using as the action mode indicator. Switch into commandline mode.
|
||||
if (_searchBox().Text().empty() && _lastFilterTextWasEmpty && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::CommandlineMode);
|
||||
}
|
||||
|
||||
_switchToMode(CommandPaletteMode::CommandlineMode);
|
||||
e.Handled(true);
|
||||
}
|
||||
else
|
||||
else if (key == VirtualKey::C && ctrlDown)
|
||||
{
|
||||
const auto vkey = ::gsl::narrow_cast<WORD>(e.OriginalKey());
|
||||
|
||||
// In the interest of not telling all modes to check for keybindings, limit to TabSwitch mode for now.
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
auto success = _bindings.TryKeyChord({
|
||||
ctrlDown,
|
||||
altDown,
|
||||
shiftDown,
|
||||
vkey,
|
||||
});
|
||||
|
||||
if (success)
|
||||
{
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
_searchBox().CopySelectionToClipboard();
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::V && ctrlDown)
|
||||
{
|
||||
_searchBox().PasteFromClipboard();
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,6 +436,12 @@ namespace winrt::TerminalApp::implementation
|
||||
void CommandPalette::_lostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
const auto flyout = _searchBox().ContextFlyout();
|
||||
if (flyout && flyout.IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto focusedElementOrAncestor = Input::FocusManager::GetFocusedElement(this->XamlRoot()).try_as<DependencyObject>();
|
||||
while (focusedElementOrAncestor)
|
||||
{
|
||||
@@ -578,7 +558,7 @@ namespace winrt::TerminalApp::implementation
|
||||
case CommandPaletteMode::TabSearchMode:
|
||||
return _tabActions;
|
||||
case CommandPaletteMode::TabSwitchMode:
|
||||
return _tabActions;
|
||||
return _tabSwitcherMode == TabSwitcherMode::MostRecentlyUsed ? _mruTabActions : _tabActions;
|
||||
case CommandPaletteMode::CommandlineMode:
|
||||
return _commandLineHistory;
|
||||
default:
|
||||
@@ -691,7 +671,10 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (const auto tabPaletteItem{ filteredCommand.Item().try_as<winrt::TerminalApp::TabPaletteItem>() })
|
||||
{
|
||||
_SwitchToTabRequestedHandlers(*this, tabPaletteItem.Tab());
|
||||
if (const auto tab{ tabPaletteItem.Tab() })
|
||||
{
|
||||
_SwitchToTabRequestedHandlers(*this, tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -853,9 +836,9 @@ namespace winrt::TerminalApp::implementation
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings)
|
||||
void CommandPalette::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap)
|
||||
{
|
||||
_bindings = bindings;
|
||||
_keymap = keymap;
|
||||
}
|
||||
|
||||
void CommandPalette::SetCommands(Collections::IVector<Command> const& actions)
|
||||
@@ -868,31 +851,40 @@ namespace winrt::TerminalApp::implementation
|
||||
_allCommands.Append(filteredCommand);
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
if (Visibility() == Visibility::Visible && _currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
_updateFilteredActions();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPalette::SetTabs(Collections::IVector<TabBase> const& tabs, const bool clearList)
|
||||
// Method Description:
|
||||
// - Replaces a list of filtered commands in the target collection with new
|
||||
// commands based on the tabs in the source collection.
|
||||
// Although the source observable we still don't register on it,
|
||||
// so the palette user will need to reset the binding manually every time
|
||||
// the source collection changes
|
||||
// Arguments:
|
||||
// - source: the tabs to use for creation filtered commands
|
||||
// - target: the collection to store newly created filtered commands
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_bindTabs(
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& source,
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& target)
|
||||
{
|
||||
_tabActions.Clear();
|
||||
for (const auto& tab : tabs)
|
||||
target.Clear();
|
||||
for (const auto& tab : source)
|
||||
{
|
||||
auto tabPaletteItem{ winrt::make<winrt::TerminalApp::implementation::TabPaletteItem>(tab) };
|
||||
auto filteredCommand{ winrt::make<FilteredCommand>(tabPaletteItem) };
|
||||
_tabActions.Append(filteredCommand);
|
||||
target.Append(filteredCommand);
|
||||
}
|
||||
}
|
||||
|
||||
// The smooth remove/add animations that happen during
|
||||
// UpdateFilteredActions don't work very well with changing the tab
|
||||
// order, because of the sheer amount of remove/adds. So, let's just
|
||||
// clear & rebuild the list when we change the set of tabs.
|
||||
//
|
||||
// Some callers might actually want smooth updating, like when the list
|
||||
// of tabs changes.
|
||||
if (clearList && _currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
_filteredActions.Clear();
|
||||
}
|
||||
_updateFilteredActions();
|
||||
void CommandPalette::SetTabs(Collections::IObservableVector<TabBase> const& tabs, Collections::IObservableVector<TabBase> const& mruTabs)
|
||||
{
|
||||
_bindTabs(tabs, _tabActions);
|
||||
_bindTabs(mruTabs, _mruTabActions);
|
||||
}
|
||||
|
||||
void CommandPalette::EnableCommandPaletteMode(CommandPaletteLaunchMode const launchMode)
|
||||
@@ -902,26 +894,11 @@ namespace winrt::TerminalApp::implementation
|
||||
CommandPaletteMode::ActionMode;
|
||||
|
||||
_switchToMode(mode);
|
||||
_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);
|
||||
}
|
||||
}
|
||||
_currentMode = mode;
|
||||
|
||||
ParsedCommandLineText(L"");
|
||||
_searchBox().Text(L"");
|
||||
@@ -954,6 +931,13 @@ namespace winrt::TerminalApp::implementation
|
||||
PrefixCharacter(L">");
|
||||
break;
|
||||
}
|
||||
|
||||
// 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.
|
||||
_filteredActions.Clear();
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -970,27 +954,34 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::hstring searchText{ _getTrimmedInput() };
|
||||
|
||||
auto commandsToFilter = _commandsToFilter();
|
||||
for (const auto& action : commandsToFilter)
|
||||
{
|
||||
// Update filter for all commands
|
||||
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
|
||||
// Pay attention that it already updates the highlighting in the UI
|
||||
action.UpdateFilter(searchText);
|
||||
|
||||
// if there is active search we skip commands with 0 weight
|
||||
if (searchText.empty() || action.Weight() > 0)
|
||||
if (_currentMode == CommandPaletteMode::TabSwitchMode)
|
||||
{
|
||||
std::copy(begin(commandsToFilter), end(commandsToFilter), std::back_inserter(actions));
|
||||
}
|
||||
else if (_currentMode == CommandPaletteMode::TabSearchMode || _currentMode == CommandPaletteMode::ActionMode || _currentMode == CommandPaletteMode::CommandlineMode)
|
||||
{
|
||||
for (const auto& action : commandsToFilter)
|
||||
{
|
||||
actions.push_back(action);
|
||||
// Update filter for all commands
|
||||
// This will modify the highlighting but will also lead to re-computation of weight (and consequently sorting).
|
||||
// Pay attention that it already updates the highlighting in the UI
|
||||
action.UpdateFilter(searchText);
|
||||
|
||||
// if there is active search we skip commands with 0 weight
|
||||
if (searchText.empty() || action.Weight() > 0)
|
||||
{
|
||||
actions.push_back(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We want to present the commands sorted,
|
||||
// unless we are in the TabSwitcherMode and TabSearchMode,
|
||||
// in which we want to preserve the original order (to be aligned with the tab view)
|
||||
if (_currentMode != CommandPaletteMode::TabSearchMode && _currentMode != CommandPaletteMode::TabSwitchMode && _currentMode != CommandPaletteMode::CommandlineMode)
|
||||
// We want to present the commands sorted
|
||||
if (_currentMode == CommandPaletteMode::ActionMode)
|
||||
{
|
||||
std::sort(actions.begin(), actions.end(), FilteredCommand::Compare);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@@ -1065,20 +1056,18 @@ namespace winrt::TerminalApp::implementation
|
||||
_currentNestedCommands.Clear();
|
||||
}
|
||||
|
||||
void CommandPalette::EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx)
|
||||
void CommandPalette::EnableTabSwitcherMode(const uint32_t startIdx, TabSwitcherMode tabSwitcherMode)
|
||||
{
|
||||
_switcherStartIdx = startIdx;
|
||||
|
||||
if (searchMode)
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::TabSearchMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::TabSwitchMode);
|
||||
}
|
||||
|
||||
_updateFilteredActions();
|
||||
// The _switcherStartIdx field allows us to select the current tab
|
||||
// We need to take it into account only in the in-order mode,
|
||||
// as an MRU mode the current tab is on top by definition.
|
||||
_switcherStartIdx = tabSwitcherMode == TabSwitcherMode::InOrder ? startIdx : 0;
|
||||
_tabSwitcherMode = tabSwitcherMode;
|
||||
_switchToMode(CommandPaletteMode::TabSwitchMode);
|
||||
}
|
||||
|
||||
void CommandPalette::EnableTabSearchMode()
|
||||
{
|
||||
_switchToMode(CommandPaletteMode::TabSearchMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include "FilteredCommand.h"
|
||||
#include "CommandPalette.g.h"
|
||||
#include "../CommandlineArgs/AppCommandlineArgs.h"
|
||||
#include "AppCommandlineArgs.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
@@ -31,10 +31,8 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::FilteredCommand> FilteredActions();
|
||||
|
||||
void SetCommands(Windows::Foundation::Collections::IVector<Microsoft::Terminal::Settings::Model::Command> const& actions);
|
||||
void SetTabs(Windows::Foundation::Collections::IVector<winrt::TerminalApp::TabBase> const& tabs, const bool clearList);
|
||||
void SetKeyBindings(Microsoft::Terminal::TerminalControl::IKeyBindings bindings);
|
||||
|
||||
void EnableCommandPaletteMode(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode const launchMode);
|
||||
void SetTabs(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& tabs, Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& mruTabs);
|
||||
void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap);
|
||||
|
||||
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
|
||||
|
||||
@@ -45,9 +43,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void ScrollToTop();
|
||||
void ScrollToBottom();
|
||||
|
||||
// Tab Switcher
|
||||
void EnableTabSwitcherMode(const bool searchMode, const uint32_t startIdx);
|
||||
void SetTabSwitchOrder(const Microsoft::Terminal::Settings::Model::TabSwitcherMode order);
|
||||
void EnableCommandPaletteMode(Microsoft::Terminal::Settings::Model::CommandPaletteLaunchMode const launchMode);
|
||||
void EnableTabSwitcherMode(const uint32_t startIdx, Microsoft::Terminal::Settings::Model::TabSwitcherMode tabSwitcherMode);
|
||||
void EnableTabSearchMode();
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, NoMatchesText, _PropertyChangedHandlers);
|
||||
@@ -77,8 +75,7 @@ namespace winrt::TerminalApp::implementation
|
||||
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);
|
||||
|
||||
@@ -109,11 +106,15 @@ namespace winrt::TerminalApp::implementation
|
||||
std::wstring _getTrimmedInput();
|
||||
void _evaluatePrefix();
|
||||
|
||||
Microsoft::Terminal::TerminalControl::IKeyBindings _bindings;
|
||||
Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr };
|
||||
|
||||
// Tab Switcher
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _tabActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _mruTabActions{ nullptr };
|
||||
Microsoft::Terminal::Settings::Model::TabSwitcherMode _tabSwitcherMode;
|
||||
uint32_t _switcherStartIdx;
|
||||
|
||||
void _bindTabs(Windows::Foundation::Collections::IObservableVector<winrt::TerminalApp::TabBase> const& source, Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> const& target);
|
||||
void _anchorKeyUpHandler();
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::ListView::SizeChanged_revoker _sizeChangedRevoker;
|
||||
@@ -130,7 +131,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
static constexpr int CommandLineHistoryLength = 10;
|
||||
Windows::Foundation::Collections::IVector<winrt::TerminalApp::FilteredCommand> _commandLineHistory{ nullptr };
|
||||
::CommandlineArgs::AppCommandlineArgs _appArgs;
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
|
||||
friend class TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
|
||||
@@ -22,13 +22,16 @@ namespace TerminalApp
|
||||
Windows.Foundation.Collections.IObservableVector<FilteredCommand> FilteredActions { get; };
|
||||
|
||||
void SetCommands(Windows.Foundation.Collections.IVector<Microsoft.Terminal.Settings.Model.Command> actions);
|
||||
void SetTabs(Windows.Foundation.Collections.IVector<TabBase> tabs, Boolean clearList);
|
||||
void SetKeyBindings(Microsoft.Terminal.TerminalControl.IKeyBindings bindings);
|
||||
void EnableCommandPaletteMode(Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode launchMode);
|
||||
|
||||
void SetTabs(Windows.Foundation.Collections.IObservableVector<TabBase> tabs, Windows.Foundation.Collections.IObservableVector<TabBase> mruTabs);
|
||||
|
||||
void SetKeyMap(Microsoft.Terminal.Settings.Model.KeyMapping keymap);
|
||||
|
||||
void SelectNextItem(Boolean moveDown);
|
||||
|
||||
void EnableTabSwitcherMode(Boolean searchMode, UInt32 startIdx);
|
||||
void EnableCommandPaletteMode(Microsoft.Terminal.Settings.Model.CommandPaletteLaunchMode launchMode);
|
||||
void EnableTabSwitcherMode(UInt32 startIdx, Microsoft.Terminal.Settings.Model.TabSwitcherMode tabSwitcherMode);
|
||||
void EnableTabSearchMode();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<CommandPalette, TabBase> SwitchToTabRequested;
|
||||
event Windows.Foundation.TypedEventHandler<CommandPalette, Microsoft.Terminal.Settings.Model.Command> DispatchCommandRequested;
|
||||
|
||||
@@ -16,7 +16,6 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
AllowFocusOnInteraction="True"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
PreviewKeyDown="_previewKeyDownHandler"
|
||||
KeyDown="_keyDownHandler"
|
||||
PreviewKeyUp="_keyUpHandler"
|
||||
LostFocus="_lostFocusHandler"
|
||||
mc:Ignorable="d"
|
||||
@@ -34,6 +33,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParsedCommandLineTextVisibilityConverter"/>
|
||||
<local:EmptyStringVisibilityConverter x:Key="ParentCommandVisibilityConverter"/>
|
||||
<local:HasNestedCommandsVisibilityConverter x:Key="HasNestedCommandsVisibilityConverter"/>
|
||||
<local:HasNestedCommandsHelpTextConverter x:Key="HasNestedCommandsHelpTextConverter"/>
|
||||
<model:IconPathConverter x:Key="IconSourceConverter"/>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
@@ -132,7 +132,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Style x:Key="KeyChordTextBlockStyle" TargetType="TextBlock"/>
|
||||
|
||||
<!-- ParsedCommandLineText styles (use XAML defaults for High Contrast theme) -->
|
||||
<Style x:Key="ParsedCommandLineBoderStyle" TargetType="Border"/>
|
||||
<Style x:Key="ParsedCommandLineBorderStyle" TargetType="Border"/>
|
||||
<Style x:Key="ParsedCommandLineTextBlockStyle" TargetType="TextBlock"/>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
@@ -278,7 +278,6 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
PreviewKeyDown="_keyDownHandler"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
@@ -287,9 +286,9 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
AutomationProperties.Name="{x:Bind Item.Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}">
|
||||
AutomationProperties.AcceleratorKey="{x:Bind Item.KeyChordText, Mode=OneWay}"
|
||||
AutomationProperties.HelpText="{x:Bind Item, Mode=OneWay, Converter={StaticResource HasNestedCommandsHelpTextConverter}}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" ColumnSpacing="8" >
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -314,7 +313,13 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
|
||||
<!-- The block for the key chord is only visible
|
||||
when there's actual text set as the label. See
|
||||
CommandKeyChordVisibilityConverter for details. -->
|
||||
CommandKeyChordVisibilityConverter for details.
|
||||
|
||||
We're setting the accessibility view on the
|
||||
border and text block to Raw because otherwise,
|
||||
Narrator will read out the key chord. Problem is,
|
||||
it already did that because it was the list item's
|
||||
"AcceleratorKey". It's redundant. -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Visibility="{x:Bind Item.KeyChordText,
|
||||
@@ -323,12 +328,14 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
Style="{ThemeResource KeyChordBorderStyle}"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw">
|
||||
|
||||
<TextBlock
|
||||
Style="{ThemeResource KeyChordTextBlockStyle}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind Item.KeyChordText, Mode=OneWay}" />
|
||||
Text="{x:Bind Item.KeyChordText, Mode=OneWay}"
|
||||
AutomationProperties.AccessibilityView="Raw" />
|
||||
</Border>
|
||||
|
||||
<!-- xE70E is ChevronUp. Rotated 90 degrees, it's _ChevronRight_ -->
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "pch.h"
|
||||
#include "Commandline.h"
|
||||
|
||||
using namespace CommandlineArgs;
|
||||
using namespace TerminalApp;
|
||||
|
||||
size_t Commandline::Argc() const
|
||||
{
|
||||
@@ -23,12 +23,12 @@ namespace TerminalAppLocalTests
|
||||
{
|
||||
class CommandlineTest;
|
||||
};
|
||||
namespace CommandlineArgs
|
||||
namespace TerminalApp
|
||||
{
|
||||
class Commandline;
|
||||
};
|
||||
|
||||
class CommandlineArgs::Commandline
|
||||
class TerminalApp::Commandline
|
||||
{
|
||||
public:
|
||||
static constexpr std::wstring_view Delimiter{ L";" };
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "pch.h"
|
||||
#include "HasNestedCommandsVisibilityConverter.h"
|
||||
#include "HasNestedCommandsVisibilityConverter.g.cpp"
|
||||
#include "HasNestedCommandsHelpTextConverter.g.cpp"
|
||||
|
||||
#include "LibraryResources.h"
|
||||
|
||||
using namespace winrt::Windows;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -36,4 +39,23 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
|
||||
Foundation::IInspectable HasNestedCommandsHelpTextConverter::Convert(Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
const auto paletteItem{ value.try_as<winrt::TerminalApp::ActionPaletteItem>() };
|
||||
const auto hasNestedCommands = paletteItem && paletteItem.Command().HasNestedCommands();
|
||||
return winrt::box_value(hasNestedCommands ? RS_(L"CommandPalette_MoreOptions/[using:Windows.UI.Xaml.Automation]AutomationProperties/HelpText") : L"");
|
||||
}
|
||||
|
||||
// unused for one-way bindings
|
||||
Foundation::IInspectable HasNestedCommandsHelpTextConverter::ConvertBack(Foundation::IInspectable const& /* value */,
|
||||
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
|
||||
Foundation::IInspectable const& /* parameter */,
|
||||
hstring const& /* language */)
|
||||
{
|
||||
throw hresult_not_implemented();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "HasNestedCommandsVisibilityConverter.g.h"
|
||||
#include "HasNestedCommandsHelpTextConverter.g.h"
|
||||
#include "../inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -19,9 +20,25 @@ namespace winrt::TerminalApp::implementation
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
};
|
||||
|
||||
struct HasNestedCommandsHelpTextConverter : HasNestedCommandsHelpTextConverterT<HasNestedCommandsHelpTextConverter>
|
||||
{
|
||||
HasNestedCommandsHelpTextConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
|
||||
Windows::Foundation::IInspectable ConvertBack(Windows::Foundation::IInspectable const& value,
|
||||
Windows::UI::Xaml::Interop::TypeName const& targetType,
|
||||
Windows::Foundation::IInspectable const& parameter,
|
||||
hstring const& language);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(HasNestedCommandsVisibilityConverter);
|
||||
BASIC_FACTORY(HasNestedCommandsHelpTextConverter);
|
||||
}
|
||||
|
||||
@@ -16,4 +16,9 @@ namespace TerminalApp
|
||||
HasNestedCommandsVisibilityConverter();
|
||||
};
|
||||
|
||||
runtimeclass HasNestedCommandsHelpTextConverter : [default] Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
HasNestedCommandsHelpTextConverter();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include "MinMaxCloseControl.h"
|
||||
|
||||
#include "MinMaxCloseControl.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
@@ -77,6 +80,7 @@ namespace winrt::TerminalApp::implementation
|
||||
MinimizeButton().Height(maximizedHeight);
|
||||
MaximizeButton().Height(maximizedHeight);
|
||||
CloseButton().Height(maximizedHeight);
|
||||
MaximizeToolTip().Text(RS_(L"WindowRestoreDownButtonToolTip"));
|
||||
break;
|
||||
|
||||
case WindowVisualState::WindowVisualStateNormal:
|
||||
@@ -87,6 +91,7 @@ namespace winrt::TerminalApp::implementation
|
||||
MinimizeButton().Height(windowedHeight);
|
||||
MaximizeButton().Height(windowedHeight);
|
||||
CloseButton().Height(windowedHeight);
|
||||
MaximizeToolTip().Text(RS_(L"WindowMaximizeButtonToolTip"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<StaticResource x:Key="CaptionButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStroke" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemBaseHighColor"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokePointerOver" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokePressed" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonBackground" Color="Transparent" />
|
||||
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
|
||||
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="#e81123"/>
|
||||
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White"/>
|
||||
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a"/>
|
||||
@@ -35,9 +37,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<StaticResource x:Key="CaptionButtonBackgroundPointerOver" ResourceKey="SystemControlBackgroundBaseLowBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseMediumLowBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStroke" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemBaseHighColor"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokePointerOver" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokePressed" ResourceKey="SystemControlForegroundBaseHighBrush"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonBackground" Color="Transparent" />
|
||||
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
|
||||
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="#e81123"/>
|
||||
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White"/>
|
||||
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a"/>
|
||||
@@ -46,9 +50,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<x:Double x:Key="CaptionButtonStrokeWidth">3.0</x:Double>
|
||||
<SolidColorBrush x:Key="CaptionButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}"/>
|
||||
<StaticResource x:Key="CaptionButtonBackgroundColor" ResourceKey="SystemColorButtonFaceColor"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonStroke" Color="{ThemeResource SystemColorButtonTextColor}"/>
|
||||
<StaticResource x:Key="CaptionButtonStrokeColor" ResourceKey="SystemColorButtonTextColor"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonStrokePointerOver" Color="{ThemeResource SystemColorHighlightTextColor}"/>
|
||||
<SolidColorBrush x:Key="CaptionButtonStrokePressed" Color="{ThemeResource SystemColorHighlightTextColor}"/>
|
||||
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}"/>
|
||||
@@ -95,8 +101,8 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition From="PointerOver" To="Normal">
|
||||
<Storyboard>
|
||||
<ColorAnimation Storyboard.TargetName="ButtonBaseElement" Storyboard.TargetProperty="(UIElement.Background).(SolidColorBrush.Color)" To="{Binding Color, Source={ThemeResource CaptionButtonBackground}}" Duration="0:0:0.2"/>
|
||||
<ColorAnimation Storyboard.TargetName="Path" Storyboard.TargetProperty="(UIElement.Stroke).(SolidColorBrush.Color)" To="{Binding Color, Source={ThemeResource CaptionButtonStroke}}" Duration="0:0:0.1"/>
|
||||
<ColorAnimation Storyboard.TargetName="ButtonBaseElement" Storyboard.TargetProperty="(UIElement.Background).(SolidColorBrush.Color)" To="{ThemeResource CaptionButtonBackgroundColor}" Duration="0:0:0.2"/>
|
||||
<ColorAnimation Storyboard.TargetName="Path" Storyboard.TargetProperty="(UIElement.Stroke).(SolidColorBrush.Color)" To="{ThemeResource CaptionButtonStrokeColor}" Duration="0:0:0.1"/>
|
||||
</Storyboard>
|
||||
</VisualTransition>
|
||||
</VisualStateGroup.Transitions>
|
||||
@@ -182,6 +188,13 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<x:String x:Key="CaptionButtonPathWindowMaximized">M 0 2 h 8 v 8 h -8 v -8 M 2 2 v -2 h 8 v 8 h -2</x:String>
|
||||
</ResourceDictionary>
|
||||
</Button.Resources>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip>
|
||||
<TextBlock>
|
||||
<Run x:Name="MaximizeToolTip"/>
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<Button Height="{StaticResource CaptionButtonHeightWindowed}" MinWidth="46.0" Width="46.0"
|
||||
x:Name="CloseButton"
|
||||
|
||||
@@ -17,11 +17,10 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
using namespace winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace winrt::TerminalApp;
|
||||
|
||||
using namespace TerminalApp;
|
||||
|
||||
static const int PaneBorderSize = 2;
|
||||
static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
||||
static const float Half = 0.50f;
|
||||
|
||||
// WARNING: Don't do this! This won't work
|
||||
// Duration duration{ std::chrono::milliseconds{ 200 } };
|
||||
@@ -60,6 +59,7 @@ Pane::Pane(const GUID& profile, const TermControl& control, const bool lastFocus
|
||||
|
||||
// Register an event with the control to have it inform us when it gains focus.
|
||||
_gotFocusRevoker = control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
|
||||
// When our border is tapped, make sure to transfer focus to our control.
|
||||
// LOAD-BEARING: This will NOT work if the border's BorderBrush is set to
|
||||
@@ -368,15 +368,18 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect
|
||||
auto paneProfile = settings.FindProfile(_profile.value());
|
||||
if (paneProfile)
|
||||
{
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
|
||||
// We don't want to do anything if nothing is set, so check for that first
|
||||
if (static_cast<int>(paneProfile.BellStyle()) != 0)
|
||||
{
|
||||
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
}
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual))
|
||||
{
|
||||
// Bubble this event up to app host, starting with bubbling to the hosting tab
|
||||
_PaneRaiseVisualBellHandlers(nullptr);
|
||||
if (WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Audible))
|
||||
{
|
||||
// Audible is set, play the sound
|
||||
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
}
|
||||
|
||||
// raise the event with the bool value corresponding to the visual flag
|
||||
_PaneRaiseBellHandlers(nullptr, WI_IsFlagSet(paneProfile.BellStyle(), winrt::Microsoft::Terminal::Settings::Model::BellStyle::Visual));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,6 +398,16 @@ void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable cons
|
||||
_GotFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
// Event Description:
|
||||
// - Called when our control loses focus. We'll use this to trigger our LostFocus
|
||||
// callback. The tab that's hosting us should have registered a callback which
|
||||
// can be used to update its own internal focus state
|
||||
void Pane::_ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */,
|
||||
RoutedEventArgs const& /* args */)
|
||||
{
|
||||
_LostFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Fire our Closed event to tell our parent that we should be removed.
|
||||
// Arguments:
|
||||
@@ -584,6 +597,19 @@ void Pane::_FocusFirstChild()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (_root.ActualWidth() == 0 && _root.ActualHeight() == 0)
|
||||
{
|
||||
// When these sizes are 0, then the pane might still be in startup,
|
||||
// and doesn't yet have a real size. In that case, the control.Focus
|
||||
// event won't be handled until _after_ the startup events are all
|
||||
// processed. This will lead to the Tab not being notified that the
|
||||
// focus moved to a different Pane.
|
||||
//
|
||||
// In that scenario, trigger the event manually here, to correctly
|
||||
// inform the Tab that we're now focused.
|
||||
_GotFocusHandlers(shared_from_this());
|
||||
}
|
||||
|
||||
_control.Focus(FocusState::Programmatic);
|
||||
}
|
||||
else
|
||||
@@ -704,6 +730,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
|
||||
// re-attach our handler for the control's GotFocus event.
|
||||
_gotFocusRevoker = _control.GotFocus(winrt::auto_revoke, { this, &Pane::_ControlGotFocusHandler });
|
||||
_lostFocusRevoker = _control.LostFocus(winrt::auto_revoke, { this, &Pane::_ControlLostFocusHandler });
|
||||
|
||||
// If we're inheriting the "last active" state from one of our children,
|
||||
// focus our control now. This should trigger our own GotFocus event.
|
||||
@@ -1216,32 +1243,6 @@ void Pane::_SetupEntranceAnimation()
|
||||
setupAnimation(secondSize, false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the pane can be split
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// Return Value:
|
||||
// - True if the pane can be split. False otherwise.
|
||||
bool Pane::CanSplit(SplitState splitType)
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
return _CanSplit(splitType);
|
||||
}
|
||||
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
return _firstChild->CanSplit(splitType);
|
||||
}
|
||||
|
||||
if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->CanSplit(splitType);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine if a given Pane can be split, but without
|
||||
// using the ActualWidth() and ActualHeight() methods. This is used during
|
||||
@@ -1272,12 +1273,15 @@ bool Pane::CanSplit(SplitState splitType)
|
||||
// - This method is highly similar to Pane::PreCalculateAutoSplit
|
||||
std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> target,
|
||||
SplitState splitType,
|
||||
const float splitSize,
|
||||
const winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (target.get() == this)
|
||||
{
|
||||
const auto firstPrecent = 1.0f - splitSize;
|
||||
const auto secondPercent = splitSize;
|
||||
// If this pane is a leaf, and it's the pane we're looking for, use
|
||||
// the available space to calculate which direction to split in.
|
||||
const Size minSize = _GetMinSize();
|
||||
@@ -1290,17 +1294,19 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
|
||||
else if (splitType == SplitState::Vertical)
|
||||
{
|
||||
const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize;
|
||||
const auto newWidth = widthMinusSeparator * Half;
|
||||
const auto newFirstWidth = widthMinusSeparator * firstPrecent;
|
||||
const auto newSecondWidth = widthMinusSeparator * secondPercent;
|
||||
|
||||
return { newWidth > minSize.Width };
|
||||
return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width };
|
||||
}
|
||||
|
||||
else if (splitType == SplitState::Horizontal)
|
||||
{
|
||||
const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize;
|
||||
const auto newHeight = heightMinusSeparator * Half;
|
||||
const auto newFirstHeight = heightMinusSeparator * firstPrecent;
|
||||
const auto newSecondHeight = heightMinusSeparator * secondPercent;
|
||||
|
||||
return { newHeight > minSize.Height };
|
||||
return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height };
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1329,8 +1335,8 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
|
||||
(availableSpace.Height - firstHeight) - PaneBorderSize :
|
||||
availableSpace.Height;
|
||||
|
||||
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight });
|
||||
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight });
|
||||
const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, splitSize, { firstWidth, firstHeight });
|
||||
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, splitSize, { secondWidth, secondHeight });
|
||||
}
|
||||
|
||||
// We should not possibly be getting here - both the above branches should
|
||||
@@ -1348,23 +1354,26 @@ std::optional<bool> Pane::PreCalculateCanSplit(const std::shared_ptr<Pane> targe
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - The two newly created Panes
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState splitType, const GUID& profile, const TermControl& control)
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::Split(SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
const TermControl& control)
|
||||
{
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
if (_firstChild->_HasFocusedChild())
|
||||
{
|
||||
return _firstChild->Split(splitType, profile, control);
|
||||
return _firstChild->Split(splitType, splitSize, profile, control);
|
||||
}
|
||||
else if (_secondChild->_HasFocusedChild())
|
||||
{
|
||||
return _secondChild->Split(splitType, profile, control);
|
||||
return _secondChild->Split(splitType, splitSize, profile, control);
|
||||
}
|
||||
|
||||
return { nullptr, nullptr };
|
||||
}
|
||||
|
||||
return _Split(splitType, profile, control);
|
||||
return _Split(splitType, splitSize, profile, control);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1392,45 +1401,6 @@ SplitState Pane::_convertAutomaticSplitState(const SplitState& splitType) const
|
||||
return splitType;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the pane can be split.
|
||||
// Arguments:
|
||||
// - splitType: what type of split we want to create.
|
||||
// Return Value:
|
||||
// - True if the pane can be split. False otherwise.
|
||||
bool Pane::_CanSplit(SplitState splitType)
|
||||
{
|
||||
const Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
|
||||
gsl::narrow_cast<float>(_root.ActualHeight()) };
|
||||
|
||||
const Size minSize = _GetMinSize();
|
||||
|
||||
auto actualSplitType = _convertAutomaticSplitState(splitType);
|
||||
|
||||
if (actualSplitType == SplitState::None)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actualSplitType == SplitState::Vertical)
|
||||
{
|
||||
const auto widthMinusSeparator = actualSize.Width - CombinedPaneBorderSize;
|
||||
const auto newWidth = widthMinusSeparator * Half;
|
||||
|
||||
return newWidth > minSize.Width;
|
||||
}
|
||||
|
||||
if (actualSplitType == SplitState::Horizontal)
|
||||
{
|
||||
const auto heightMinusSeparator = actualSize.Height - CombinedPaneBorderSize;
|
||||
const auto newHeight = heightMinusSeparator * Half;
|
||||
|
||||
return newHeight > minSize.Height;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Does the bulk of the work of creating a new split. Initializes our UI,
|
||||
// creates a new Pane to host the control, registers event handlers.
|
||||
@@ -1440,7 +1410,10 @@ bool Pane::_CanSplit(SplitState splitType)
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - The two newly created Panes
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType, const GUID& profile, const TermControl& control)
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
const TermControl& control)
|
||||
{
|
||||
if (splitType == SplitState::None)
|
||||
{
|
||||
@@ -1463,9 +1436,10 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
||||
// control telling us that it's now focused, we want it telling its new
|
||||
// parent.
|
||||
_gotFocusRevoker.revoke();
|
||||
_lostFocusRevoker.revoke();
|
||||
|
||||
_splitState = actualSplitType;
|
||||
_desiredSplitPosition = Half;
|
||||
_desiredSplitPosition = 1.0f - splitSize;
|
||||
|
||||
// Remove any children we currently have. We can't add the existing
|
||||
// TermControl to a new grid until we do this.
|
||||
@@ -1576,9 +1550,9 @@ void Pane::Restore(std::shared_ptr<Pane> zoomedPane)
|
||||
// otherwise the ID value will not make sense (leaves have IDs, parents do not)
|
||||
// Return Value:
|
||||
// - The ID of this pane
|
||||
uint16_t Pane::Id() noexcept
|
||||
std::optional<uint16_t> Pane::Id() noexcept
|
||||
{
|
||||
return _id.value();
|
||||
return _id;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -2112,4 +2086,5 @@ std::optional<SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
||||
@@ -58,14 +58,16 @@ public:
|
||||
bool ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
bool NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::FocusDirection& direction);
|
||||
|
||||
bool CanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
|
||||
std::optional<winrt::Microsoft::Terminal::Settings::Model::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
|
||||
const winrt::Windows::Foundation::Size parentSize) const;
|
||||
std::optional<bool> PreCalculateCanSplit(const std::shared_ptr<Pane> target,
|
||||
winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
const winrt::Windows::Foundation::Size availableSpace) const;
|
||||
void Shutdown();
|
||||
void Close();
|
||||
@@ -75,13 +77,14 @@ public:
|
||||
void Maximize(std::shared_ptr<Pane> zoomedPane);
|
||||
void Restore(std::shared_ptr<Pane> zoomedPane);
|
||||
|
||||
uint16_t Id() noexcept;
|
||||
std::optional<uint16_t> Id() noexcept;
|
||||
void Id(uint16_t id) noexcept;
|
||||
void FocusPane(const uint16_t id);
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(PaneRaiseVisualBell, _PaneRaiseVisualBellHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
|
||||
|
||||
private:
|
||||
struct SnapSizeResult;
|
||||
@@ -109,6 +112,7 @@ private:
|
||||
winrt::event_token _warningBellToken{ 0 };
|
||||
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
|
||||
std::shared_mutex _createCloseLock{};
|
||||
|
||||
@@ -120,8 +124,8 @@ private:
|
||||
bool _HasFocusedChild() const noexcept;
|
||||
void _SetupChildCloseHandlers();
|
||||
|
||||
bool _CanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
@@ -142,6 +146,8 @@ private:
|
||||
winrt::Windows::Foundation::IInspectable const& e);
|
||||
void _ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _ControlLostFocusHandler(winrt::Windows::Foundation::IInspectable const& sender,
|
||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
|
||||
std::pair<float, float> _CalcChildrenSizes(const float fullSize) const;
|
||||
SnapChildrenSizeResult _CalcSnappedChildrenSizes(const bool widthOrHeight, const float fullSize) const;
|
||||
|
||||
@@ -226,6 +226,9 @@
|
||||
<value>• Found a keybinding with too many strings for the "keys" array. There should only be one string value in the "keys" array.</value>
|
||||
<comment>{Locked="\"keys\"","•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
</data>
|
||||
<data name="FailedToParseSubCommands" xml:space="preserve">
|
||||
<value>• Failed to parse all subcommands of nested command.</value>
|
||||
</data>
|
||||
<data name="MissingRequiredParameter" xml:space="preserve">
|
||||
<value>• Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
|
||||
<comment>{Locked="•"} This glyph is a bullet, used in a bulleted list.</comment>
|
||||
@@ -249,6 +252,14 @@
|
||||
<value>Found a command with an invalid "colorScheme". This command will be ignored. Make sure that when setting a "colorScheme", the value matches the "name" of a color scheme in the "schemes" list.</value>
|
||||
<comment>{Locked="\"colorScheme\"","\"name\"","\"schemes\""}</comment>
|
||||
</data>
|
||||
<data name="InvalidSplitSize" xml:space="preserve">
|
||||
<value>Found a "splitPane" command with an invalid "size". This command will be ignored. Make sure the size is between 0 and 1, exclusive.</value>
|
||||
<comment>{Locked="\"splitPane\"","\"size\""}</comment>
|
||||
</data>
|
||||
<data name="FailedToParseStartupActions" xml:space="preserve">
|
||||
<value>Failed to parse "startupActions".</value>
|
||||
<comment>{Locked="\"startupActions\""}</comment>
|
||||
</data>
|
||||
<data name="CmdCommandArgDesc" xml:space="preserve">
|
||||
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
|
||||
</data>
|
||||
@@ -268,6 +279,9 @@
|
||||
<data name="CmdFocusTabTargetArgDesc" xml:space="preserve">
|
||||
<value>Move focus the tab at the given index</value>
|
||||
</data>
|
||||
<data name="CmdSplitPaneSizeArgDesc" xml:space="preserve">
|
||||
<value>Specify the size as a percentage of the parent pane. Valid values are between (0,1), exclusive.</value>
|
||||
</data>
|
||||
<data name="CmdNewTabDesc" xml:space="preserve">
|
||||
<value>Create a new tab</value>
|
||||
</data>
|
||||
@@ -311,6 +325,16 @@
|
||||
<data name="CmdFullscreenDesc" xml:space="preserve">
|
||||
<value>Launch the window in fullscreen mode</value>
|
||||
</data>
|
||||
<data name="CmdMoveFocusDesc" xml:space="preserve">
|
||||
<value>Move focus to the adjacent pane in the specified direction</value>
|
||||
</data>
|
||||
<data name="CmdMFDesc" xml:space="preserve">
|
||||
<value>An alias for the "move-focus" subcommand.</value>
|
||||
<comment>{Locked="\"move-focus\""}</comment>
|
||||
</data>
|
||||
<data name="CmdMoveFocusDirectionArgDesc" xml:space="preserve">
|
||||
<value>The direction to move focus in</value>
|
||||
</data>
|
||||
<data name="CmdFocusDesc" xml:space="preserve">
|
||||
<value>Launch the window in focus mode</value>
|
||||
</data>
|
||||
@@ -335,9 +359,6 @@
|
||||
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Maximize</value>
|
||||
</data>
|
||||
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Maximize</value>
|
||||
</data>
|
||||
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Minimize</value>
|
||||
</data>
|
||||
@@ -398,7 +419,7 @@
|
||||
<data name="MultiLinePasteDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="MultiLinePasteDialog.Content" xml:space="preserve">
|
||||
<data name="MultiLineWarningText.Text" xml:space="preserve">
|
||||
<value>You are about to paste text that contains multiple lines. If you paste this text into your shell, it may result in the unexpected execution of commands. Do you wish to continue?</value>
|
||||
</data>
|
||||
<data name="MultiLinePasteDialog.PrimaryButtonText" xml:space="preserve">
|
||||
@@ -415,7 +436,7 @@
|
||||
</data>
|
||||
<data name="CommandPalette_ParsedCommandLine" xml:space="preserve">
|
||||
<value>Executing command line will invoke the following commands:</value>
|
||||
<comment>Will be followed by a list of strings describing parsed commands</comment>
|
||||
<comment>Will be followed by a list of strings describing parsed commands</comment>
|
||||
</data>
|
||||
<data name="CommandPalette_FailedParsingCommandLine" xml:space="preserve">
|
||||
<value>Failed parsing command line:</value>
|
||||
@@ -520,4 +541,16 @@
|
||||
<data name="NoticeWarning" xml:space="preserve">
|
||||
<value>Warning</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="ClipboardTextHeader.Text" xml:space="preserve">
|
||||
<value>Clipboard contents (preview):</value>
|
||||
</data>
|
||||
<data name="CommandPalette_MoreOptions.[using:Windows.UI.Xaml.Automation]AutomationProperties.HelpText" xml:space="preserve">
|
||||
<value>More options</value>
|
||||
</data>
|
||||
<data name="WindowMaximizeButtonToolTip" xml:space="preserve">
|
||||
<value>Maximize</value>
|
||||
</data>
|
||||
<data name="WindowRestoreDownButtonToolTip" xml:space="preserve">
|
||||
<value>Restore Down</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -22,6 +22,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - true if we handled the event was handled, else false.
|
||||
bool ShortcutActionDispatch::DoAction(const ActionAndArgs& actionAndArgs)
|
||||
{
|
||||
if (!actionAndArgs)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& action = actionAndArgs.Action();
|
||||
const auto& args = actionAndArgs.Args();
|
||||
auto eventArgs = args ? ActionEventArgs{ args } :
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace winrt::TerminalApp::implementation
|
||||
Controls::MenuFlyoutItem closeTabMenuItem;
|
||||
Controls::FontIcon closeSymbol;
|
||||
closeSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
|
||||
closeSymbol.Glyph(L"\xE8BB");
|
||||
closeSymbol.Glyph(L"\xE711");
|
||||
|
||||
closeTabMenuItem.Click([weakThis](auto&&, auto&&) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
@@ -62,7 +62,6 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Build the menu
|
||||
Controls::MenuFlyout newTabFlyout;
|
||||
newTabFlyout.Items().Append(_CreateCloseSubMenu());
|
||||
newTabFlyout.Items().Append(closeTabMenuItem);
|
||||
TabViewItem().ContextFlyout(newTabFlyout);
|
||||
}
|
||||
@@ -138,10 +137,88 @@ namespace winrt::TerminalApp::implementation
|
||||
TabViewIndex(idx);
|
||||
TabViewNumTabs(numTabs);
|
||||
_EnableCloseMenuItems();
|
||||
_UpdateSwitchToTabKeyChord();
|
||||
}
|
||||
|
||||
void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
|
||||
{
|
||||
_dispatch = dispatch;
|
||||
}
|
||||
|
||||
void TabBase::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap)
|
||||
{
|
||||
_keymap = keymap;
|
||||
_UpdateSwitchToTabKeyChord();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the key chord resulting in switch to the current tab.
|
||||
// Updates tool tip if required
|
||||
// Arguments:
|
||||
// - keyChord - string representation of the key chord that switches to the current tab
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TabBase::_UpdateSwitchToTabKeyChord()
|
||||
{
|
||||
SwitchToTabArgs args{ _TabViewIndex };
|
||||
ActionAndArgs switchToTab{ ShortcutAction::SwitchToTab, args };
|
||||
const auto keyChord = _keymap ? _keymap.GetKeyBindingForActionWithArgs(switchToTab) : nullptr;
|
||||
const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L"";
|
||||
|
||||
if (_keyChord == keyChordText)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_keyChord = keyChordText;
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
_UpdateToolTip();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a text for the title run in the tool tip by returning tab title
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The value to populate in the title run of the tool tip
|
||||
winrt::hstring TabBase::_CreateToolTipTitle()
|
||||
{
|
||||
return _Title;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets tab tool tip to a concatenation of title and key chord
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TabBase::_UpdateToolTip()
|
||||
{
|
||||
auto titleRun = WUX::Documents::Run();
|
||||
titleRun.Text(_CreateToolTipTitle());
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.TextAlignment(WUX::TextAlignment::Center);
|
||||
textBlock.Inlines().Append(titleRun);
|
||||
|
||||
if (!_keyChord.empty())
|
||||
{
|
||||
auto keyChordRun = WUX::Documents::Run();
|
||||
keyChordRun.Text(_keyChord);
|
||||
keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(keyChordRun);
|
||||
}
|
||||
|
||||
WUX::Controls::ToolTip toolTip{};
|
||||
toolTip.Content(textBlock);
|
||||
WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs);
|
||||
void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap);
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
@@ -43,12 +44,18 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{};
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{};
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr };
|
||||
winrt::hstring _keyChord{};
|
||||
|
||||
virtual void _CreateContextMenu();
|
||||
virtual winrt::hstring _CreateToolTipTitle();
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu();
|
||||
void _EnableCloseMenuItems();
|
||||
void _CloseTabsAfter();
|
||||
void _CloseOtherTabs();
|
||||
winrt::fire_and_forget _UpdateSwitchToTabKeyChord();
|
||||
void _UpdateToolTip();
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace winrt::TerminalApp::implementation
|
||||
"TabRenamerOpened",
|
||||
TraceLoggingDescription("Event emitted when the tab renamer is opened"),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -75,6 +75,14 @@ namespace winrt::TerminalApp::implementation
|
||||
void TabHeaderControl::RenameBoxLostFocusHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*e*/)
|
||||
{
|
||||
// If the context menu associated with the renamer text box is open we know it gained the focus.
|
||||
// In this case we ignore this event (we will regain the focus once the menu will be closed).
|
||||
const auto flyout = HeaderRenamerTextBox().ContextFlyout();
|
||||
if (flyout && flyout.IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Log the data here, rather than in _CloseRenameBox. If we do it there,
|
||||
// it'll get fired twice, once when the key is pressed to commit/cancel,
|
||||
// and then again when the focus is lost
|
||||
@@ -85,7 +93,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TraceLoggingDescription("Event emitted when the tab renamer is closed"),
|
||||
TraceLoggingBoolean(_renameCancelled, "CancelledRename", "True if the user cancelled the rename, false if they committed."),
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
|
||||
|
||||
_CloseRenameBox();
|
||||
if (!_renameCancelled)
|
||||
@@ -98,7 +106,11 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Hides the rename box and displays the title text block
|
||||
void TabHeaderControl::_CloseRenameBox()
|
||||
{
|
||||
HeaderRenamerTextBox().Visibility(Windows::UI::Xaml::Visibility::Collapsed);
|
||||
HeaderTextBlock().Visibility(Windows::UI::Xaml::Visibility::Visible);
|
||||
if (HeaderRenamerTextBox().Visibility() == Windows::UI::Xaml::Visibility::Visible)
|
||||
{
|
||||
HeaderRenamerTextBox().Visibility(Windows::UI::Xaml::Visibility::Collapsed);
|
||||
HeaderTextBlock().Visibility(Windows::UI::Xaml::Visibility::Visible);
|
||||
_RenameEndedHandlers(*this, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ namespace winrt::TerminalApp::implementation
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsPaneZoomed, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(double, RenamerMaxWidth, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingActive, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, IsProgressRingIndeterminate, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(bool, BellIndicator, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(uint32_t, ProgressValue, _PropertyChangedHandlers);
|
||||
|
||||
TYPED_EVENT(RenameEnded, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
bool _receivedKeyDown{ false };
|
||||
bool _renameCancelled{ false };
|
||||
|
||||
@@ -9,13 +9,16 @@ namespace TerminalApp
|
||||
{
|
||||
String Title { get; set; };
|
||||
Boolean IsPaneZoomed { get; set; };
|
||||
Double RenamerMaxWidth { get; set; };
|
||||
Boolean IsProgressRingActive { get; set; };
|
||||
Boolean IsProgressRingIndeterminate { get; set; };
|
||||
Boolean BellIndicator { get; set; };
|
||||
UInt32 ProgressValue { get; set; };
|
||||
|
||||
TabHeaderControl();
|
||||
void BeginRename();
|
||||
|
||||
event TitleChangeRequestedArgs TitleChangeRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> RenameEnded;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,25 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
mc:Ignorable="d"
|
||||
MinHeight="16">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<Thickness x:Key="TextControlBorderThemeThicknessFocused">0,0,0,1</Thickness>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<StackPanel x:Name="HeaderStackPanel"
|
||||
Orientation="Horizontal">
|
||||
<mux:ProgressRing x:Name="HeaderProgressRing"
|
||||
@@ -26,6 +43,12 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<!--We want the progress ring to 'replace' the tab icon, but we don't have control
|
||||
over the tab icon here (the tab view item does) - so we hide the tab icon there
|
||||
and use a negative margin for the progress ring here to put it where the icon would be-->
|
||||
<FontIcon x:Name="HeaderBellIndicator"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind BellIndicator, Mode=OneWay}"
|
||||
Glyph=""
|
||||
FontSize="12"
|
||||
Margin="0,0,8,0"/>
|
||||
<FontIcon x:Name="HeaderZoomIcon"
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
Visibility="{x:Bind IsPaneZoomed, Mode=OneWay}"
|
||||
@@ -39,8 +62,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
Visibility="Collapsed"
|
||||
MinHeight="0"
|
||||
Padding="4,0,4,0"
|
||||
Margin="0,-8,0,-8"
|
||||
MaxLength="1024"
|
||||
LostFocus="RenameBoxLostFocusHandler"/>
|
||||
LostFocus="RenameBoxLostFocusHandler"
|
||||
Height="16"
|
||||
FontSize="12"
|
||||
IsSpellCheckEnabled="False"
|
||||
MaxWidth="{x:Bind RenamerMaxWidth, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -19,7 +19,7 @@ using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TabPaletteItem::TabPaletteItem(winrt::TerminalApp::TabBase const& tab) :
|
||||
_Tab(tab)
|
||||
_tab(tab)
|
||||
{
|
||||
Name(tab.Title());
|
||||
Icon(tab.Icon());
|
||||
|
||||
@@ -14,9 +14,13 @@ namespace winrt::TerminalApp::implementation
|
||||
TabPaletteItem() = default;
|
||||
TabPaletteItem(winrt::TerminalApp::TabBase const& tab);
|
||||
|
||||
GETSET_PROPERTY(winrt::TerminalApp::TabBase, Tab, nullptr);
|
||||
winrt::TerminalApp::TabBase Tab() const noexcept
|
||||
{
|
||||
return _tab.get();
|
||||
}
|
||||
|
||||
private:
|
||||
winrt::weak_ref<winrt::TerminalApp::TabBase> _tab;
|
||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _tabChangedRevoker;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@
|
||||
|
||||
<ClCompile>
|
||||
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
|
||||
on being compiled with RTTI (/GR). We need this in this project, because
|
||||
we're including the CLI11 header via the CommandlineArgs project. -->
|
||||
on being compiled with RTTI (/GR). -->
|
||||
<RuntimeTypeInfo>true</RuntimeTypeInfo>
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
@@ -72,6 +71,8 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ActionPaletteItem.h" />
|
||||
<ClInclude Include="App.base.h" />
|
||||
<ClInclude Include="AppCommandlineArgs.h" />
|
||||
<ClInclude Include="Commandline.h" />
|
||||
<ClInclude Include="CommandLinePaletteItem.h" />
|
||||
<ClInclude Include="Jumplist.h" />
|
||||
<ClInclude Include="MinMaxCloseControl.h">
|
||||
@@ -143,6 +144,8 @@
|
||||
<ClCompile Include="ActionPaletteItem.cpp" />
|
||||
<ClCompile Include="CommandLinePaletteItem.cpp" />
|
||||
<ClCompile Include="init.cpp" />
|
||||
<ClCompile Include="AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="Commandline.cpp" />
|
||||
<ClCompile Include="Jumplist.cpp" />
|
||||
<ClCompile Include="MinMaxCloseControl.cpp">
|
||||
<DependentUpon>MinMaxCloseControl.xaml</DependentUpon>
|
||||
@@ -285,14 +288,6 @@
|
||||
you also update all the consumers
|
||||
-->
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\types\lib\types.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\CommandlineArgs\CommandlineArgs.vcxproj" >
|
||||
<Private>true</Private>
|
||||
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
|
||||
<!-- Make sure to set ReferenceOutputAssembly=false, or we'll try and
|
||||
MDMERGE with MUX from the CommandlineArgs output, and get duplicate type
|
||||
errors everywhere. -->
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\cascadia\WinRTUtils\WinRTUtils.vcxproj">
|
||||
<Project>{CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "pch.h"
|
||||
#include "TerminalPage.h"
|
||||
#include "Utils.h"
|
||||
#include "AppLogic.h"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
@@ -44,7 +43,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
TerminalPage::TerminalPage() :
|
||||
_tabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
|
||||
_mruTabs{ winrt::single_threaded_vector<TerminalApp::TabBase>() },
|
||||
_mruTabs{ winrt::single_threaded_observable_vector<TerminalApp::TabBase>() },
|
||||
_startupActions{ winrt::single_threaded_vector<ActionAndArgs>() },
|
||||
_hostingHwnd{}
|
||||
{
|
||||
@@ -108,7 +107,7 @@ namespace winrt::TerminalApp::implementation
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
_UpdateCommandsForPalette();
|
||||
CommandPalette().SetKeyBindings(*_bindings);
|
||||
CommandPalette().SetKeyMap(_settings.KeyMap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +135,8 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
_tabRow.PointerMoved({ this, &TerminalPage::_RestorePointerCursorHandler });
|
||||
|
||||
_tabView.CanReorderTabs(!isElevated);
|
||||
_tabView.CanDragTabs(!isElevated);
|
||||
|
||||
@@ -209,6 +210,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
page->_SplitPane(SplitState::Automatic,
|
||||
SplitType::Manual,
|
||||
0.5f,
|
||||
nullptr);
|
||||
}
|
||||
else
|
||||
@@ -253,6 +255,13 @@ namespace winrt::TerminalApp::implementation
|
||||
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
|
||||
|
||||
_isAlwaysOnTop = _settings.GlobalSettings().AlwaysOnTop();
|
||||
|
||||
// Setup mouse vanish attributes
|
||||
SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &_shouldMouseVanish, false);
|
||||
|
||||
// Store cursor, so we can restore it, e.g., after mouse vanishing
|
||||
// (we'll need to adapt this logic once we make cursor context aware)
|
||||
_defaultPointerCursor = CoreWindow::GetForCurrentThread().PointerCursor();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -560,6 +569,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
page->_SplitPane(SplitState::Automatic,
|
||||
SplitType::Manual,
|
||||
0.5f,
|
||||
newTerminalArgs);
|
||||
}
|
||||
else
|
||||
@@ -600,7 +610,9 @@ namespace winrt::TerminalApp::implementation
|
||||
settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick });
|
||||
newTabFlyout.Items().Append(settingsItem);
|
||||
|
||||
auto settingsKeyChord = keyBindings.GetKeyBindingForAction(ShortcutAction::OpenSettings);
|
||||
Microsoft::Terminal::Settings::Model::OpenSettingsArgs args{ SettingsTarget::SettingsFile };
|
||||
Microsoft::Terminal::Settings::Model::ActionAndArgs settingsAction{ ShortcutAction::OpenSettings, args };
|
||||
const auto settingsKeyChord{ keyBindings.GetKeyBindingForActionWithArgs(settingsAction) };
|
||||
if (settingsKeyChord)
|
||||
{
|
||||
_SetAcceleratorForMenuItem(settingsItem, settingsKeyChord);
|
||||
@@ -746,6 +758,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_mruTabs.Append(*newTabImpl);
|
||||
|
||||
newTabImpl->SetDispatch(*_actionDispatch);
|
||||
newTabImpl->SetKeyMap(_settings.KeyMap());
|
||||
|
||||
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
|
||||
_UpdateTabIndices();
|
||||
@@ -804,12 +817,25 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
newTabImpl->TabRenamerDeactivated([weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) {
|
||||
if (const auto page{ weakThis.get() })
|
||||
{
|
||||
if (!page->_newTabButton.Flyout().IsOpen())
|
||||
{
|
||||
if (const auto tab{ page->_GetFocusedTab() })
|
||||
{
|
||||
tab.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (debugConnection) // this will only be set if global debugging is on and tap is active
|
||||
{
|
||||
TermControl newControl{ settings, debugConnection };
|
||||
_RegisterTerminalEvents(newControl, *newTabImpl);
|
||||
// Split (auto) with the debug tap.
|
||||
newTabImpl->SplitPane(SplitState::Automatic, profileGuid, newControl);
|
||||
newTabImpl->SplitPane(SplitState::Automatic, 0.5f, profileGuid, newControl);
|
||||
}
|
||||
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
@@ -927,6 +953,56 @@ namespace winrt::TerminalApp::implementation
|
||||
_ShowAboutDialog();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// Called when the users pressed keyBindings while CommandPalette is open.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_KeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
const auto actionAndArgs = _settings.KeyMap().TryLookup(kc);
|
||||
if (actionAndArgs)
|
||||
{
|
||||
if (CommandPalette().Visibility() == Visibility::Visible && actionAndArgs.Action() != ShortcutAction::ToggleCommandPalette)
|
||||
{
|
||||
CommandPalette().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
_actionDispatch->DoAction(actionAndArgs);
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// Handles preview key on the SUI tab, by handling close tab / next tab / previous tab
|
||||
// This is a temporary solution - we need to fix all key-bindings work from SUI as long as they don't harm
|
||||
// the SUI behavior
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& /*sender*/, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
auto const ctrlDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Control), CoreVirtualKeyStates::Down);
|
||||
auto const altDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Menu), CoreVirtualKeyStates::Down);
|
||||
auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast<int32_t>(key) };
|
||||
const auto actionAndArgs = _settings.KeyMap().TryLookup(kc);
|
||||
if (actionAndArgs && (actionAndArgs.Action() == ShortcutAction::CloseTab || actionAndArgs.Action() == ShortcutAction::NextTab || actionAndArgs.Action() == ShortcutAction::PrevTab || actionAndArgs.Action() == ShortcutAction::ClosePane))
|
||||
{
|
||||
_actionDispatch->DoAction(actionAndArgs);
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Configure the AppKeyBindings to use our ShortcutActionDispatch and the updated KeyMapping
|
||||
// as the object to handle dispatching ShortcutAction events.
|
||||
@@ -1004,8 +1080,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
auto newTabTitle = tab.Title();
|
||||
|
||||
if (_settings.GlobalSettings().ShowTitleInTitlebar() &&
|
||||
tab.FocusState() != FocusState::Unfocused)
|
||||
if (_settings.GlobalSettings().ShowTitleInTitlebar() && tab == _GetFocusedTab())
|
||||
{
|
||||
_titleChangeHandlers(*this, newTabTitle);
|
||||
}
|
||||
@@ -1065,34 +1140,38 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Duplicates the current focused tab
|
||||
void TerminalPage::_DuplicateTabViewItem()
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
|
||||
const auto& profileGuid = terminalTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
const auto& profileGuid = terminalTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
{
|
||||
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
|
||||
const auto workingDirectory = terminalTab->GetActiveTerminalControl().WorkingDirectory();
|
||||
const auto validWorkingDirectory = !workingDirectory.empty();
|
||||
if (validWorkingDirectory)
|
||||
{
|
||||
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
settings.StartingDirectory(workingDirectory);
|
||||
}
|
||||
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,6 +1196,13 @@ namespace winrt::TerminalApp::implementation
|
||||
// - tabIndex: the index of the tab to be removed
|
||||
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
|
||||
{
|
||||
// We use _removing flag to suppress _OnTabSelectionChanged events
|
||||
// that might get triggered while removing
|
||||
_removing = true;
|
||||
auto unsetRemoving = wil::scope_exit([&]() noexcept { _removing = false; });
|
||||
|
||||
const auto focusedTabIndex{ _GetFocusedTabIndex() };
|
||||
|
||||
// Removing the tab from the collection should destroy its control and disconnect its connection,
|
||||
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
|
||||
auto tab{ _tabs.GetAt(tabIndex) };
|
||||
@@ -1137,43 +1223,51 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
_lastTabClosedHandlers(*this, nullptr);
|
||||
}
|
||||
else if (_isFullscreen || _rearranging || _isInFocusMode)
|
||||
else if (focusedTabIndex.has_value() && focusedTabIndex.value() == gsl::narrow_cast<uint32_t>(tabIndex))
|
||||
{
|
||||
// GH#5799 - If we're fullscreen, the TabView isn't visible. If it's
|
||||
// not Visible, it's _not_ going to raise a SelectionChanged event,
|
||||
// which is what we usually use to focus another tab. Instead, we'll
|
||||
// have to do it manually here.
|
||||
//
|
||||
// GH#7916 - Similarly, TabView isn't visible in focus mode.
|
||||
// We need to focus another tab manually from the same considerations as above.
|
||||
//
|
||||
// GH#5559 Similarly, we suppress _OnTabItemsChanged events during a
|
||||
// rearrange, so if a tab is closed while we're rearranging tabs, do
|
||||
// this manually.
|
||||
//
|
||||
// We can't use
|
||||
// auto selectedIndex = _tabView.SelectedIndex();
|
||||
// Because this will always return -1 in this scenario unfortunately.
|
||||
//
|
||||
// So, what we're going to try to do is move the focus to the tab
|
||||
// to the left, within the bounds of how many tabs we have.
|
||||
//
|
||||
// EX: we have 4 tabs: [A, B, C, D]. If we close:
|
||||
// * A (tabIndex=0): We'll want to focus tab B (now in index 0)
|
||||
// * B (tabIndex=1): We'll want to focus tab A (now in index 0)
|
||||
// * C (tabIndex=2): We'll want to focus tab B (now in index 1)
|
||||
// * D (tabIndex=3): We'll want to focus tab C (now in index 2)
|
||||
const auto newSelectedIndex = std::clamp<int32_t>(tabIndex - 1, 0, _tabs.Size());
|
||||
// _UpdatedSelectedTab will do the work of setting up the new tab as
|
||||
// the focused one, and unfocusing all the others.
|
||||
_UpdatedSelectedTab(newSelectedIndex);
|
||||
// Manually select the new tab to get focus, rather than relying on TabView since:
|
||||
// 1. We want to customize this behavior (e.g., use MRU logic)
|
||||
// 2. In fullscreen (GH#5799) and focus (GH#7916) modes the _OnTabItemsChanged is not fired
|
||||
// 3. When rearranging tabs (GH#7916) _OnTabItemsChanged is suppressed
|
||||
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
|
||||
|
||||
// Also, we need to _manually_ set the SelectedItem of the tabView
|
||||
// here. If we don't, then the TabView will technically not have a
|
||||
// selected item at all, which can make things like ClosePane not
|
||||
// work correctly.
|
||||
auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) };
|
||||
_tabView.SelectedItem(newSelectedTab.TabViewItem());
|
||||
if (tabSwitchMode == TabSwitcherMode::MostRecentlyUsed)
|
||||
{
|
||||
const auto newSelectedTab = _mruTabs.GetAt(0);
|
||||
|
||||
uint32_t newSelectedIndex;
|
||||
if (_tabs.IndexOf(newSelectedTab, newSelectedIndex))
|
||||
{
|
||||
_UpdatedSelectedTab(newSelectedIndex);
|
||||
_tabView.SelectedItem(newSelectedTab.TabViewItem());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We can't use
|
||||
// auto selectedIndex = _tabView.SelectedIndex();
|
||||
// Because this will always return -1 in this scenario unfortunately.
|
||||
//
|
||||
// So, what we're going to try to do is move the focus to the tab
|
||||
// to the left, within the bounds of how many tabs we have.
|
||||
//
|
||||
// EX: we have 4 tabs: [A, B, C, D]. If we close:
|
||||
// * A (tabIndex=0): We'll want to focus tab B (now in index 0)
|
||||
// * B (tabIndex=1): We'll want to focus tab A (now in index 0)
|
||||
// * C (tabIndex=2): We'll want to focus tab B (now in index 1)
|
||||
// * D (tabIndex=3): We'll want to focus tab C (now in index 2)
|
||||
const auto newSelectedIndex = std::clamp<int32_t>(tabIndex - 1, 0, _tabs.Size());
|
||||
// _UpdatedSelectedTab will do the work of setting up the new tab as
|
||||
// the focused one, and unfocusing all the others.
|
||||
_UpdatedSelectedTab(newSelectedIndex);
|
||||
|
||||
// Also, we need to _manually_ set the SelectedItem of the tabView
|
||||
// here. If we don't, then the TabView will technically not have a
|
||||
// selected item at all, which can make things like ClosePane not
|
||||
// work correctly.
|
||||
auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) };
|
||||
_tabView.SelectedItem(newSelectedTab.TabViewItem());
|
||||
}
|
||||
}
|
||||
|
||||
// GH#5559 - If we were in the middle of a drag/drop, end it by clearing
|
||||
@@ -1211,6 +1305,9 @@ namespace winrt::TerminalApp::implementation
|
||||
// Add an event handler for when the terminal wants to set a progress indicator on the taskbar
|
||||
term.SetTaskbarProgress({ this, &TerminalPage::_SetTaskbarProgressHandler });
|
||||
|
||||
term.HidePointerCursor({ this, &TerminalPage::_HidePointerCursorHandler });
|
||||
term.RestorePointerCursor({ this, &TerminalPage::_RestorePointerCursorHandler });
|
||||
|
||||
// Bind Tab events to the TermControl and the Tab's Pane
|
||||
hostingTab.Initialize(term);
|
||||
|
||||
@@ -1274,57 +1371,27 @@ namespace winrt::TerminalApp::implementation
|
||||
// - Sets focus to the tab to the right or left the currently selected tab.
|
||||
void TerminalPage::_SelectNextTab(const bool bMoveRight)
|
||||
{
|
||||
if (CommandPalette().Visibility() == Visibility::Visible)
|
||||
{
|
||||
// If the tab switcher is currently open, don't change its mode.
|
||||
// Just select the new tab.
|
||||
CommandPalette().SelectNextItem(bMoveRight);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index{ _GetFocusedTabIndex().value_or(0) };
|
||||
const auto tabSwitchMode = _settings.GlobalSettings().TabSwitcherMode();
|
||||
const bool useInOrderTabIndex = tabSwitchMode != TabSwitcherMode::MostRecentlyUsed;
|
||||
|
||||
// First, determine what the index of the newly selected tab should be.
|
||||
// This changes if we're doing an in-order traversal vs a MRU traversal.
|
||||
auto newTabIndex = 0;
|
||||
if (useInOrderTabIndex)
|
||||
if (tabSwitchMode == TabSwitcherMode::Disabled)
|
||||
{
|
||||
// Determine what the next in-order tab index is
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
uint32_t tabCount = _tabs.Size();
|
||||
// Wraparound math. By adding tabCount and then calculating
|
||||
// modulo tabCount, we clamp the values to the range [0,
|
||||
// tabCount) while still supporting moving leftward from 0 to
|
||||
// tabCount - 1.
|
||||
newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
}
|
||||
uint32_t tabCount = _tabs.Size();
|
||||
// Wraparound math. By adding tabCount and then calculating
|
||||
// modulo tabCount, we clamp the values to the range [0,
|
||||
// tabCount) while still supporting moving leftward from 0 to
|
||||
// tabCount - 1.
|
||||
const auto newTabIndex = ((tabCount + index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
_SelectTab(newTabIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine what the next "most recently used" index is.
|
||||
// In this case, our focused tab index (in the MRU ordering) is
|
||||
// always 0. So, going next should go to index 1, and going prev
|
||||
// should wrap to the end.
|
||||
uint32_t tabCount = _mruTabs.Size();
|
||||
newTabIndex = ((tabCount + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
}
|
||||
|
||||
const bool useTabSwitcher = tabSwitchMode != TabSwitcherMode::Disabled;
|
||||
|
||||
if (useTabSwitcher)
|
||||
{
|
||||
CommandPalette().SetTabs(useInOrderTabIndex ? _tabs : _mruTabs, true);
|
||||
CommandPalette().SetTabs(_tabs, _mruTabs);
|
||||
|
||||
// Otherwise, set up the tab switcher in the selected mode, with
|
||||
// the given ordering, and make it visible.
|
||||
CommandPalette().EnableTabSwitcherMode(false, newTabIndex);
|
||||
CommandPalette().EnableTabSwitcherMode(index, tabSwitchMode);
|
||||
CommandPalette().Visibility(Visibility::Visible);
|
||||
}
|
||||
else if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
_SelectTab(newTabIndex);
|
||||
CommandPalette().SelectNextItem(bMoveRight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1373,20 +1440,17 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalPage::_UnZoomIfNeeded()
|
||||
{
|
||||
if (auto focusedTab = _GetFocusedTab())
|
||||
if (const auto activeTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto activeTab = _GetTerminalTabImpl(focusedTab))
|
||||
if (activeTab->IsZoomed())
|
||||
{
|
||||
if (activeTab->IsZoomed())
|
||||
{
|
||||
// Remove the content from the tab first, so Pane::UnZoom can
|
||||
// re-attach the content to the tree w/in the pane
|
||||
_tabContent.Children().Clear();
|
||||
// In ExitZoom, we'll change the Tab's Content(), triggering the
|
||||
// content changed event, which will re-attach the tab's new content
|
||||
// root to the tree.
|
||||
activeTab->ExitZoom();
|
||||
}
|
||||
// Remove the content from the tab first, so Pane::UnZoom can
|
||||
// re-attach the content to the tree w/in the pane
|
||||
_tabContent.Children().Clear();
|
||||
// In ExitZoom, we'll change the Tab's Content(), triggering the
|
||||
// content changed event, which will re-attach the tab's new content
|
||||
// root to the tree.
|
||||
activeTab->ExitZoom();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1401,24 +1465,18 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalPage::_MoveFocus(const FocusDirection& direction)
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
{
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->NavigateFocus(direction);
|
||||
}
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->NavigateFocus(direction);
|
||||
}
|
||||
}
|
||||
|
||||
TermControl TerminalPage::_GetActiveControl()
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
{
|
||||
return terminalTab->GetActiveTerminalControl();
|
||||
}
|
||||
return terminalTab->GetActiveTerminalControl();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1443,7 +1501,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Method Description:
|
||||
// - returns a com_ptr to the currently focused tab. This might return null,
|
||||
// so make sure to check the result!
|
||||
winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab()
|
||||
winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab() const noexcept
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
@@ -1452,6 +1510,18 @@ namespace winrt::TerminalApp::implementation
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - returns a com_ptr to the currently focused tab implementation. This might return null,
|
||||
// so make sure to check the result!
|
||||
winrt::com_ptr<TerminalTab> TerminalPage::_GetFocusedTabImpl() const noexcept
|
||||
{
|
||||
if (auto tab{ _GetFocusedTab() })
|
||||
{
|
||||
return _GetTerminalTabImpl(tab);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - An async method for changing the focused tab on the UI thread. This
|
||||
// method will _only_ set the selected item of the TabView, which will
|
||||
@@ -1493,12 +1563,16 @@ namespace winrt::TerminalApp::implementation
|
||||
// tab's Closed event.
|
||||
void TerminalPage::_CloseFocusedPane()
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->ClosePane();
|
||||
}
|
||||
else if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
if (_tabs.GetAt(*index).try_as<TerminalApp::SettingsTab>())
|
||||
{
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->ClosePane();
|
||||
_RemoveTabViewItemByIndex(*index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1542,26 +1616,23 @@ namespace winrt::TerminalApp::implementation
|
||||
// - rowsToScroll: a number of lines to move the viewport. If not provided we will use a system default.
|
||||
void TerminalPage::_Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll)
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
uint32_t realRowsToScroll;
|
||||
if (rowsToScroll == nullptr)
|
||||
{
|
||||
uint32_t realRowsToScroll;
|
||||
if (rowsToScroll == nullptr)
|
||||
{
|
||||
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
|
||||
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
|
||||
terminalTab->GetActiveTerminalControl().GetViewHeight() :
|
||||
_systemRowsToScroll;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use the custom value specified in the command
|
||||
realRowsToScroll = rowsToScroll.Value();
|
||||
}
|
||||
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
|
||||
terminalTab->Scroll(scrollDelta);
|
||||
// The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page
|
||||
realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ?
|
||||
terminalTab->GetActiveTerminalControl().GetViewHeight() :
|
||||
_systemRowsToScroll;
|
||||
}
|
||||
else
|
||||
{
|
||||
// use the custom value specified in the command
|
||||
realRowsToScroll = rowsToScroll.Value();
|
||||
}
|
||||
auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll);
|
||||
terminalTab->Scroll(scrollDelta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1578,6 +1649,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// configurations. See CascadiaSettings::BuildSettings for more details.
|
||||
void TerminalPage::_SplitPane(const SplitState splitType,
|
||||
const SplitType splitMode,
|
||||
const float splitSize,
|
||||
const NewTerminalArgs& newTerminalArgs)
|
||||
{
|
||||
// Do nothing if we're requesting no split.
|
||||
@@ -1586,17 +1658,9 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
auto indexOpt = _GetFocusedTabIndex();
|
||||
const auto focusedTab{ _GetFocusedTabImpl() };
|
||||
|
||||
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
|
||||
if (!indexOpt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto focusedTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt));
|
||||
|
||||
// Do nothing if the focused tab isn't a TerminalTab
|
||||
// Do nothing if no TerminalTab is focused
|
||||
if (!focusedTab)
|
||||
{
|
||||
return;
|
||||
@@ -1615,6 +1679,12 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = { winrt::make<TerminalSettings>(_settings, current_guid.value(), *_bindings) };
|
||||
const auto workingDirectory = focusedTab->GetActiveTerminalControl().WorkingDirectory();
|
||||
const auto validWorkingDirectory = !workingDirectory.empty();
|
||||
if (validWorkingDirectory)
|
||||
{
|
||||
controlSettings.StartingDirectory(workingDirectory);
|
||||
}
|
||||
realGuid = current_guid.value();
|
||||
}
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
@@ -1647,7 +1717,7 @@ namespace winrt::TerminalApp::implementation
|
||||
realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace);
|
||||
}
|
||||
|
||||
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, availableSpace);
|
||||
const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, splitSize, availableSpace);
|
||||
if (!canSplit)
|
||||
{
|
||||
return;
|
||||
@@ -1660,7 +1730,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_UnZoomIfNeeded();
|
||||
|
||||
focusedTab->SplitPane(realSplitType, realGuid, newControl);
|
||||
focusedTab->SplitPane(realSplitType, splitSize, realGuid, newControl);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
@@ -1675,13 +1745,10 @@ namespace winrt::TerminalApp::implementation
|
||||
// - <none>
|
||||
void TerminalPage::_ResizePane(const ResizeDirection& direction)
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
{
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->ResizePane(direction);
|
||||
}
|
||||
_UnZoomIfNeeded();
|
||||
terminalTab->ResizePane(direction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1692,14 +1759,8 @@ namespace winrt::TerminalApp::implementation
|
||||
// - scrollDirection: ScrollUp will move the viewport up, ScrollDown will move the viewport down
|
||||
void TerminalPage::_ScrollPage(ScrollDirection scrollDirection)
|
||||
{
|
||||
auto indexOpt = _GetFocusedTabIndex();
|
||||
// Do nothing if for some reason, there's no tab in focus. We don't want to crash.
|
||||
if (!indexOpt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)))
|
||||
// Do nothing if for some reason, there's no terminal tab in focus. We don't want to crash.
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
const auto control = _GetActiveControl();
|
||||
const auto termHeight = control.GetViewHeight();
|
||||
@@ -1710,13 +1771,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TerminalPage::_ScrollToBufferEdge(ScrollDirection scrollDirection)
|
||||
{
|
||||
if (const auto indexOpt = _GetFocusedTabIndex())
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)))
|
||||
{
|
||||
auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX);
|
||||
terminalTab->Scroll(scrollDelta);
|
||||
}
|
||||
auto scrollDelta = _ComputeScrollDelta(scrollDirection, INT_MAX);
|
||||
terminalTab->Scroll(scrollDelta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1826,12 +1884,9 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (_settings && _settings.GlobalSettings().SnapToGridOnResize())
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
if (const auto terminalTab{ _GetFocusedTabImpl() })
|
||||
{
|
||||
if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index)))
|
||||
{
|
||||
return terminalTab->CalcSnappedDimension(widthOrHeight, dimension);
|
||||
}
|
||||
return terminalTab->CalcSnappedDimension(widthOrHeight, dimension);
|
||||
}
|
||||
}
|
||||
return dimension;
|
||||
@@ -1927,8 +1982,9 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
const bool hasNewLine = std::find(text.cbegin(), text.cend(), L'\n') != text.cend();
|
||||
const bool warnMultiLine = hasNewLine && _settings.GlobalSettings().WarnAboutMultiLinePaste();
|
||||
const auto isNewLineLambda = [](auto c) { return c == L'\n' || c == L'\r'; };
|
||||
const auto hasNewLine = std::find_if(text.cbegin(), text.cend(), isNewLineLambda) != text.cend();
|
||||
const auto warnMultiLine = hasNewLine && _settings.GlobalSettings().WarnAboutMultiLinePaste();
|
||||
|
||||
constexpr const std::size_t minimumSizeForWarning = 1024 * 5; // 5 KiB
|
||||
const bool warnLargeText = text.size() > minimumSizeForWarning &&
|
||||
@@ -1938,6 +1994,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// We have to initialize the dialog here to be able to change the text of the text block within it
|
||||
FindName(L"MultiLinePasteDialog").try_as<WUX::Controls::ContentDialog>();
|
||||
ClipboardText().Text(text);
|
||||
|
||||
// The vertical offset on the scrollbar does not reset automatically, so reset it manually
|
||||
ClipboardContentScrollViewer().ScrollToVerticalOffset(0);
|
||||
|
||||
ContentDialogResult warningResult;
|
||||
if (warnMultiLine)
|
||||
{
|
||||
@@ -1948,6 +2011,9 @@ namespace winrt::TerminalApp::implementation
|
||||
warningResult = co_await _ShowLargePasteWarningDialog();
|
||||
}
|
||||
|
||||
// Clear the clipboard text so it doesn't lie around in memory
|
||||
ClipboardText().Text(L"");
|
||||
|
||||
if (warningResult != ContentDialogResult::Primary)
|
||||
{
|
||||
// user rejected the paste
|
||||
@@ -2188,7 +2254,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// here, then the user won't be able to navigate the ATS any
|
||||
// longer.
|
||||
//
|
||||
// When the tab swither is eventually dismissed, the focus will
|
||||
// When the tab switcher is eventually dismissed, the focus will
|
||||
// get tossed back to the focused terminal control, so we don't
|
||||
// need to worry about focus getting lost.
|
||||
if (CommandPalette().Visibility() != Visibility::Visible)
|
||||
@@ -2197,8 +2263,13 @@ namespace winrt::TerminalApp::implementation
|
||||
_UpdateMRUTab(index);
|
||||
}
|
||||
|
||||
tab.TabViewItem().StartBringIntoView();
|
||||
|
||||
// Raise an event that our title changed
|
||||
_titleChangeHandlers(*this, tab.Title());
|
||||
if (_settings.GlobalSettings().ShowTitleInTitlebar())
|
||||
{
|
||||
_titleChangeHandlers(*this, tab.Title());
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
@@ -2212,7 +2283,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - eventArgs: the event's constituent arguments
|
||||
void TerminalPage::_OnTabSelectionChanged(const IInspectable& sender, const WUX::Controls::SelectionChangedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
if (!_rearranging)
|
||||
if (!_rearranging && !_removing)
|
||||
{
|
||||
auto tabView = sender.as<MUX::Controls::TabView>();
|
||||
auto selectedIndex = tabView.SelectedIndex();
|
||||
@@ -2309,6 +2380,9 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
settingsTab.UpdateSettings(_settings);
|
||||
}
|
||||
|
||||
auto tabImpl{ winrt::get_self<TabBase>(tab) };
|
||||
tabImpl->SetKeyMap(_settings.KeyMap());
|
||||
}
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
@@ -2353,7 +2427,7 @@ namespace winrt::TerminalApp::implementation
|
||||
IVectorView<Profile> profiles,
|
||||
IMapView<winrt::hstring, ColorScheme> schemes)
|
||||
{
|
||||
IVector<SettingsLoadWarnings> warnings;
|
||||
IVector<SettingsLoadWarnings> warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() };
|
||||
|
||||
std::vector<ColorScheme> sortedSchemes;
|
||||
sortedSchemes.reserve(schemes.Size());
|
||||
@@ -2698,7 +2772,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - an empty list if we failed to parse, otherwise a list of actions to execute.
|
||||
std::vector<ActionAndArgs> TerminalPage::ConvertExecuteCommandlineToActions(const ExecuteCommandlineArgs& args)
|
||||
{
|
||||
::CommandlineArgs::AppCommandlineArgs appArgs;
|
||||
::TerminalApp::AppCommandlineArgs appArgs;
|
||||
if (appArgs.ParseArgs(args) == 0)
|
||||
{
|
||||
return appArgs.GetStartupActions();
|
||||
@@ -2781,6 +2855,8 @@ namespace winrt::TerminalApp::implementation
|
||||
sui.SetHostingWindow(reinterpret_cast<uint64_t>(*_hostingHwnd));
|
||||
}
|
||||
|
||||
sui.PreviewKeyDown({ this, &TerminalPage::_SUIPreviewKeyDownHandler });
|
||||
|
||||
sui.OpenJson([weakThis{ get_weak() }](auto&& /*s*/, winrt::Microsoft::Terminal::Settings::Model::SettingsTarget e) {
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
@@ -2795,6 +2871,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_mruTabs.Append(*newTabImpl);
|
||||
|
||||
newTabImpl->SetDispatch(*_actionDispatch);
|
||||
newTabImpl->SetKeyMap(_settings.KeyMap());
|
||||
|
||||
// Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command.
|
||||
_UpdateTabIndices();
|
||||
@@ -2837,7 +2914,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// Return Value:
|
||||
// - If the tab is a TerminalTab, a com_ptr to the implementation type.
|
||||
// If the tab is not a TerminalTab, nullptr
|
||||
winrt::com_ptr<TerminalTab> TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) const
|
||||
winrt::com_ptr<TerminalTab> TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab)
|
||||
{
|
||||
if (auto terminalTab = tab.try_as<TerminalApp::TerminalTab>())
|
||||
{
|
||||
@@ -2997,6 +3074,32 @@ namespace winrt::TerminalApp::implementation
|
||||
return text;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Hides cursor if required
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_HidePointerCursorHandler(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
|
||||
{
|
||||
if (_shouldMouseVanish && !_isMouseHidden)
|
||||
{
|
||||
CoreWindow::GetForCurrentThread().PointerCursor(nullptr);
|
||||
_isMouseHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Restores cursor if required
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_RestorePointerCursorHandler(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
|
||||
{
|
||||
if (_isMouseHidden)
|
||||
{
|
||||
CoreWindow::GetForCurrentThread().PointerCursor(_defaultPointerCursor);
|
||||
_isMouseHidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
|
||||
#include "../CommandlineArgs/AppCommandlineArgs.h"
|
||||
#include "AppCommandlineArgs.h"
|
||||
|
||||
static constexpr uint32_t DefaultRowsToScroll{ 3 };
|
||||
static constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
|
||||
@@ -112,8 +112,8 @@ namespace winrt::TerminalApp::implementation
|
||||
Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _tabs;
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::TabBase> _mruTabs;
|
||||
winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab) const;
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _mruTabs;
|
||||
static winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab);
|
||||
|
||||
void _UpdateTabIndices();
|
||||
|
||||
@@ -126,6 +126,7 @@ namespace winrt::TerminalApp::implementation
|
||||
bool _rearranging;
|
||||
std::optional<int> _rearrangeFrom;
|
||||
std::optional<int> _rearrangeTo;
|
||||
bool _removing{ false };
|
||||
|
||||
uint32_t _systemRowsToScroll{ DefaultRowsToScroll };
|
||||
|
||||
@@ -157,6 +158,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _AboutButtonOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
void _ThirdPartyNoticesOnClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept;
|
||||
void _RegisterActionCallbacks();
|
||||
|
||||
@@ -182,7 +185,9 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl();
|
||||
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
|
||||
TerminalApp::TabBase _GetFocusedTab();
|
||||
TerminalApp::TabBase _GetFocusedTab() const noexcept;
|
||||
winrt::com_ptr<TerminalTab> _GetFocusedTabImpl() const noexcept;
|
||||
|
||||
winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex);
|
||||
void _CloseFocusedTab();
|
||||
void _CloseFocusedPane();
|
||||
@@ -193,8 +198,13 @@ namespace winrt::TerminalApp::implementation
|
||||
// Todo: add more event implementations here
|
||||
// MSFT:20641986: Add keybindings for New Window
|
||||
void _Scroll(ScrollDirection scrollDirection, const Windows::Foundation::IReference<uint32_t>& rowsToScroll);
|
||||
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitState splitType, const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual, const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
|
||||
|
||||
void _SplitPane(const Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const Microsoft::Terminal::Settings::Model::SplitType splitMode = Microsoft::Terminal::Settings::Model::SplitType::Manual,
|
||||
const float splitSize = 0.5f,
|
||||
const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs = nullptr);
|
||||
void _ResizePane(const Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
|
||||
void _ScrollPage(ScrollDirection scrollDirection);
|
||||
void _ScrollToBufferEdge(ScrollDirection scrollDirection);
|
||||
void _SetAcceleratorForMenuItem(Windows::UI::Xaml::Controls::MenuFlyoutItem& menuItem, const winrt::Microsoft::Terminal::TerminalControl::KeyChord& keyChord);
|
||||
@@ -245,16 +255,19 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _OpenSettingsUI();
|
||||
|
||||
void _MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index);
|
||||
|
||||
static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll);
|
||||
static uint32_t _ReadSystemRowsToScroll();
|
||||
|
||||
void _UpdateTabSwitcherCommands(const bool mru);
|
||||
void _UpdateMRUTab(const uint32_t index);
|
||||
|
||||
void _TryMoveTab(const uint32_t currentTabIndex, const int32_t suggestedNewTabIndex);
|
||||
|
||||
bool _shouldMouseVanish{ false };
|
||||
bool _isMouseHidden{ false };
|
||||
Windows::UI::Core::CoreCursor _defaultPointerCursor{ nullptr };
|
||||
void _HidePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _RestorePointerCursorHandler(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
|
||||
#pragma region ActionHandlers
|
||||
// These are all defined in AppActionHandlers.cpp
|
||||
void _HandleOpenNewTabDropdown(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);
|
||||
|
||||
@@ -58,6 +58,26 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
x:Name="MultiLinePasteDialog"
|
||||
x:Uid="MultiLinePasteDialog"
|
||||
DefaultButton="Primary">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="MultiLineWarningText"
|
||||
TextWrapping="Wrap">
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
x:Uid="ClipboardTextHeader"
|
||||
Margin="0,16,0,0">
|
||||
</TextBlock>
|
||||
<ScrollViewer
|
||||
Margin="0,8,0,0"
|
||||
x:Name="ClipboardContentScrollViewer"
|
||||
MaxHeight="100">
|
||||
<TextBlock
|
||||
x:Name="ClipboardText"
|
||||
TextWrapping="Wrap"
|
||||
FontFamily="Cascadia Mono">
|
||||
</TextBlock>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
@@ -95,6 +115,7 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
Visibility="Collapsed"
|
||||
PreviewKeyDown ="_KeyDownHandler"
|
||||
VerticalAlignment="Stretch" />
|
||||
|
||||
<mux:InfoBar x:Name="KeyboardWarningInfoBar"
|
||||
|
||||
@@ -51,10 +51,28 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
_UpdateHeaderControlMaxWidth();
|
||||
|
||||
// Use our header control as the TabViewItem's header
|
||||
TabViewItem().Header(_headerControl);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the timer for the bell indicator in the tab header fires
|
||||
// - Removes the bell indicator from the tab header
|
||||
// Arguments:
|
||||
// - sender, e: not used
|
||||
void TerminalTab::_BellIndicatorTimerTick(Windows::Foundation::IInspectable const& /*sender*/, Windows::Foundation::IInspectable const& /*e*/)
|
||||
{
|
||||
ShowBellIndicator(false);
|
||||
// Just do a sanity check that the timer still exists before we stop it
|
||||
if (_bellIndicatorTimer.has_value())
|
||||
{
|
||||
_bellIndicatorTimer->Stop();
|
||||
_bellIndicatorTimer = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes a TabViewItem for this Tab instance.
|
||||
// Arguments:
|
||||
@@ -76,6 +94,26 @@ namespace winrt::TerminalApp::implementation
|
||||
_RecalculateAndApplyTabColor();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TerminalTab::_UpdateHeaderControlMaxWidth()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() };
|
||||
if (settings.GlobalSettings().TabWidthMode() == winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::SizeToContent)
|
||||
{
|
||||
tab->_headerControl.RenamerMaxWidth(HeaderRenameBoxWidthTitleLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
tab->_headerControl.RenamerMaxWidth(HeaderRenameBoxWidthDefault);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns nullptr if no children of this tab were the last control to be
|
||||
// focused, or the TermControl that _was_ the last control to be focused (if
|
||||
@@ -123,6 +161,11 @@ namespace winrt::TerminalApp::implementation
|
||||
lastFocusedControl.Focus(_focusState);
|
||||
lastFocusedControl.TaskbarProgressChanged();
|
||||
}
|
||||
// When we gain focus, remove the bell indicator if it is active
|
||||
if (_headerControl.BellIndicator())
|
||||
{
|
||||
ShowBellIndicator(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +206,9 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalTab::UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings, const GUID& profile)
|
||||
{
|
||||
_rootPane->UpdateSettings(settings, profile);
|
||||
|
||||
// The tabWidthMode may have changed, update the header control accordingly
|
||||
_UpdateHeaderControlMaxWidth();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -213,17 +259,57 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
if (hide)
|
||||
if (tab->_iconHidden != hide)
|
||||
{
|
||||
Icon({});
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
|
||||
tab->_iconHidden = true;
|
||||
if (hide)
|
||||
{
|
||||
Icon({});
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
|
||||
}
|
||||
else
|
||||
{
|
||||
Icon(_lastIconPath);
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
|
||||
}
|
||||
tab->_iconHidden = hide;
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Hide or show the bell indicator in the tab header
|
||||
// Arguments:
|
||||
// - show: if true, we show the indicator; if false, we hide the indicator
|
||||
winrt::fire_and_forget TerminalTab::ShowBellIndicator(const bool show)
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_headerControl.BellIndicator(show);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Activates the timer for the bell indicator in the tab
|
||||
// - Called if a bell raised when the tab already has focus
|
||||
winrt::fire_and_forget TerminalTab::ActivateBellIndicatorTimer()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(TabViewItem().Dispatcher());
|
||||
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
if (!tab->_bellIndicatorTimer.has_value())
|
||||
{
|
||||
Icon(_lastIconPath);
|
||||
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
|
||||
tab->_iconHidden = false;
|
||||
DispatcherTimer bellIndicatorTimer;
|
||||
bellIndicatorTimer.Interval(std::chrono::milliseconds(2000));
|
||||
bellIndicatorTimer.Tick({ get_weak(), &TerminalTab::_BellIndicatorTimerTick });
|
||||
bellIndicatorTimer.Start();
|
||||
tab->_bellIndicatorTimer.emplace(std::move(bellIndicatorTimer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,6 +351,8 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Update the control to reflect the changed title
|
||||
_headerControl.Title(activeTitle);
|
||||
Automation::AutomationProperties::SetName(tab->TabViewItem(), activeTitle);
|
||||
_UpdateToolTip();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,18 +371,7 @@ namespace winrt::TerminalApp::implementation
|
||||
co_await winrt::resume_foreground(control.Dispatcher());
|
||||
|
||||
const auto currentOffset = control.GetScrollOffset();
|
||||
control.ScrollViewport(currentOffset + delta);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Determines whether the focused pane has sufficient space to be split.
|
||||
// Arguments:
|
||||
// - splitType: The type of split we want to create.
|
||||
// Return Value:
|
||||
// - True if the focused pane can be split. False otherwise.
|
||||
bool TerminalTab::CanSplitPane(SplitState splitType)
|
||||
{
|
||||
return _activePane->CanSplit(splitType);
|
||||
control.ScrollViewport(::base::ClampAdd(currentOffset, delta));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -306,14 +383,27 @@ namespace winrt::TerminalApp::implementation
|
||||
// - control: A TermControl to use in the new pane.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalTab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control)
|
||||
void TerminalTab::SplitPane(SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
TermControl& control)
|
||||
{
|
||||
// Make sure to take the ID before calling Split() - Split() will clear out the active pane's ID
|
||||
const auto activePaneId = _activePane->Id();
|
||||
auto [first, second] = _activePane->Split(splitType, profile, control);
|
||||
first->Id(activePaneId);
|
||||
second->Id(_nextPaneId);
|
||||
++_nextPaneId;
|
||||
auto [first, second] = _activePane->Split(splitType, splitSize, profile, control);
|
||||
if (activePaneId)
|
||||
{
|
||||
first->Id(activePaneId.value());
|
||||
second->Id(_nextPaneId);
|
||||
++_nextPaneId;
|
||||
}
|
||||
else
|
||||
{
|
||||
first->Id(_nextPaneId);
|
||||
++_nextPaneId;
|
||||
second->Id(_nextPaneId);
|
||||
++_nextPaneId;
|
||||
}
|
||||
_activePane = first;
|
||||
_AttachEventHandlersToControl(control);
|
||||
|
||||
@@ -531,16 +621,18 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// We need to move the pane to the top of our mru list
|
||||
// If its already somewhere in the list, remove it first
|
||||
const auto paneId = pane->Id();
|
||||
for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i)
|
||||
if (const auto paneId = pane->Id())
|
||||
{
|
||||
if (*i == paneId)
|
||||
for (auto i = _mruPanes.begin(); i != _mruPanes.end(); ++i)
|
||||
{
|
||||
_mruPanes.erase(i);
|
||||
break;
|
||||
if (*i == paneId.value())
|
||||
{
|
||||
_mruPanes.erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_mruPanes.insert(_mruPanes.begin(), paneId.value());
|
||||
}
|
||||
_mruPanes.insert(_mruPanes.begin(), paneId);
|
||||
// Raise our own ActivePaneChanged event.
|
||||
_ActivePaneChangedHandlers();
|
||||
}
|
||||
@@ -563,10 +655,30 @@ namespace winrt::TerminalApp::implementation
|
||||
// Do nothing if the Tab's lifetime is expired or pane isn't new.
|
||||
auto tab{ weakThis.get() };
|
||||
|
||||
if (tab && sender != tab->_activePane)
|
||||
if (tab)
|
||||
{
|
||||
tab->_UpdateActivePane(sender);
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
if (sender != tab->_activePane)
|
||||
{
|
||||
tab->_UpdateActivePane(sender);
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
}
|
||||
tab->_focusState = WUX::FocusState::Programmatic;
|
||||
// This tab has gained focus, remove the bell indicator if it is active
|
||||
if (tab->_headerControl.BellIndicator())
|
||||
{
|
||||
tab->ShowBellIndicator(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pane->LostFocus([weakThis](std::shared_ptr<Pane> /*sender*/) {
|
||||
// Do nothing if the Tab's lifetime is expired or pane isn't new.
|
||||
auto tab{ weakThis.get() };
|
||||
|
||||
if (tab)
|
||||
{
|
||||
// update this tab's focus state
|
||||
tab->_focusState = WUX::FocusState::Unfocused;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -597,13 +709,27 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// Add a PaneRaiseVisualBell event handler to the Pane. When the pane emits this event,
|
||||
// we need to bubble it all the way to app host. In this part of the chain we bubble it
|
||||
// from the hosting tab to the page.
|
||||
pane->PaneRaiseVisualBell([weakThis](auto&& /*s*/) {
|
||||
// Add a PaneRaiseBell event handler to the Pane
|
||||
pane->PaneRaiseBell([weakThis](auto&& /*s*/, auto&& visual) {
|
||||
if (auto tab{ weakThis.get() })
|
||||
{
|
||||
tab->_TabRaiseVisualBellHandlers();
|
||||
if (visual)
|
||||
{
|
||||
// If visual is set, we need to bubble this event all the way to app host to flash the taskbar
|
||||
// In this part of the chain we bubble it from the hosting tab to the page
|
||||
tab->_TabRaiseVisualBellHandlers();
|
||||
}
|
||||
|
||||
// Show the bell indicator in the tab header
|
||||
tab->ShowBellIndicator(true);
|
||||
|
||||
// If this tab is focused, activate the bell indicator timer, which will
|
||||
// remove the bell indicator once it fires
|
||||
// (otherwise, the indicator is removed when the tab gets focus)
|
||||
if (tab->_focusState != WUX::FocusState::Unfocused)
|
||||
{
|
||||
tab->ActivateBellIndicatorTimer();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -688,7 +814,6 @@ namespace winrt::TerminalApp::implementation
|
||||
newTabFlyout.Items().Append(chooseColorMenuItem);
|
||||
newTabFlyout.Items().Append(renameTabMenuItem);
|
||||
newTabFlyout.Items().Append(menuSeparator);
|
||||
newTabFlyout.Items().Append(_CreateCloseSubMenu());
|
||||
newTabFlyout.Items().Append(closeTabMenuItem);
|
||||
TabViewItem().ContextFlyout(newTabFlyout);
|
||||
}
|
||||
@@ -936,9 +1061,11 @@ namespace winrt::TerminalApp::implementation
|
||||
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
|
||||
}
|
||||
|
||||
bool TerminalTab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const
|
||||
bool TerminalTab::PreCalculateCanSplit(SplitState splitType,
|
||||
const float splitSize,
|
||||
winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false);
|
||||
return _rootPane->PreCalculateCanSplit(_activePane, splitType, splitSize, availableSpace).value_or(false);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -984,6 +1111,27 @@ namespace winrt::TerminalApp::implementation
|
||||
return _zoomedPane != nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a text for the title run in the tool tip by returning tab title
|
||||
// or <profile name>: <tab title> in the case the profile name differs from the title
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The value to populate in the title run of the tool tip
|
||||
winrt::hstring TerminalTab::_CreateToolTipTitle()
|
||||
{
|
||||
if (const auto& control{ GetActiveTerminalControl() })
|
||||
{
|
||||
const auto profileName{ control.Settings().ProfileName() };
|
||||
if (profileName != Title())
|
||||
{
|
||||
return fmt::format(L"{}: {}", profileName, Title()).data();
|
||||
}
|
||||
}
|
||||
|
||||
return Title();
|
||||
}
|
||||
|
||||
DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
|
||||
DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "TabBase.h"
|
||||
#include "TerminalTab.g.h"
|
||||
|
||||
static constexpr double HeaderRenameBoxWidthDefault{ 165 };
|
||||
static constexpr double HeaderRenameBoxWidthTitleLength{ std::numeric_limits<double>::infinity() };
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -30,15 +33,22 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::fire_and_forget Scroll(const int delta);
|
||||
|
||||
bool CanSplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType);
|
||||
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, const GUID& profile, winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void SplitPane(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
const GUID& profile,
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
winrt::fire_and_forget HideIcon(const bool hide);
|
||||
|
||||
winrt::fire_and_forget ShowBellIndicator(const bool show);
|
||||
winrt::fire_and_forget ActivateBellIndicatorTimer();
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
winrt::Microsoft::Terminal::Settings::Model::SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
|
||||
bool PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const;
|
||||
bool PreCalculateCanSplit(winrt::Microsoft::Terminal::Settings::Model::SplitState splitType,
|
||||
const float splitSize,
|
||||
winrt::Windows::Foundation::Size availableSpace) const;
|
||||
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
void ResizePane(const winrt::Microsoft::Terminal::Settings::Model::ResizeDirection& direction);
|
||||
@@ -71,6 +81,7 @@ namespace winrt::TerminalApp::implementation
|
||||
DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
|
||||
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
|
||||
DECLARE_EVENT(TabRaiseVisualBell, _TabRaiseVisualBellHandlers, winrt::delegate<>);
|
||||
FORWARDED_TYPED_EVENT(TabRenamerDeactivated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable, (&_headerControl), RenameEnded);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Pane> _rootPane{ nullptr };
|
||||
@@ -96,9 +107,15 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
std::optional<Windows::UI::Xaml::DispatcherTimer> _bellIndicatorTimer;
|
||||
void _BellIndicatorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
|
||||
void _MakeTabViewItem();
|
||||
|
||||
winrt::fire_and_forget _UpdateHeaderControlMaxWidth();
|
||||
|
||||
void _CreateContextMenu() override;
|
||||
virtual winrt::hstring _CreateToolTipTitle() override;
|
||||
|
||||
void _RefreshVisualState();
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<packages>
|
||||
<package id="Microsoft.Toolkit.Win32.UI.XamlApplication" version="6.1.2" targetFramework="native" />
|
||||
<package id="Microsoft.UI.Xaml" version="2.5.0-prerelease.201202003" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.201017.1" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.210309.3" targetFramework="native" />
|
||||
</packages>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user