Merge remote-tracking branch 'origin/main' into dev/migrie/f/remoting.dll

This commit is contained in:
Mike Griese 2021-01-05 11:19:08 -06:00
commit fa2df47898
87 changed files with 2282 additions and 1105 deletions

View file

@ -1,6 +1,6 @@
# Dictionaries are lists of words to accept unconditionally
While check spelling will complain about a whitelisted word
While check spelling will complain about an expected word
which is no longer present, you can include things here even if
they are not otherwise present in the repository.

View file

@ -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.

View file

@ -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
View 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.

View file

@ -369,6 +369,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"
}
}
}

View file

@ -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:
@ -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
{

View file

@ -20,6 +20,7 @@ Revision History:
#pragma once
#include "boost/container/small_vector.hpp"
#include "TextAttributeRun.hpp"
#include "AttrRowIterator.hpp"
@ -28,7 +29,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;
~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;
void Reset(const TextAttribute attr);
@ -65,7 +75,7 @@ public:
friend class AttrRowIterator;
private:
std::vector<TextAttributeRun> _list;
boost::container::small_vector<TextAttributeRun, 1> _list;
size_t _cchRowWidth;
#ifdef UNIT_TESTING

View file

@ -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);

View file

@ -15,6 +15,7 @@ Author(s):
#pragma once
#include "boost/container/small_vector.hpp"
#include "TextAttribute.hpp"
#include "TextAttributeRun.hpp"
@ -38,20 +39,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;

View file

@ -15,13 +15,16 @@
// Return Value:
// - instantiated object
// Note: will through if unable to allocate char/attribute buffers
CharRow::CharRow(size_t rowWidth, ROW* const pParent) :
#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 :
_wrapForced{ false },
_doubleBytePadded{ false },
_data(rowWidth, value_type()),
_pParent{ FAIL_FAST_IF_NULL(pParent) }
{
}
#pragma warning(pop)
// Routine Description:
// - Sets the wrap status for the current row
@ -141,7 +144,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 +158,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;

View file

@ -24,6 +24,7 @@ Revision History:
#include "CharRowCellReference.hpp"
#include "CharRowCell.hpp"
#include "UnicodeStorage.hpp"
#include "boost/container/small_vector.hpp"
class ROW;
@ -49,11 +50,12 @@ 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;
@ -63,7 +65,7 @@ public:
void Reset() noexcept;
[[nodiscard]] HRESULT Resize(const size_t newSize) noexcept;
size_t MeasureLeft() const noexcept;
size_t MeasureRight() const noexcept;
size_t MeasureRight() const;
void ClearCell(const size_t column);
bool ContainsText() const noexcept;
const DbcsAttribute& DbcsAttrAt(const size_t column) const;
@ -101,7 +103,7 @@ protected:
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;

View file

@ -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

View file

@ -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))

View file

@ -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;

View file

@ -16,50 +16,15 @@
// - 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 },
_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:
@ -87,7 +52,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 +78,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();

View file

@ -32,27 +32,28 @@ 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;
const CharRow& GetCharRow() const noexcept { return _charRow; }
CharRow& GetCharRow() noexcept { return _charRow; }
const ATTR_ROW& GetAttrRow() const noexcept;
ATTR_ROW& GetAttrRow() noexcept;
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
SHORT GetId() const noexcept;
void SetId(const SHORT id) noexcept;
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;
std::wstring GetText() const { return _charRow.GetText(); }
RowCellIterator AsCellIter(const size_t startIndex) const;
RowCellIterator AsCellIter(const size_t startIndex, const size_t count) const;
RowCellIterator AsCellIter(const size_t startIndex) const { return AsCellIter(startIndex, size() - startIndex); }
RowCellIterator AsCellIter(const size_t startIndex, const size_t count) const { return RowCellIterator(*this, startIndex, count); }
UnicodeStorage& GetUnicodeStorage() noexcept;
const UnicodeStorage& GetUnicodeStorage() const noexcept;
@ -69,7 +70,7 @@ private:
CharRow _charRow;
ATTR_ROW _attrRow;
SHORT _id;
size_t _rowWidth;
unsigned short _rowWidth;
TextBuffer* _pParent; // non ownership pointer
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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>
@ -22,7 +22,6 @@
<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" />
@ -61,4 +60,4 @@
</ItemGroup>
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
</Project>
</Project>

View file

@ -40,7 +40,6 @@ SOURCES= \
..\RowCellIterator.cpp \
..\TextColor.cpp \
..\TextAttribute.cpp \
..\TextAttributeRun.cpp \
..\textBuffer.cpp \
..\textBufferCellIterator.cpp \
..\textBufferTextIterator.cpp \

View file

@ -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);
@ -837,11 +838,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);
@ -2410,7 +2410,7 @@ 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);
auto& row = GetRowByOffset(i);
concatAll += row.GetCharRow().GetText();
}

View file

@ -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)

View file

@ -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.

View file

@ -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));

View file

@ -122,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);
}
}
@ -130,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();
}
}
@ -343,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);
}
}
}
@ -372,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);
@ -392,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);
}
@ -412,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);
@ -432,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);
}

View file

@ -247,6 +247,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
@ -274,7 +278,7 @@ void AppCommandlineArgs::_buildSplitPaneParser()
style = SplitState::Vertical;
}
}
SplitPaneArgs args{ style, terminalArgs };
SplitPaneArgs args{ style, _splitPaneSize, terminalArgs };
splitPaneActionAndArgs.Args(args);
_startupActions.push_back(splitPaneActionAndArgs);
});

View file

@ -86,6 +86,7 @@ private:
bool _splitVertical{ false };
bool _splitHorizontal{ false };
float _splitPaneSize{ 0.5f };
int _focusTabIndex{ -1 };
bool _focusNextTab{ false };

View file

@ -42,7 +42,8 @@ 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")
};
static const std::array<std::wstring_view, static_cast<uint32_t>(SettingsLoadErrors::ERRORS_SIZE)> settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),

View file

@ -291,7 +291,6 @@ 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);
if (key == VirtualKey::Up)
{
@ -691,7 +690,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);
}
}
}
}

View file

@ -21,7 +21,6 @@ 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 } };
@ -1216,32 +1215,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 +1245,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 +1266,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 +1307,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 +1326,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 +1373,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 +1382,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)
{
@ -1465,7 +1410,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
_gotFocusRevoker.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.

View file

@ -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();
@ -120,8 +122,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);

View file

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -249,6 +249,10 @@
<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="CmdCommandArgDesc" xml:space="preserve">
<value>An optional command, with arguments, to be spawned in the new tab or pane</value>
</data>
@ -268,6 +272,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>

View file

@ -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());

View file

@ -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;
};
}

View file

@ -209,6 +209,7 @@ namespace winrt::TerminalApp::implementation
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
nullptr);
}
else
@ -560,6 +561,7 @@ namespace winrt::TerminalApp::implementation
{
page->_SplitPane(SplitState::Automatic,
SplitType::Manual,
0.5f,
newTerminalArgs);
}
else
@ -600,7 +602,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);
@ -809,7 +813,7 @@ namespace winrt::TerminalApp::implementation
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 +931,34 @@ 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) };
auto setting = AppLogic::CurrentAppSettings();
auto keymap = setting.GlobalSettings().KeyMap();
const auto actionAndArgs = 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:
// - Configure the AppKeyBindings to use our ShortcutActionDispatch and the updated KeyMapping
// as the object to handle dispatching ShortcutAction events.
@ -1065,34 +1097,31 @@ 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 settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
const auto& profileGuid = terminalTab->GetFocusedProfile();
if (profileGuid.has_value())
{
const auto settings{ winrt::make<TerminalSettings>(_settings, profileGuid.value(), *_bindings) };
_CreateNewTabFromSettings(profileGuid.value(), settings);
}
CATCH_LOG();
}
CATCH_LOG();
}
}
@ -1117,6 +1146,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 +1173,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
@ -1373,20 +1417,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 +1442,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 +1478,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 +1487,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,13 +1540,10 @@ 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();
}
_UnZoomIfNeeded();
terminalTab->ClosePane();
}
}
@ -1542,26 +1586,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 +1619,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 +1628,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;
@ -1647,7 +1681,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 +1694,7 @@ namespace winrt::TerminalApp::implementation
_UnZoomIfNeeded();
focusedTab->SplitPane(realSplitType, realGuid, newControl);
focusedTab->SplitPane(realSplitType, splitSize, realGuid, newControl);
}
CATCH_LOG();
}
@ -1675,13 +1709,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 +1723,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 +1735,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 +1848,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;
@ -2212,7 +2231,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();
@ -2837,7 +2856,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>())
{

View file

@ -113,7 +113,7 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IObservableVector<TerminalApp::TabBase> _tabs;
Windows::Foundation::Collections::IVector<TerminalApp::TabBase> _mruTabs;
winrt::com_ptr<TerminalTab> _GetTerminalTabImpl(const TerminalApp::TabBase& tab) const;
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,7 @@ 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 _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept;
void _RegisterActionCallbacks();
@ -182,7 +184,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 +197,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,12 +254,9 @@ 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);

View file

@ -95,6 +95,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"

View file

@ -50,7 +50,6 @@ namespace winrt::TerminalApp::implementation
tab->SetTabText(title);
}
});
// Use our header control as the TabViewItem's header
TabViewItem().Header(_headerControl);
}
@ -76,6 +75,13 @@ namespace winrt::TerminalApp::implementation
_RecalculateAndApplyTabColor();
}
void TerminalTab::_SetToolTip(const winrt::hstring& tabTitle)
{
WUX::Controls::ToolTip toolTip{};
toolTip.Content(winrt::box_value(tabTitle));
WUX::Controls::ToolTipService::SetToolTip(TabViewItem(), toolTip);
}
// 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
@ -213,17 +219,19 @@ namespace winrt::TerminalApp::implementation
if (auto tab{ weakThis.get() })
{
if (hide)
if (tab->_iconHidden != hide)
{
Icon({});
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
tab->_iconHidden = true;
}
else
{
Icon(_lastIconPath);
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
tab->_iconHidden = false;
if (hide)
{
Icon({});
TabViewItem().IconSource(IconPathConverter::IconSourceMUX({}));
}
else
{
Icon(_lastIconPath);
TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath));
}
tab->_iconHidden = hide;
}
}
}
@ -265,6 +273,7 @@ namespace winrt::TerminalApp::implementation
// Update the control to reflect the changed title
_headerControl.Title(activeTitle);
_SetToolTip(activeTitle);
}
}
@ -286,17 +295,6 @@ namespace winrt::TerminalApp::implementation
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);
}
// Method Description:
// - Split the focused pane in our tree of panes, and place the
// given TermControl into the newly created pane.
@ -306,11 +304,14 @@ 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);
auto [first, second] = _activePane->Split(splitType, splitSize, profile, control);
first->Id(activePaneId);
second->Id(_nextPaneId);
++_nextPaneId;
@ -936,9 +937,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:

View file

@ -30,15 +30,19 @@ 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);
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);
@ -98,6 +102,8 @@ namespace winrt::TerminalApp::implementation
void _MakeTabViewItem();
void _SetToolTip(const winrt::hstring& tabTitle);
void _CreateContextMenu() override;
void _RefreshVisualState();

View file

@ -1288,7 +1288,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_terminal->SetSelectionEnd(terminalPosition, mode);
_selectionNeedsToBeCopied = true;
}
else if (mode == ::Terminal::SelectionExpansionMode::Cell)
else if (mode == ::Terminal::SelectionExpansionMode::Cell && !shiftEnabled)
{
// Single Click: reset the selection and begin a new one
_terminal->ClearSelection();

View file

@ -329,7 +329,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
{
try
{
auto row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
auto& row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
if (row.GetCharRow().WasWrapForced())
{
proposedTop--;

View file

@ -11,6 +11,7 @@
using namespace winrt;
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Media;
@ -38,8 +39,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
RS_(L"ColorScheme_BrightWhite/Header")
};
static const std::array<std::wstring, 9> InBoxSchemes = {
L"Campbell",
L"Campbell Powershell",
L"Vintage",
L"One Half Dark",
L"One Half Light",
L"Solarized Dark",
L"Solarized Light",
L"Tango Dark",
L"Tango Light"
};
ColorSchemes::ColorSchemes() :
_ColorSchemeList{ single_threaded_observable_vector<hstring>() },
_ColorSchemeList{ single_threaded_observable_vector<Model::ColorScheme>() },
_CurrentColorTable{ single_threaded_observable_vector<Editor::ColorTableEntry>() }
{
InitializeComponent();
@ -73,10 +86,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
SelectionChangedEventArgs const& args)
{
// Update the color scheme this page is modifying
auto str = winrt::unbox_value<hstring>(args.AddedItems().GetAt(0));
auto colorScheme = _State.Globals().ColorSchemes().Lookup(str);
const auto colorScheme{ args.AddedItems().GetAt(0).try_as<Model::ColorScheme>() };
CurrentColorScheme(colorScheme);
_UpdateColorTable(colorScheme);
// Set the text disclaimer for the text box
hstring disclaimer{};
const std::wstring schemeName{ colorScheme.Name() };
if (std::find(std::begin(InBoxSchemes), std::end(InBoxSchemes), schemeName) != std::end(InBoxSchemes))
{
// load disclaimer for in-box profiles
disclaimer = RS_(L"ColorScheme_DeleteButtonDisclaimerInBox");
}
DeleteButtonDisclaimer().Text(disclaimer);
// Update the state of the page
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"CanDeleteCurrentScheme" });
IsRenaming(false);
}
// Function Description:
@ -92,7 +118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
const auto& colorSchemeMap{ _State.Globals().ColorSchemes() };
for (const auto& pair : colorSchemeMap)
{
_ColorSchemeList.Append(pair.Key());
_ColorSchemeList.Append(pair.Value());
}
}
@ -120,6 +146,101 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
bool ColorSchemes::CanDeleteCurrentScheme() const
{
if (const auto scheme{ CurrentColorScheme() })
{
// Only allow this color scheme to be deleted if it's not provided in-box
const std::wstring myName{ scheme.Name() };
return std::find(std::begin(InBoxSchemes), std::end(InBoxSchemes), myName) == std::end(InBoxSchemes);
}
return false;
}
void ColorSchemes::DeleteConfirmation_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
const auto schemeName{ CurrentColorScheme().Name() };
_State.Globals().RemoveColorScheme(schemeName);
const auto removedSchemeIndex{ ColorSchemeComboBox().SelectedIndex() };
if (static_cast<uint32_t>(removedSchemeIndex) < _ColorSchemeList.Size() - 1)
{
// select same index
ColorSchemeComboBox().SelectedIndex(removedSchemeIndex + 1);
}
else
{
// select last color scheme (avoid out of bounds error)
ColorSchemeComboBox().SelectedIndex(removedSchemeIndex - 1);
}
_ColorSchemeList.RemoveAt(removedSchemeIndex);
DeleteButton().Flyout().Hide();
}
void ColorSchemes::AddNew_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
// Give the new scheme a distinct name
const hstring schemeName{ fmt::format(L"Color Scheme {}", _State.Globals().ColorSchemes().Size() + 1) };
Model::ColorScheme scheme{ schemeName };
// Add the new color scheme
_State.Globals().AddColorScheme(scheme);
// Update current page
_ColorSchemeList.Append(scheme);
ColorSchemeComboBox().SelectedItem(scheme);
}
// Function Description:
// - Pre-populates/focuses the name TextBox, updates the UI
// Arguments:
// - <unused>
// Return Value:
// - <none>
void ColorSchemes::Rename_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
NameBox().Text(CurrentColorScheme().Name());
IsRenaming(true);
NameBox().Focus(FocusState::Programmatic);
NameBox().SelectAll();
}
void ColorSchemes::RenameAccept_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
_RenameCurrentScheme(NameBox().Text());
}
void ColorSchemes::RenameCancel_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
IsRenaming(false);
}
void ColorSchemes::NameBox_PreviewKeyDown(IInspectable const& /*sender*/, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
{
if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Enter)
{
_RenameCurrentScheme(NameBox().Text());
e.Handled(true);
}
else if (e.OriginalKey() == winrt::Windows::System::VirtualKey::Escape)
{
IsRenaming(false);
e.Handled(true);
}
}
void ColorSchemes::_RenameCurrentScheme(hstring newName)
{
CurrentColorScheme().Name(newName);
IsRenaming(false);
// The color scheme is renamed appropriately, but the ComboBox still shows the old name (until you open it)
// We need to manually force the ComboBox to refresh itself.
const auto selectedIndex{ ColorSchemeComboBox().SelectedIndex() };
ColorSchemeComboBox().SelectedIndex((selectedIndex + 1) % ColorSchemeList().Size());
ColorSchemeComboBox().SelectedIndex(selectedIndex);
}
// Function Description:
// - Updates the currently modifiable color table based on the given current color scheme.
// Arguments:

View file

@ -27,17 +27,28 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void ColorSchemeSelectionChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::SelectionChangedEventArgs const& args);
void ColorPickerChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::ColorChangedEventArgs const& args);
void AddNew_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void Rename_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void RenameAccept_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void RenameCancel_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void NameBox_PreviewKeyDown(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
bool CanDeleteCurrentScheme() const;
void DeleteConfirmation_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
GETSET_PROPERTY(Editor::ColorSchemesPageNavigationState, State, nullptr);
GETSET_PROPERTY(Windows::Foundation::Collections::IObservableVector<winrt::Microsoft::Terminal::Settings::Editor::ColorTableEntry>, CurrentColorTable, nullptr);
GETSET_PROPERTY(Windows::Foundation::Collections::IObservableVector<winrt::hstring>, ColorSchemeList, nullptr);
GETSET_PROPERTY(Windows::Foundation::Collections::IObservableVector<Model::ColorScheme>, ColorSchemeList, nullptr);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
OBSERVABLE_GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::ColorScheme, CurrentColorScheme, _PropertyChangedHandlers, nullptr);
OBSERVABLE_GETSET_PROPERTY(bool, IsRenaming, _PropertyChangedHandlers, nullptr);
private:
void _UpdateColorTable(const winrt::Microsoft::Terminal::Settings::Model::ColorScheme& colorScheme);
void _UpdateColorSchemeList();
void _RenameCurrentScheme(hstring newName);
};
struct ColorTableEntry : ColorTableEntryT<ColorTableEntry>

View file

@ -13,9 +13,12 @@ namespace Microsoft.Terminal.Settings.Editor
ColorSchemes();
ColorSchemesPageNavigationState State { get; };
Boolean CanDeleteCurrentScheme { get; };
Boolean IsRenaming { get; };
Microsoft.Terminal.Settings.Model.ColorScheme CurrentColorScheme { get; };
Windows.Foundation.Collections.IObservableVector<ColorTableEntry> CurrentColorTable;
Windows.Foundation.Collections.IObservableVector<String> ColorSchemeList { get; };
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.ColorScheme> ColorSchemeList { get; };
}
[default_interface] runtimeclass ColorTableEntry : Windows.UI.Xaml.Data.INotifyPropertyChanged

View file

@ -5,6 +5,7 @@ the MIT License. See LICENSE in the project root for license information. -->
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
@ -15,8 +16,8 @@ the MIT License. See LICENSE in the project root for license information. -->
<ResourceDictionary Source="CommonResources.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Thickness x:Key="ColorSchemesStandardControlMargin">10,0,0,20</Thickness>
<Thickness x:Key="ColorSchemesStandardControlMargin">0,0,10,20</Thickness>
<Style x:Key="ColorStackPanelStyle" TargetType="StackPanel">
<Setter Property="Margin" Value="{StaticResource ColorSchemesStandardControlMargin}"/>
<Setter Property="Height" Value="59"/>
@ -42,32 +43,85 @@ the MIT License. See LICENSE in the project root for license information. -->
<local:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<local:ColorToHexConverter x:Key="ColorToHexConverter"/>
<local:InvertedBooleanToVisibilityConverter x:Key="InvertedBooleanToVisibilityConverter"/>
</ResourceDictionary>
</Page.Resources>
<ScrollViewer>
<Grid>
<Grid Margin="{StaticResource StandardIndentMargin}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MaxWidth="480"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<!--Select a color scheme-->
<ComboBox x:Name="ColorSchemeComboBox"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
SelectedIndex="0"
ItemsSource="{x:Bind ColorSchemeList, Mode=OneWay}"
SelectionChanged="ColorSchemeSelectionChanged"
Style="{StaticResource ComboBoxSettingStyle}"
Margin="{StaticResource ColorSchemesStandardControlMargin}"/>
<!--Select Color and Add New Button-->
<StackPanel Orientation="Horizontal"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource StandardControlSpacing}">
<!-- Color Table -->
<StackPanel Orientation="Horizontal"
Visibility="{x:Bind IsRenaming, Converter={StaticResource InvertedBooleanToVisibilityConverter}, Mode=OneWay}">
<!--Select a color scheme-->
<ComboBox x:Name="ColorSchemeComboBox"
SelectedIndex="0"
ItemsSource="{x:Bind ColorSchemeList, Mode=OneWay}"
SelectionChanged="ColorSchemeSelectionChanged"
Style="{StaticResource ComboBoxSettingStyle}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="model:ColorScheme">
<TextBlock Text="{x:Bind Name, Mode=OneWay}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!--Rename Button-->
<!--Bind IsEnabled to prevent a the color scheme from returning from the dead-->
<Button x:Uid="Rename"
Style="{StaticResource BrowseButtonStyle}"
Click="Rename_Click"
IsEnabled="{x:Bind CanDeleteCurrentScheme, Mode=OneWay}">
<FontIcon Glyph="&#xE8AC;"/>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal"
Visibility="{x:Bind IsRenaming, Mode=OneWay}">
<TextBox x:Name="NameBox"
Style="{StaticResource TextBoxSettingStyle}"
PreviewKeyDown="NameBox_PreviewKeyDown"/>
<Button x:Uid="RenameAccept"
Style="{StaticResource AccentBrowseButtonStyle}"
Click="RenameAccept_Click">
<FontIcon Glyph="&#xE8FB;"/>
</Button>
<Button x:Uid="RenameCancel"
Style="{StaticResource BrowseButtonStyle}"
Click="RenameCancel_Click">
<FontIcon Glyph="&#xE711;"/>
</Button>
</StackPanel>
<!--Add new button-->
<Button Click="AddNew_Click"
Style="{StaticResource BrowseButtonStyle}">
<StackPanel Orientation="Horizontal">
<FontIcon Glyph="&#xE710;"
FontSize="15"/>
<TextBlock x:Uid="ColorScheme_AddNewButton"
Margin="10,0,0,0"
FontSize="15"/>
</StackPanel>
</Button>
</StackPanel>
<!-- Color Table (Left Column)-->
<ItemsControl x:Name="ColorTableControl"
ItemsSource="{x:Bind CurrentColorTable, Mode=OneWay}"
Grid.Row="1"
@ -98,8 +152,8 @@ the MIT License. See LICENSE in the project root for license information. -->
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- Additional Colors -->
<ItemsControl Grid.Row="2"
<!-- Additional Colors (Right Column) -->
<ItemsControl Grid.Row="1"
Grid.Column="1">
<StackPanel Style="{StaticResource ColorStackPanelStyle}">
<TextBlock x:Uid="ColorScheme_Foreground"
@ -149,6 +203,71 @@ the MIT License. See LICENSE in the project root for license information. -->
</Button>
</StackPanel>
</ItemsControl>
<!--Delete Button-->
<StackPanel Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Margin="{StaticResource StandardControlSpacing}">
<Button x:Name="DeleteButton"
Style="{StaticResource BaseButtonStyle}"
IsEnabled="{x:Bind CanDeleteCurrentScheme, Mode=OneWay}"
Margin="0,0,0,10">
<Button.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="ButtonBackground" Color="Firebrick"/>
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#C23232"/>
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#A21212"/>
<SolidColorBrush x:Key="ButtonForeground" Color="White"/>
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="White"/>
<SolidColorBrush x:Key="ButtonForegroundPressed" Color="White"/>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="ButtonBackground" Color="Firebrick"/>
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="#C23232"/>
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="#A21212"/>
<SolidColorBrush x:Key="ButtonForeground" Color="White"/>
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="White"/>
<SolidColorBrush x:Key="ButtonForegroundPressed" Color="White"/>
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush x:Key="ButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}"/>
<SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}"/>
<SolidColorBrush x:Key="ButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}"/>
<SolidColorBrush x:Key="ButtonForeground" Color="{ThemeResource SystemColorButtonTextColor}"/>
<SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="{ThemeResource SystemColorHighlightTextColor}"/>
<SolidColorBrush x:Key="ButtonForegroundPressed" Color="{ThemeResource SystemColorHighlightTextColor}"/>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Button.Resources>
<Button.Content>
<StackPanel Orientation="Horizontal">
<FontIcon Glyph="&#xE74D;"
FontSize="{StaticResource StandardFontSize}"/>
<TextBlock x:Uid="ColorScheme_DeleteButton"
FontSize="{StaticResource StandardFontSize}"
Margin="10,0,0,0"/>
</StackPanel>
</Button.Content>
<Button.Flyout>
<Flyout>
<StackPanel>
<TextBlock x:Uid="ColorScheme_DeleteConfirmationMessage"
Style="{StaticResource CustomFlyoutTextStyle}"/>
<Button x:Uid="ColorScheme_DeleteConfirmationButton"
Style="{StaticResource BaseButtonStyle}"
Click="DeleteConfirmation_Click"/>
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
<TextBlock x:Name="DeleteButtonDisclaimer"
Style="{StaticResource DisclaimerStyle}"
VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</ScrollViewer>
</Page>

View file

@ -5,12 +5,16 @@
<x:Double x:Key="StandardFontSize">15.0</x:Double>
<Thickness x:Key="StandardIndentMargin">20,0,0,0</Thickness>
<x:Double x:Key="StandardBoxMinWidth">300</x:Double>
<Thickness x:Key="StandardControlSpacing">0,0,0,20</Thickness>
<x:Double x:Key="StandardBoxMinWidth">250</x:Double>
<Thickness x:Key="PivotIndentMargin">10,0,0,0</Thickness>
<Thickness x:Key="PivotStackPanelMargin">0,10,0,0</Thickness>
<!-- This is for easier transition to the SettingsContainer control.
The SettingsContainer will wrap a setting with inheritance UI.-->
<Style x:Key="SettingContainerStyle" TargetType="ContentPresenter">
<Setter Property="Padding" Value="0,0,0,20"/>
<Setter Property="Padding" Value="{StaticResource StandardControlSpacing}"/>
</Style>
<!--Used to stack a group of settings-->
@ -63,6 +67,20 @@
<Setter Property="Margin" Value="0,20,0,10"/>
</Style>
<!--Used for disclaimers-->
<Style x:Key="DisclaimerStyle" TargetType="TextBlock">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="TextWrapping" Value="WrapWholeWords"/>
</Style>
<!--Used for flyout messages-->
<Style x:Key="CustomFlyoutTextStyle" TargetType="TextBlock" BasedOn="{StaticResource BaseTextBlockStyle}">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Margin" Value="0,0,0,12"/>
</Style>
<!--Number Box-->
<Style x:Key="NumberBoxSettingStyle" TargetType="muxc:NumberBox">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
@ -74,6 +92,7 @@
<!--Button-Related Styling-->
<Style x:Key="BaseButtonStyle" TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
<Setter Property="ToolTipService.Placement" Value="Mouse"/>
</Style>
<Style x:Key="BrowseButtonStyle" TargetType="Button" BasedOn="{StaticResource BaseButtonStyle}">
@ -81,6 +100,13 @@
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<Style x:Key="AccentBrowseButtonStyle" TargetType="Button" BasedOn="{StaticResource AccentButtonStyle}">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
<Setter Property="ToolTipService.Placement" Value="Mouse"/>
<Setter Property="Margin" Value="10,0,0,0"/>
<Setter Property="VerticalAlignment" Value="Bottom"/>
</Style>
<!--Slider-Related Styling-->
<Style x:Key="SliderSettingStyle" TargetType="Slider" BasedOn="{StaticResource DefaultSliderStyle}">
<Setter Property="FontSize" Value="{StaticResource StandardFontSize}"/>
@ -105,5 +131,4 @@
<Setter Property="Width" Value="300"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</ResourceDictionary>

View file

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "InvertedBooleanToVisibilityConverter.h"
#include "InvertedBooleanToVisibilityConverter.g.cpp"
using namespace winrt::Windows;
using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Foundation::IInspectable InvertedBooleanToVisibilityConverter::Convert(Foundation::IInspectable const& value,
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
Foundation::IInspectable const& /* parameter */,
hstring const& /* language */)
{
return winrt::box_value(winrt::unbox_value<bool>(value) ? Visibility::Collapsed : Visibility::Visible);
}
Foundation::IInspectable InvertedBooleanToVisibilityConverter::ConvertBack(Foundation::IInspectable const& /*value*/,
Windows::UI::Xaml::Interop::TypeName const& /* targetType */,
Foundation::IInspectable const& /*parameter*/,
hstring const& /* language */)
{
throw hresult_not_implemented();
}
}

View file

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "InvertedBooleanToVisibilityConverter.g.h"
#include "Utils.h"
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
struct InvertedBooleanToVisibilityConverter : InvertedBooleanToVisibilityConverterT<InvertedBooleanToVisibilityConverter>
{
InvertedBooleanToVisibilityConverter() = 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::Microsoft::Terminal::Settings::Editor::factory_implementation
{
BASIC_FACTORY(InvertedBooleanToVisibilityConverter);
}

View file

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass InvertedBooleanToVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter
{
InvertedBooleanToVisibilityConverter();
};
}

View file

@ -10,6 +10,7 @@
#include "Profiles.h"
#include "GlobalAppearance.h"
#include "ColorSchemes.h"
#include "..\types\inc\utils.hpp"
#include <LibraryResources.h>
@ -75,7 +76,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (const auto tag{ navViewItem.Tag() })
{
if (tag.try_as<Model::Profile>())
if (tag.try_as<Editor::ProfileViewModel>())
{
// hide NavViewItem pointing to a Profile
navViewItem.Visibility(Visibility::Collapsed);
@ -103,13 +104,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
if (const auto tag{ selectedItem.as<MUX::Controls::NavigationViewItem>().Tag() })
{
if (const auto profile{ tag.try_as<Model::Profile>() })
if (const auto oldProfile{ tag.try_as<Editor::ProfileViewModel>() })
{
// check if the profile still exists
if (_settingsClone.FindProfile(profile.Guid()))
if (const auto profile{ _settingsClone.FindProfile(oldProfile.Guid()) })
{
// Navigate to the page with the given profile
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), winrt::make<ProfilePageNavigationState>(_viewModelForProfile(profile), _settingsClone.GlobalSettings().ColorSchemes(), *this));
_Navigate(_viewModelForProfile(profile));
return;
}
}
@ -125,7 +126,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// could not find the page we were on, fallback to first menu item
const auto firstItem{ navigationMenu.MenuItems().GetAt(0) };
navigationMenu.SelectedItem(firstItem);
if (const auto tag{ navigationMenu.SelectedItem().as<NavigationViewItem>().Tag() })
if (const auto tag{ navigationMenu.SelectedItem().as<MUX::Controls::NavigationViewItem>().Tag() })
{
_Navigate(unbox_value<hstring>(tag));
}
@ -197,7 +198,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
else if (const auto profile = clickedItemContainer.Tag().try_as<Editor::ProfileViewModel>())
{
// Navigate to a page with the given profile
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), winrt::make<ProfilePageNavigationState>(profile, _settingsClone.GlobalSettings().ColorSchemes(), *this));
_Navigate(profile);
}
}
}
@ -218,7 +219,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
else if (clickedItemTag == globalProfileTag)
{
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), winrt::make<ProfilePageNavigationState>(_viewModelForProfile(_settingsClone.ProfileDefaults()), _settingsClone.GlobalSettings().ColorSchemes(), *this));
auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults()) };
profileVM.IsBaseLayer(true);
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), winrt::make<ProfilePageNavigationState>(profileVM, _settingsClone.GlobalSettings().ColorSchemes(), *this));
}
else if (clickedItemTag == colorSchemesTag)
{
@ -230,6 +233,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
void MainPage::_Navigate(const Editor::ProfileViewModel& profile)
{
auto state{ winrt::make<ProfilePageNavigationState>(profile, _settingsClone.GlobalSettings().ColorSchemes(), *this) };
// Add an event handler for when the user wants to delete a profile.
state.DeleteProfile({ this, &MainPage::_DeleteProfile });
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), state);
}
void MainPage::OpenJsonTapped(IInspectable const& /*sender*/, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& /*args*/)
{
const CoreWindow window = CoreWindow::GetForCurrentThread();
@ -297,7 +310,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// Select and navigate to the new profile
SettingsNav().SelectedItem(navItem);
contentFrame().Navigate(xaml_typename<Editor::Profiles>(), winrt::make<ProfilePageNavigationState>(profileViewModel, _settingsClone.GlobalSettings().ColorSchemes(), *this));
_Navigate(profileViewModel);
}
MUX::Controls::NavigationViewItem MainPage::_CreateProfileNavViewItem(const Editor::ProfileViewModel& profile)
@ -313,4 +326,31 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
return profileNavItem;
}
void MainPage::_DeleteProfile(const IInspectable /*sender*/, const Editor::DeleteProfileEventArgs& args)
{
// Delete profile from settings model
const auto guid{ args.ProfileGuid() };
auto profileList{ _settingsClone.AllProfiles() };
for (uint32_t i = 0; i < profileList.Size(); ++i)
{
if (profileList.GetAt(i).Guid() == guid)
{
profileList.RemoveAt(i);
break;
}
}
// remove selected item
uint32_t index;
auto selectedItem{ SettingsNav().SelectedItem() };
auto menuItems{ SettingsNav().MenuItems() };
menuItems.IndexOf(selectedItem, index);
menuItems.RemoveAt(index);
// navigate to the profile next to this one
const auto newSelectedItem{ menuItems.GetAt(index < menuItems.Size() - 1 ? index : index - 1) };
SettingsNav().SelectedItem(newSelectedItem);
_Navigate(newSelectedItem.try_as<MUX::Controls::NavigationViewItem>().Tag().try_as<Editor::ProfileViewModel>());
}
}

View file

@ -35,9 +35,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void _InitializeProfilesList();
void _CreateAndNavigateToNewProfile(const uint32_t index);
winrt::Microsoft::UI::Xaml::Controls::NavigationViewItem _CreateProfileNavViewItem(const ProfileViewModel& profile);
winrt::Microsoft::UI::Xaml::Controls::NavigationViewItem _CreateProfileNavViewItem(const Editor::ProfileViewModel& profile);
void _DeleteProfile(const Windows::Foundation::IInspectable sender, const Editor::DeleteProfileEventArgs& args);
void _Navigate(hstring clickedItemTag);
void _Navigate(const Editor::ProfileViewModel& profile);
void _RefreshCurrentPage();
};
}

View file

@ -44,6 +44,9 @@
<ClInclude Include="FontWeightConverter.h">
<DependentUpon>FontWeightConverter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="InvertedBooleanToVisibilityConverter.h">
<DependentUpon>InvertedBooleanToVisibilityConverter.idl</DependentUpon>
</ClInclude>
<ClInclude Include="PercentageConverter.h">
<DependentUpon>PercentageConverter.idl</DependentUpon>
</ClInclude>
@ -114,6 +117,9 @@
<ClCompile Include="FontWeightConverter.cpp">
<DependentUpon>FontWeightConverter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="InvertedBooleanToVisibilityConverter.cpp">
<DependentUpon>InvertedBooleanToVisibilityConverter.idl</DependentUpon>
</ClCompile>
<ClCompile Include="PercentageConverter.cpp">
<DependentUpon>PercentageConverter.idl</DependentUpon>
</ClCompile>
@ -151,6 +157,7 @@
<Midl Include="ColorToBrushConverter.idl" />
<Midl Include="ColorToHexConverter.idl" />
<Midl Include="FontWeightConverter.idl" />
<Midl Include="InvertedBooleanToVisibilityConverter.idl" />
<Midl Include="PercentageConverter.idl" />
<Midl Include="EnumEntry.idl" />
<Midl Include="GlobalAppearance.idl">

View file

@ -25,6 +25,9 @@
<Midl Include="FontWeightConverter.idl">
<Filter>Converters</Filter>
</Midl>
<Midl Include="InvertedBooleanToVisibilityConverter.idl">
<Filter>Converters</Filter>
</Midl>
<Midl Include="PercentageConverter.idl">
<Filter>Converters</Filter>
</Midl>

View file

@ -10,6 +10,7 @@
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Xaml;
using namespace winrt::Windows::UI::Xaml::Controls;
using namespace winrt::Windows::UI::Xaml::Navigation;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
@ -18,8 +19,42 @@ using namespace winrt::Windows::Storage::AccessCache;
using namespace winrt::Windows::Storage::Pickers;
using namespace winrt::Microsoft::Terminal::Settings::Model;
static const std::array<winrt::guid, 2> InBoxProfileGuids{
winrt::guid{ 0x61c54bbd, 0xc2c6, 0x5271, { 0x96, 0xe7, 0x00, 0x9a, 0x87, 0xff, 0x44, 0xbf } }, // Windows Powershell
winrt::guid{ 0x0caa0dad, 0x35be, 0x5f56, { 0xa8, 0xff, 0xaf, 0xce, 0xee, 0xaa, 0x61, 0x01 } } // Command Prompt
};
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
bool ProfileViewModel::CanDeleteProfile() const
{
const auto guid{ Guid() };
if (IsBaseLayer())
{
return false;
}
else if (std::find(std::begin(InBoxProfileGuids), std::end(InBoxProfileGuids), guid) != std::end(InBoxProfileGuids))
{
// in-box profile
return false;
}
else if (!Source().empty())
{
// dynamic profile
return false;
}
else
{
return true;
}
}
void ProfilePageNavigationState::DeleteProfile()
{
auto deleteProfileArgs{ winrt::make_self<DeleteProfileEventArgs>(_Profile.Guid()) };
_DeleteProfileHandlers(*this, *deleteProfileArgs);
}
Profiles::Profiles() :
_ColorSchemeList{ single_threaded_observable_vector<ColorScheme>() }
{
@ -64,6 +99,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
biButton.IsChecked(biButton.Tag().as<int32_t>() == biAlignmentVal);
}
// Set the text disclaimer for the text box
hstring disclaimer{};
const auto guid{ _State.Profile().Guid() };
if (std::find(std::begin(InBoxProfileGuids), std::end(InBoxProfileGuids), guid) != std::end(InBoxProfileGuids))
{
// load disclaimer for in-box profiles
disclaimer = RS_(L"Profile_DeleteButtonDisclaimerInBox");
}
else if (!_State.Profile().Source().empty())
{
// load disclaimer for dynamic profiles
disclaimer = RS_(L"Profile_DeleteButtonDisclaimerDynamic");
}
DeleteButtonDisclaimer().Text(disclaimer);
}
ColorScheme Profiles::CurrentColorScheme()
@ -86,6 +136,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_State.Profile().ColorSchemeName(val.Name());
}
void Profiles::DeleteConfirmation_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/)
{
auto state{ winrt::get_self<ProfilePageNavigationState>(_State) };
state->DeleteProfile();
}
fire_and_forget Profiles::BackgroundImage_Click(IInspectable const&, RoutedEventArgs const&)
{
auto lifetime = get_strong();

View file

@ -5,6 +5,7 @@
#include "Profiles.g.h"
#include "ProfilePageNavigationState.g.h"
#include "DeleteProfileEventArgs.g.h"
#include "ProfileViewModel.g.h"
#include "Utils.h"
#include "ViewModelHelpers.h"
@ -17,6 +18,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
ProfileViewModel(const Model::Profile& profile) :
_profile{ profile } {}
bool CanDeleteProfile() const;
GETSET_PROPERTY(bool, IsBaseLayer, false);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType);
OBSERVABLE_PROJECTED_SETTING(_profile, Name);
@ -60,6 +64,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
Model::Profile _profile;
};
struct DeleteProfileEventArgs :
public DeleteProfileEventArgsT<DeleteProfileEventArgs>
{
public:
DeleteProfileEventArgs(guid profileGuid) :
_ProfileGuid(profileGuid) {}
guid ProfileGuid() const noexcept { return _ProfileGuid; }
private:
guid _ProfileGuid{};
};
struct ProfilePageNavigationState : ProfilePageNavigationStateT<ProfilePageNavigationState>
{
public:
@ -70,9 +87,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
}
void DeleteProfile();
Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme> Schemes() { return _Schemes; }
void Schemes(const Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme>& val) { _Schemes = val; }
TYPED_EVENT(DeleteProfile, Editor::ProfilePageNavigationState, Editor::DeleteProfileEventArgs);
GETSET_PROPERTY(IHostedInWindow, WindowRoot, nullptr);
GETSET_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
@ -95,6 +115,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
fire_and_forget StartingDirectory_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
fire_and_forget Icon_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void BIAlignment_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void DeleteConfirmation_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
// CursorShape visibility logic
void CursorShape_Changed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);

View file

@ -10,6 +10,9 @@ namespace Microsoft.Terminal.Settings.Editor
{
runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged
{
Boolean CanDeleteProfile { get; };
Boolean IsBaseLayer;
OBSERVABLE_PROJECTED_SETTING(String, Name);
OBSERVABLE_PROJECTED_SETTING(Guid, Guid);
OBSERVABLE_PROJECTED_SETTING(String, Source);
@ -50,12 +53,19 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle);
}
runtimeclass DeleteProfileEventArgs
{
Guid ProfileGuid { get; };
}
runtimeclass ProfilePageNavigationState
{
Windows.Foundation.Collections.IMapView<String, Microsoft.Terminal.Settings.Model.ColorScheme> Schemes;
IHostedInWindow WindowRoot; // necessary to send the right HWND into the file picker dialogs.
ProfileViewModel Profile { get; };
event Windows.Foundation.TypedEventHandler<ProfilePageNavigationState, DeleteProfileEventArgs> DeleteProfile;
};
[default_interface] runtimeclass Profiles : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged

File diff suppressed because it is too large Load diff

View file

@ -19,9 +19,8 @@ the MIT License. See LICENSE in the project root for license information. -->
<ScrollViewer>
<StackPanel Style="{StaticResource SettingsStackStyle}">
<TextBlock x:Uid="Globals_RenderingDisclaimer"
FontSize="{StaticResource StandardFontSize}"
FontStyle="Italic"
Margin="0,0,0,20"/>
Style="{StaticResource DisclaimerStyle}"
Margin="{StaticResource StandardControlSpacing}"/>
<!--Force Full Repaint-->
<ContentPresenter Style="{StaticResource SettingContainerStyle}">

View file

@ -722,6 +722,27 @@
<data name="Profile_BellStyleVisual.Content" xml:space="preserve">
<value>Visual (Flash Taskbar)</value>
</data>
<data name="ColorScheme_AddNewButton.Text" xml:space="preserve">
<value>Add new</value>
</data>
<data name="ColorScheme_DeleteButton.Text" xml:space="preserve">
<value>Delete color scheme</value>
</data>
<data name="ColorScheme_Name.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="ColorScheme_Name.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>The name of the color scheme.</value>
</data>
<data name="Profile_DeleteButton.Text" xml:space="preserve">
<value>Delete profile</value>
</data>
<data name="Profile_Name.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>The name of the profile that appears in the dropdown.</value>
</data>
<data name="Profile_Name.Header" xml:space="preserve">
<value>Name</value>
</data>
<data name="Profile_AcrylicHeader.Text" xml:space="preserve">
<value>Acrylic</value>
</data>
@ -741,4 +762,40 @@
<value>Open your settings.json file. Alt+Click to open your defaults.json file.</value>
<comment>{Locked="settings.json"}, {Locked="defaults.json"}</comment>
</data>
<data name="ColorScheme_DeleteButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Delete the current color scheme.</value>
</data>
<data name="Rename.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Rename</value>
</data>
<data name="Profile_DeleteButtonDisclaimerInBox" xml:space="preserve">
<value>This profile cannot be deleted because it is included by default.</value>
</data>
<data name="Profile_DeleteButtonDisclaimerDynamic" xml:space="preserve">
<value>This profile cannot be deleted because it is automatically generated.</value>
</data>
<data name="ColorScheme_DeleteButtonDisclaimerInBox" xml:space="preserve">
<value>This color scheme cannot be deleted or renamed because it is included by default.</value>
</data>
<data name="ColorScheme_DeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete color scheme</value>
</data>
<data name="ColorScheme_DeleteConfirmationMessage.Text" xml:space="preserve">
<value>Are you sure you want to delete this color scheme?</value>
</data>
<data name="RenameAccept.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Accept rename</value>
</data>
<data name="RenameCancel.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Cancel rename</value>
</data>
<data name="Profile_BaseLayerDisclaimer.Text" xml:space="preserve">
<value>Settings defined here will apply to all profiles unless they are overridden by a profile's settings.</value>
</data>
<data name="Profile_DeleteConfirmationButton.Content" xml:space="preserve">
<value>Yes, delete profile</value>
</data>
<data name="Profile_DeleteConfirmationMessage.Text" xml:space="preserve">
<value>Are you sure you want to delete this profile?</value>
</data>
</root>

View file

@ -231,8 +231,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::hstring SplitPaneArgs::GenerateName() const
{
// The string will be similar to the following:
// * "Duplicate pane[, split: <direction>][, new terminal arguments...]"
// * "Split pane[, split: <direction>][, new terminal arguments...]"
// * "Duplicate pane[, split: <direction>][, size: <size>%][, new terminal arguments...]"
// * "Split pane[, split: <direction>][, size: <size>%][, new terminal arguments...]"
//
// Direction will only be added to the string if the split direction is
// not "auto".
@ -262,6 +262,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
break;
}
if (_SplitSize != .5f)
{
ss << L"size: " << (_SplitSize * 100) << L"%, ";
}
winrt::hstring newTerminalArgsStr;
if (_TerminalArgs)
{
@ -287,6 +292,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_(L"OpenDefaultSettingsCommandKey");
case SettingsTarget::AllFiles:
return RS_(L"OpenBothSettingsFilesCommandKey");
case SettingsTarget::SettingsUI:
return RS_(L"OpenSettingsUICommandKey");
default:
return RS_(L"OpenSettingsCommandKey");
}

View file

@ -379,6 +379,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct SplitPaneArgs : public SplitPaneArgsT<SplitPaneArgs>
{
SplitPaneArgs() = default;
SplitPaneArgs(SplitState style, double size, const Model::NewTerminalArgs& terminalArgs) :
_SplitStyle{ style },
_SplitSize{ size },
_TerminalArgs{ terminalArgs } {};
SplitPaneArgs(SplitState style, const Model::NewTerminalArgs& terminalArgs) :
_SplitStyle{ style },
_TerminalArgs{ terminalArgs } {};
@ -387,9 +391,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
GETSET_PROPERTY(SplitState, SplitStyle, SplitState::Automatic);
GETSET_PROPERTY(Model::NewTerminalArgs, TerminalArgs, nullptr);
GETSET_PROPERTY(SplitType, SplitMode, SplitType::Manual);
GETSET_PROPERTY(double, SplitSize, .5);
static constexpr std::string_view SplitKey{ "split" };
static constexpr std::string_view SplitModeKey{ "splitMode" };
static constexpr std::string_view SplitSizeKey{ "size" };
public:
hstring GenerateName() const;
@ -402,6 +408,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return otherAsUs->_SplitStyle == _SplitStyle &&
(otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) :
otherAsUs->_TerminalArgs == _TerminalArgs) &&
otherAsUs->_SplitSize == _SplitSize &&
otherAsUs->_SplitMode == _SplitMode;
}
return false;
@ -413,6 +420,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
args->_TerminalArgs = NewTerminalArgs::FromJson(json);
JsonUtils::GetValueForKey(json, SplitKey, args->_SplitStyle);
JsonUtils::GetValueForKey(json, SplitModeKey, args->_SplitMode);
JsonUtils::GetValueForKey(json, SplitSizeKey, args->_SplitSize);
if (args->_SplitSize >= 1 || args->_SplitSize <= 0)
{
return { nullptr, { SettingsLoadWarnings::InvalidSplitSize } };
}
return { *args, {} };
}
IActionArgs Copy() const
@ -421,6 +433,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
copy->_SplitStyle = _SplitStyle;
copy->_TerminalArgs = _TerminalArgs.Copy();
copy->_SplitMode = _SplitMode;
copy->_SplitSize = _SplitSize;
return *copy;
}
};
@ -428,6 +441,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct OpenSettingsArgs : public OpenSettingsArgsT<OpenSettingsArgs>
{
OpenSettingsArgs() = default;
OpenSettingsArgs(const SettingsTarget& target) :
_Target{ target } {}
GETSET_PROPERTY(SettingsTarget, Target, SettingsTarget::SettingsFile);
static constexpr std::string_view TargetKey{ "target" };
@ -837,4 +852,5 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(CloseOtherTabsArgs);
BASIC_FACTORY(CloseTabsAfterArgs);
BASIC_FACTORY(MoveTabArgs);
BASIC_FACTORY(OpenSettingsArgs);
}

View file

@ -135,16 +135,19 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass SplitPaneArgs : IActionArgs
{
SplitPaneArgs(SplitState split, Double size, NewTerminalArgs terminalArgs);
SplitPaneArgs(SplitState split, NewTerminalArgs terminalArgs);
SplitPaneArgs(SplitType splitMode);
SplitState SplitStyle { get; };
NewTerminalArgs TerminalArgs { get; };
SplitType SplitMode { get; };
Double SplitSize { get; };
};
[default_interface] runtimeclass OpenSettingsArgs : IActionArgs
{
OpenSettingsArgs(SettingsTarget target);
SettingsTarget Target { get; };
};

View file

@ -5,6 +5,7 @@
#include "ColorScheme.h"
#include "DefaultSettings.h"
#include "../../types/inc/Utils.hpp"
#include "../../types/inc/colorTable.hpp"
#include "Utils.h"
#include "JsonUtils.h"
@ -41,14 +42,18 @@ static constexpr std::array<std::string_view, 16> TableColors = {
};
ColorScheme::ColorScheme() :
_Foreground{ DEFAULT_FOREGROUND_WITH_ALPHA },
_Background{ DEFAULT_BACKGROUND_WITH_ALPHA },
_SelectionBackground{ DEFAULT_FOREGROUND },
_CursorColor{ DEFAULT_CURSOR_COLOR }
ColorScheme(L"", DEFAULT_FOREGROUND_WITH_ALPHA, DEFAULT_BACKGROUND_WITH_ALPHA, DEFAULT_CURSOR_COLOR)
{
Utils::InitializeCampbellColorTable(_table);
}
ColorScheme::ColorScheme(winrt::hstring name, Color defaultFg, Color defaultBg, Color cursorColor) :
ColorScheme::ColorScheme(winrt::hstring name) :
ColorScheme(name, DEFAULT_FOREGROUND_WITH_ALPHA, DEFAULT_BACKGROUND_WITH_ALPHA, DEFAULT_CURSOR_COLOR)
{
Utils::InitializeCampbellColorTable(_table);
}
ColorScheme::ColorScheme(winrt::hstring name, COLORREF defaultFg, COLORREF defaultBg, COLORREF cursorColor) :
_Name{ name },
_Foreground{ defaultFg },
_Background{ defaultBg },

View file

@ -33,7 +33,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
public:
ColorScheme();
ColorScheme(hstring name, Windows::UI::Color defaultFg, Windows::UI::Color defaultBg, Windows::UI::Color cursorColor);
ColorScheme(hstring name);
ColorScheme(hstring name, COLORREF defaultFg, COLORREF defaultBg, COLORREF cursorColor);
com_ptr<ColorScheme> Copy() const;
static com_ptr<ColorScheme> FromJson(const Json::Value& json);
@ -60,3 +61,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
friend class SettingsModelLocalTests::ColorSchemeTests;
};
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ColorScheme);
}

View file

@ -4,6 +4,8 @@
namespace Microsoft.Terminal.Settings.Model
{
[default_interface] runtimeclass ColorScheme {
ColorScheme(String name);
String Name;
Windows.UI.Color Foreground;

View file

@ -334,6 +334,11 @@ void GlobalAppSettings::AddColorScheme(const Model::ColorScheme& scheme)
_colorSchemes.Insert(scheme.Name(), scheme);
}
void GlobalAppSettings::RemoveColorScheme(hstring schemeName)
{
_colorSchemes.TryRemove(schemeName);
}
// Method Description:
// - Return the warnings that we've collected during parsing the JSON for the
// keybindings. It's possible that the user provided keybindings have some

View file

@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
Windows::Foundation::Collections::IMapView<hstring, Model::ColorScheme> ColorSchemes() noexcept;
void AddColorScheme(const Model::ColorScheme& scheme);
void RemoveColorScheme(hstring schemeName);
Model::KeyMapping KeyMap() const noexcept;

View file

@ -137,6 +137,7 @@ namespace Microsoft.Terminal.Settings.Model
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);
void RemoveColorScheme(String schemeName);
KeyMapping KeyMap();

View file

@ -363,4 +363,7 @@
<data name="BreakIntoDebuggerCommandKey" xml:space="preserve">
<value>Break into the debugger</value>
</data>
<data name="OpenSettingsUICommandKey" xml:space="preserve">
<value>Open Settings...</value>
</data>
</root>

View file

@ -19,6 +19,7 @@ namespace Microsoft.Terminal.Settings.Model
FailedToParseCommandJson = 9,
FailedToWriteToSettings = 10,
InvalidColorSchemeInCmd = 11,
InvalidSplitSize = 12,
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
};

View file

@ -3529,7 +3529,7 @@ void ConptyRoundtripTests::HyperlinkIdConsistency()
auto verifyData = [](TextBuffer& tb) {
// Check that all the linked cells still have the same ID
auto attrRow = tb.GetRowByOffset(0).GetAttrRow();
auto& attrRow = tb.GetRowByOffset(0).GetAttrRow();
auto id = attrRow.GetAttrByColumn(0).GetHyperlinkId();
for (auto i = 1; i < 4; ++i)
{

View file

@ -26,7 +26,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="AuditMode|Win32">
<ProjectConfiguration Include="AuditMode|Win32">
<Configuration>AuditMode</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
@ -90,7 +90,7 @@
<PrecompiledHeaderFile>precomp.h</PrecompiledHeaderFile>
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\interval_tree;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)\src\inc;$(SolutionDir)\dep;$(SolutionDir)\dep\Console;$(SolutionDir)\dep\Win32K;$(SolutionDir)\dep\gsl\include;$(SolutionDir)\dep\wil\include;$(SolutionDir)\oss\chromium;$(SolutionDir)\oss\fmt\include;$(SolutionDir)\oss\dynamic_bitset;$(SolutionDir)\oss\libpopcnt;$(SolutionDir)\oss\interval_tree;$(SolutionDir)\oss\boost\boost_1_73_0;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<MinimalRebuild>false</MinimalRebuild>
<RuntimeTypeInfo>false</RuntimeTypeInfo>

View file

@ -88,6 +88,8 @@ TRACELOGGING_DECLARE_PROVIDER(g_hConhostV2EventTraceProvider);
#include "../inc/operators.hpp"
#include "../inc/conattrs.hpp"
#include "boost/container/small_vector.hpp"
// TODO: MSFT 9355094 Find a better way of doing this. http://osgvsowi/9355094
[[nodiscard]] inline NTSTATUS NTSTATUS_FROM_HRESULT(HRESULT hr)
{

View file

@ -235,6 +235,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)
{

View file

@ -80,19 +80,10 @@ public:
typedef struct _RegPropertyMap
{
_RegPropertyType const propertyType;
_RegPropertyType propertyType;
PCWSTR pwszValueName;
DWORD const dwFieldOffset;
size_t const cbFieldSize;
_RegPropertyMap(
_RegPropertyType const propertyType,
PCWSTR pwszValueName,
DWORD const dwFieldOffset,
size_t const cbFieldSize) :
propertyType(propertyType),
pwszValueName(pwszValueName),
dwFieldOffset(dwFieldOffset),
cbFieldSize(cbFieldSize){};
DWORD dwFieldOffset;
size_t cbFieldSize;
_RegPropertyMap& operator=(const _RegPropertyMap&) { return *this; }
} RegPropertyMap;

View file

@ -4,6 +4,7 @@
#include "precomp.h"
#include "CustomTextLayout.h"
#include "CustomTextRenderer.h"
#include <wrl.h>
#include <wrl/client.h>
@ -19,19 +20,27 @@ using namespace Microsoft::Console::Render;
// - factory - DirectWrite factory reference in case we need other DirectWrite objects for our layout
// - analyzer - DirectWrite text analyzer from the factory that has been cached at a level above this layout (expensive to create)
// - format - The DirectWrite format object representing the size and other text properties to be applied (by default) to a layout
// - formatItalic - The italic variant of the format object representing the size and other text properties for italic text
// - font - The DirectWrite font face to use while calculating layout (by default, will fallback if necessary)
// - fontItalic - The italic variant of the font face to use while calculating layout for italic text
// - width - The count of pixels available per column (the expected pixel width of every column)
// - boxEffect - Box drawing scaling effects that are cached for the base font across layouts.
CustomTextLayout::CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory,
gsl::not_null<IDWriteTextAnalyzer1*> const analyzer,
gsl::not_null<IDWriteTextFormat*> const format,
gsl::not_null<IDWriteTextFormat*> const formatItalic,
gsl::not_null<IDWriteFontFace1*> const font,
gsl::not_null<IDWriteFontFace1*> const fontItalic,
size_t const width,
IBoxDrawingEffect* const boxEffect) :
_factory{ factory.get() },
_analyzer{ analyzer.get() },
_format{ format.get() },
_formatItalic{ formatItalic.get() },
_formatInUse{ format.get() },
_font{ font.get() },
_fontItalic{ fontItalic.get() },
_fontInUse{ font.get() },
_boxDrawingEffect{ boxEffect },
_localeName{},
_numberSubstitution{},
@ -113,6 +122,9 @@ CATCH_RETURN()
RETURN_HR_IF_NULL(E_INVALIDARG, columns);
*columns = 0;
_formatInUse = _format.Get();
_fontInUse = _font.Get();
RETURN_IF_FAILED(_AnalyzeTextComplexity());
RETURN_IF_FAILED(_AnalyzeRuns());
RETURN_IF_FAILED(_ShapeGlyphRuns());
@ -144,6 +156,10 @@ CATCH_RETURN()
FLOAT originX,
FLOAT originY) noexcept
{
const auto drawingContext = static_cast<const DrawingContext*>(clientDrawingContext);
_formatInUse = drawingContext->useItalicFont ? _formatItalic.Get() : _format.Get();
_fontInUse = drawingContext->useItalicFont ? _fontItalic.Get() : _font.Get();
RETURN_IF_FAILED(_AnalyzeTextComplexity());
RETURN_IF_FAILED(_AnalyzeRuns());
RETURN_IF_FAILED(_ShapeGlyphRuns());
@ -183,7 +199,7 @@ CATCH_RETURN()
const HRESULT hr = _analyzer->GetTextComplexity(
_text.c_str(),
textLength,
_font.Get(),
_fontInUse,
&isTextSimple,
&uiLengthRead,
&_glyphIndices.at(glyphStart));
@ -240,7 +256,7 @@ CATCH_RETURN()
{
if (!run.fontFace)
{
run.fontFace = _font;
run.fontFace = _fontInUse;
}
}
@ -360,7 +376,7 @@ CATCH_RETURN()
USHORT designUnitsPerEm = metrics.designUnitsPerEm;
RETURN_IF_FAILED(_font->GetDesignGlyphAdvances(
RETURN_IF_FAILED(_fontInUse->GetDesignGlyphAdvances(
textLength,
&_glyphIndices.at(glyphStart),
&_glyphDesignUnitAdvances.at(glyphStart),
@ -368,7 +384,7 @@ CATCH_RETURN()
for (size_t i = glyphStart; i < _glyphAdvances.size(); i++)
{
_glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale;
_glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _formatInUse->GetFontSize() * run.fontScale;
}
// Set all the clusters as sequential. In a simple run, we're going 1 to 1.
@ -433,7 +449,7 @@ CATCH_RETURN()
_glyphAdvances.resize(std::max(gsl::narrow_cast<size_t>(glyphStart) + gsl::narrow_cast<size_t>(actualGlyphCount), _glyphAdvances.size()));
_glyphOffsets.resize(std::max(gsl::narrow_cast<size_t>(glyphStart) + gsl::narrow_cast<size_t>(actualGlyphCount), _glyphOffsets.size()));
const auto fontSizeFormat = _format->GetFontSize();
const auto fontSizeFormat = _formatInUse->GetFontSize();
const auto fontSize = fontSizeFormat * run.fontScale;
hr = _analyzer->GetGlyphPlacements(
@ -913,7 +929,7 @@ CATCH_RETURN();
// internal storage representation into something that matches DWrite's structures.
DWRITE_GLYPH_RUN glyphRun;
glyphRun.bidiLevel = run.bidiLevel;
glyphRun.fontEmSize = _format->GetFontSize() * run.fontScale;
glyphRun.fontEmSize = _formatInUse->GetFontSize() * run.fontScale;
glyphRun.fontFace = run.fontFace.Get();
glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart);
glyphRun.glyphCount = run.glyphCount;
@ -1226,7 +1242,7 @@ CATCH_RETURN();
{
// Get the font fallback first
::Microsoft::WRL::ComPtr<IDWriteTextFormat1> format1;
if (FAILED(_format.As(&format1)))
if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1))))
{
// If IDWriteTextFormat1 does not exist, return directly as this OS version doesn't have font fallback.
return S_FALSE;
@ -1318,7 +1334,7 @@ CATCH_RETURN();
}
else
{
run.fontFace = _font;
run.fontFace = _fontInUse;
}
// Store the font scale as well.
@ -1458,7 +1474,7 @@ try
else
{
::Microsoft::WRL::ComPtr<IBoxDrawingEffect> eff;
RETURN_IF_FAILED(s_CalculateBoxEffect(_format.Get(), _width, run.fontFace.Get(), run.fontScale, &eff));
RETURN_IF_FAILED(s_CalculateBoxEffect(_formatInUse, _width, run.fontFace.Get(), run.fontScale, &eff));
// store data in the run
run.drawingEffect = std::move(eff);

View file

@ -22,8 +22,10 @@ namespace Microsoft::Console::Render
CustomTextLayout(gsl::not_null<IDWriteFactory1*> const factory,
gsl::not_null<IDWriteTextAnalyzer1*> const analyzer,
gsl::not_null<IDWriteTextFormat*> const format,
gsl::not_null<IDWriteFontFace1*> const font,
gsl::not_null<IDWriteTextFormat*> const normalFormat,
gsl::not_null<IDWriteTextFormat*> const italicFormat,
gsl::not_null<IDWriteFontFace1*> const normalFont,
gsl::not_null<IDWriteFontFace1*> const italicFont,
size_t const width,
IBoxDrawingEffect* const boxEffect);
@ -160,11 +162,15 @@ namespace Microsoft::Console::Render
// DirectWrite analyzer
const ::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _analyzer;
// DirectWrite text format
// DirectWrite text formats
const ::Microsoft::WRL::ComPtr<IDWriteTextFormat> _format;
const ::Microsoft::WRL::ComPtr<IDWriteTextFormat> _formatItalic;
IDWriteTextFormat* _formatInUse;
// DirectWrite font face
// DirectWrite font faces
const ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _font;
const ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _fontItalic;
IDWriteFontFace1* _fontInUse;
// Box drawing effect
const ::Microsoft::WRL::ComPtr<IBoxDrawingEffect> _boxDrawingEffect;

View file

@ -24,6 +24,7 @@ namespace Microsoft::Console::Render
renderTarget(renderTarget),
foregroundBrush(foregroundBrush),
backgroundBrush(backgroundBrush),
useItalicFont(false),
forceGrayscaleAA(forceGrayscaleAA),
dwriteFactory(dwriteFactory),
spacing(spacing),
@ -37,6 +38,7 @@ namespace Microsoft::Console::Render
ID2D1RenderTarget* renderTarget;
ID2D1SolidColorBrush* foregroundBrush;
ID2D1SolidColorBrush* backgroundBrush;
bool useItalicFont;
bool forceGrayscaleAA;
IDWriteFactory* dwriteFactory;
DWRITE_LINE_SPACING spacing;

View file

@ -1909,6 +1909,7 @@ CATCH_RETURN()
// If we have a drawing context, it may be choosing its antialiasing based
// on the colors. Update it if it exists.
// Also record whether we need to render the text with an italic font.
// We only need to do this here because this is called all the time on painting frames
// and will update it in a timely fashion. Changing the AA mode or opacity do affect
// it, but we will always hit updating the drawing brushes so we don't
@ -1916,6 +1917,7 @@ CATCH_RETURN()
if (_drawingContext)
{
_drawingContext->forceGrayscaleAA = _ShouldForceGrayscaleAA();
_drawingContext->useItalicFont = textAttributes.IsItalic();
}
if (textAttributes.IsHyperlink())
@ -1943,8 +1945,10 @@ try
fiFontInfo,
_dpi,
_dwriteTextFormat,
_dwriteTextFormatItalic,
_dwriteTextAnalyzer,
_dwriteFontFace,
_dwriteFontFaceItalic,
_lineMetrics));
_glyphCell = fiFontInfo.GetSize();
@ -1952,8 +1956,15 @@ try
// Calculate and cache the box effect for the base font. Scale is 1.0f because the base font is exactly the scale we want already.
RETURN_IF_FAILED(CustomTextLayout::s_CalculateBoxEffect(_dwriteTextFormat.Get(), _glyphCell.width(), _dwriteFontFace.Get(), 1.0f, &_boxDrawingEffect));
// Prepare the text layout
_customLayout = WRL::Make<CustomTextLayout>(_dwriteFactory.Get(), _dwriteTextAnalyzer.Get(), _dwriteTextFormat.Get(), _dwriteFontFace.Get(), _glyphCell.width(), _boxDrawingEffect.Get());
// Prepare the text layout.
_customLayout = WRL::Make<CustomTextLayout>(_dwriteFactory.Get(),
_dwriteTextAnalyzer.Get(),
_dwriteTextFormat.Get(),
_dwriteTextFormatItalic.Get(),
_dwriteFontFace.Get(),
_dwriteFontFaceItalic.Get(),
_glyphCell.width(),
_boxDrawingEffect.Get());
return S_OK;
}
@ -2033,16 +2044,20 @@ float DxEngine::GetScaling() const noexcept
int const iDpi) noexcept
{
Microsoft::WRL::ComPtr<IDWriteTextFormat> format;
Microsoft::WRL::ComPtr<IDWriteTextFormat> formatItalic;
Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> analyzer;
Microsoft::WRL::ComPtr<IDWriteFontFace1> face;
Microsoft::WRL::ComPtr<IDWriteFontFace1> faceItalic;
LineMetrics lineMetrics;
return _GetProposedFont(pfiFontInfoDesired,
pfiFontInfo,
iDpi,
format,
formatItalic,
analyzer,
face,
faceItalic,
lineMetrics);
}
@ -2311,8 +2326,10 @@ CATCH_RETURN();
FontInfo& actual,
const int dpi,
Microsoft::WRL::ComPtr<IDWriteTextFormat>& textFormat,
Microsoft::WRL::ComPtr<IDWriteTextFormat>& textFormatItalic,
Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace,
Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFaceItalic,
LineMetrics& lineMetrics) const noexcept
{
try
@ -2447,11 +2464,33 @@ CATCH_RETURN();
THROW_IF_FAILED(format.As(&textFormat));
// We also need to create an italic variant of the font face and text
// format, based on the same parameters, but using an italic style.
std::wstring fontNameItalic = fontName;
DWRITE_FONT_WEIGHT weightItalic = weight;
DWRITE_FONT_STYLE styleItalic = DWRITE_FONT_STYLE_ITALIC;
DWRITE_FONT_STRETCH stretchItalic = stretch;
const auto faceItalic = _ResolveFontFaceWithFallback(fontNameItalic, weightItalic, stretchItalic, styleItalic, fontLocaleName);
Microsoft::WRL::ComPtr<IDWriteTextFormat> formatItalic;
THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontNameItalic.data(),
nullptr,
weightItalic,
styleItalic,
stretchItalic,
fontSize,
localeName.data(),
&formatItalic));
THROW_IF_FAILED(formatItalic.As(&textFormatItalic));
Microsoft::WRL::ComPtr<IDWriteTextAnalyzer> analyzer;
THROW_IF_FAILED(_dwriteFactory->CreateTextAnalyzer(&analyzer));
THROW_IF_FAILED(analyzer.As(&textAnalyzer));
fontFace = face;
fontFaceItalic = faceItalic;
THROW_IF_FAILED(textFormat->SetLineSpacing(lineSpacing.method, lineSpacing.height, lineSpacing.baseline));
THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR));

View file

@ -196,7 +196,9 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<IDWriteFactory1> _dwriteFactory;
::Microsoft::WRL::ComPtr<IDWriteTextFormat> _dwriteTextFormat;
::Microsoft::WRL::ComPtr<IDWriteTextFormat> _dwriteTextFormatItalic;
::Microsoft::WRL::ComPtr<IDWriteFontFace1> _dwriteFontFace;
::Microsoft::WRL::ComPtr<IDWriteFontFace1> _dwriteFontFaceItalic;
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _dwriteTextAnalyzer;
::Microsoft::WRL::ComPtr<CustomTextLayout> _customLayout;
::Microsoft::WRL::ComPtr<CustomTextRenderer> _customRenderer;
@ -317,8 +319,10 @@ namespace Microsoft::Console::Render
FontInfo& actual,
const int dpi,
::Microsoft::WRL::ComPtr<IDWriteTextFormat>& textFormat,
::Microsoft::WRL::ComPtr<IDWriteTextFormat>& textFormatItalic,
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace,
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFaceItalic,
LineMetrics& lineMetrics) const noexcept;
[[nodiscard]] til::size _GetClientSize() const;

View file

@ -87,6 +87,7 @@ namespace Microsoft::Console::Render
bool _isTrueTypeFont;
UINT _fontCodepage;
HFONT _hfont;
HFONT _hfontItalic;
TEXTMETRICW _tmFontMetrics;
static const size_t s_cPolyTextCache = 80;
@ -122,6 +123,7 @@ namespace Microsoft::Console::Render
COLORREF _lastFg;
COLORREF _lastBg;
bool _lastFontItalic;
[[nodiscard]] HRESULT _InvalidCombine(const RECT* const prc) noexcept;
[[nodiscard]] HRESULT _InvalidOffset(const POINT* const ppt) noexcept;
@ -152,7 +154,8 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& FontDesired,
_Out_ FontInfo& Font,
const int iDpi,
_Inout_ wil::unique_hfont& hFont) noexcept;
_Inout_ wil::unique_hfont& hFont,
_Inout_ wil::unique_hfont& hFontItalic) noexcept;
COORD _GetFontSize() const;
bool _IsMinimized() const;

View file

@ -29,8 +29,10 @@ GdiEngine::GdiEngine() :
_fInvalidRectUsed(false),
_lastFg(INVALID_COLOR),
_lastBg(INVALID_COLOR),
_lastFontItalic(false),
_fPaintStarted(false),
_hfont((HFONT)INVALID_HANDLE_VALUE)
_hfont(nullptr),
_hfontItalic(nullptr)
{
ZeroMemory(_pPolyText, sizeof(POLYTEXTW) * s_cPolyTextCache);
_rcInvalid = { 0 };
@ -88,6 +90,12 @@ GdiEngine::~GdiEngine()
_hfont = nullptr;
}
if (_hfontItalic != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic)));
_hfontItalic = nullptr;
}
if (_hdcMemoryContext != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hdcMemoryContext)));
@ -128,6 +136,9 @@ GdiEngine::~GdiEngine()
LOG_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, _hfont));
}
// Record the fact that the selected font is not italic.
_lastFontItalic = false;
if (nullptr != hdcRealWindow)
{
LOG_HR_IF(E_FAIL, !(ReleaseDC(_hwndTargetWindow, hdcRealWindow)));
@ -210,6 +221,14 @@ GdiEngine::~GdiEngine()
RETURN_IF_FAILED(s_SetWindowLongWHelper(_hwndTargetWindow, GWL_CONSOLE_BKCOLOR, colorBackground));
}
// If the italic attribute has changed, select an appropriate font variant.
const auto fontItalic = textAttributes.IsItalic();
if (fontItalic != _lastFontItalic)
{
SelectFont(_hdcMemoryContext, fontItalic ? _hfontItalic : _hfont);
_lastFontItalic = fontItalic;
}
return S_OK;
}
@ -223,12 +242,15 @@ GdiEngine::~GdiEngine()
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::UpdateFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font) noexcept
{
wil::unique_hfont hFont;
RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont));
wil::unique_hfont hFont, hFontItalic;
RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont, hFontItalic));
// Select into DC
RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, hFont.get()));
// Record the fact that the selected font is not italic.
_lastFontItalic = false;
// Save off the font metrics for various other calculations
RETURN_HR_IF(E_FAIL, !(GetTextMetricsW(_hdcMemoryContext, &_tmFontMetrics)));
@ -300,6 +322,16 @@ GdiEngine::~GdiEngine()
// Save the font.
_hfont = hFont.release();
// Persist italic font for cleanup (and free existing if necessary)
if (_hfontItalic != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic)));
_hfontItalic = nullptr;
}
// Save the italic font.
_hfontItalic = hFontItalic.release();
// Save raster vs. TrueType and codepage data in case we need to convert.
_isTrueTypeFont = Font.IsTrueTypeFont();
_fontCodepage = Font.GetCodePage();
@ -346,8 +378,8 @@ GdiEngine::~GdiEngine()
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::GetProposedFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font, const int iDpi) noexcept
{
wil::unique_hfont hFont;
return _GetProposedFont(FontDesired, Font, iDpi, hFont);
wil::unique_hfont hFont, hFontItalic;
return _GetProposedFont(FontDesired, Font, iDpi, hFont, hFontItalic);
}
// Method Description:
@ -373,12 +405,14 @@ GdiEngine::~GdiEngine()
// - Font - the actual font
// - iDpi - The DPI we will have when rendering
// - hFont - A smart pointer to receive a handle to a ready-to-use GDI font.
// - hFontItalic - A smart pointer to receive a handle to an italic variant of the font.
// Return Value:
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::_GetProposedFont(const FontInfoDesired& FontDesired,
_Out_ FontInfo& Font,
const int iDpi,
_Inout_ wil::unique_hfont& hFont) noexcept
_Inout_ wil::unique_hfont& hFont,
_Inout_ wil::unique_hfont& hFontItalic) noexcept
{
wil::unique_hdc hdcTemp(CreateCompatibleDC(_hdcMemoryContext));
RETURN_HR_IF_NULL(E_FAIL, hdcTemp.get());
@ -395,6 +429,7 @@ GdiEngine::~GdiEngine()
// it may very well decide to choose Courier New instead of the Terminal raster.
#pragma prefast(suppress : 38037, "raster fonts get special handling, we need to get it this way")
hFont.reset((HFONT)GetStockObject(OEM_FIXED_FONT));
hFontItalic.reset((HFONT)GetStockObject(OEM_FIXED_FONT));
}
else
{
@ -454,6 +489,11 @@ GdiEngine::~GdiEngine()
// Create font.
hFont.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF_NULL(E_FAIL, hFont.get());
// Create italic variant of the font.
lf.lfItalic = TRUE;
hFontItalic.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF_NULL(E_FAIL, hFontItalic.get());
}
// Select into DC

View file

@ -383,7 +383,10 @@ static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept
void StateMachine::_ActionExecute(const wchar_t wch)
{
_trace.TraceOnExecute(wch);
_engine->ActionExecute(wch);
const bool success = _engine->ActionExecute(wch);
// Trace the result.
_trace.DispatchSequenceTrace(success);
}
// Routine Description:
@ -397,7 +400,11 @@ void StateMachine::_ActionExecute(const wchar_t wch)
void StateMachine::_ActionExecuteFromEscape(const wchar_t wch)
{
_trace.TraceOnExecuteFromEscape(wch);
_engine->ActionExecuteFromEscape(wch);
const bool success = _engine->ActionExecuteFromEscape(wch);
// Trace the result.
_trace.DispatchSequenceTrace(success);
}
// Routine Description:
@ -409,7 +416,11 @@ void StateMachine::_ActionExecuteFromEscape(const wchar_t wch)
void StateMachine::_ActionPrint(const wchar_t wch)
{
_trace.TraceOnAction(L"Print");
_engine->ActionPrint(wch);
const bool success = _engine->ActionPrint(wch);
// Trace the result.
_trace.DispatchSequenceTrace(success);
}
// Routine Description:

View file

@ -76,7 +76,11 @@ void ParserTracing::TraceCharInput(const wchar_t wch)
void ParserTracing::AddSequenceTrace(const wchar_t wch)
{
_sequenceTrace.push_back(wch);
// Don't waste time storing this if no one is listening.
if (TraceLoggingProviderEnabled(g_hConsoleVirtTermParserEventTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
_sequenceTrace.push_back(wch);
}
}
void ParserTracing::DispatchSequenceTrace(const bool fSuccess) noexcept

View file

@ -1016,7 +1016,7 @@ CEditSessionObject::Release()
RETURN_HR_IF(E_FAIL, lStartResult > 0);
FullTextRange->CompareEnd(ec, InterimRange, TF_ANCHOR_END, &lEndResult);
RETURN_HR_IF(E_FAIL, lEndResult != 1);
RETURN_HR_IF(E_FAIL, lEndResult < 0);
if (lStartResult < 0)
{

View file

@ -468,6 +468,13 @@ void Utils::InitializeCampbellColorTable(const gsl::span<COLORREF> table)
std::copy(campbellColorTable.begin(), campbellColorTable.end(), table.begin());
}
void Utils::InitializeCampbellColorTable(const gsl::span<til::color> table)
{
THROW_HR_IF(E_INVALIDARG, table.size() < 16);
std::copy(campbellColorTable.begin(), campbellColorTable.end(), table.begin());
}
// Function Description:
// - Fill the first 16 entries of a given color table with the Campbell color
// scheme, in the Windows BGR order.

View file

@ -13,6 +13,7 @@ Abstract:
namespace Microsoft::Console::Utils
{
void InitializeCampbellColorTable(const gsl::span<COLORREF> table);
void InitializeCampbellColorTable(const gsl::span<til::color> table);
void InitializeCampbellColorTableForConhost(const gsl::span<COLORREF> table);
void SwapANSIColorOrderForConhost(const gsl::span<COLORREF> table);
void Initialize256ColorTable(const gsl::span<COLORREF> table);