Compare commits

..

26 Commits

Author SHA1 Message Date
Dustin L. Howett
d1e080cf63 Migrate spelling-0.0.21 changes from main 2021-01-05 11:17:53 -06:00
Dustin L. Howett
6711e4398c Migrate spelling-0.0.19 changes from main 2021-01-05 11:17:53 -06:00
Mike Griese
4a536a39e7 This is going to turn into a disaster.
We still need to reference MUX
    to get mdmerge happy, but we can't roll that up without rolling up the DLL
    and now we'll get duplicate types in WT.vcxproj. GAH. We'll need to have
    Remoting accept a callback to say "I encountered a commandline, someone,
    deal with it?", then have apphost register for that callback. So the flow
    is monarch->AppHost->AppLogic
2021-01-05 11:17:53 -06:00
Mike Griese
d4dbd530e9 change the namespace 2021-01-05 08:55:40 -06:00
Mike Griese
b3601ab8d3 some project cleanup 2021-01-05 08:43:51 -06:00
Mike Griese
ca6a5fb537 Move the commandline args to their own project, so Remoting can reference them too 2021-01-05 08:26:16 -06:00
Mike Griese
c08889585b last commit before the holidays 2020-12-18 15:21:53 -06:00
Mike Griese
5939636182 Oh yea actually remove the thing 2020-12-18 12:48:23 -06:00
Mike Griese
f978a9c52c it's hard to believe he's gone / were it so easy 2020-12-18 12:45:29 -06:00
Mike Griese
e1402d834f This seems to work to create a thread listening for the monarch, and safely tear it down. It _seems_ like it. 2020-12-18 09:29:45 -06:00
Mike Griese
e101efd11d pr nits 2020-12-18 07:12:12 -06:00
Mike Griese
d08e65ce03 _final_ final cleanup for review 2020-12-17 14:33:50 -06:00
Mike Griese
b4fe1bffbf Final cleanup for review 2020-12-17 14:26:06 -06:00
Mike Griese
0103331987 cleanup Peasant for review 2020-12-17 12:49:27 -06:00
Mike Griese
9c6eac4e86 Clean up a lot for review 2020-12-17 12:39:59 -06:00
Mike Griese
a3faed6b7d finish renaming this test 2020-12-17 11:15:57 -06:00
Mike Griese
0579b2417b LocalTests_Remoting -> UnitTests_Remoting 2020-12-17 11:10:09 -06:00
Mike Griese
590b9ff0c7 this macro makes me feel dirty 2020-12-17 11:01:52 -06:00
Mike Griese
03bfc6e8a9 This works as a unittest, but not a local test. That's batty 2020-12-17 10:50:48 -06:00
Mike Griese
5cabcfb452 add a note to future me 2020-12-17 07:43:46 -06:00
Mike Griese
9a41647ffe HOLY SHIT I GOT THE COMANDLINE TO EXECUTE IN THE CURRENT WINDOW!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 2020-12-17 07:24:16 -06:00
Mike Griese
27ace16652 whoop, we pass the commandline from the peasant, to the monarch, and then back! 2020-12-16 16:31:51 -06:00
Mike Griese
36539cfa47 This won't work, but I'm committing this becaus I finally got it to compile a String[] 2020-12-16 16:03:23 -06:00
Mike Griese
5a9cdc8b0b Shockingly, this works, it works elevated, and it works unpackaged 2020-12-16 09:51:24 -06:00
Mike Griese
1f52d35833 Yank all the M/P files, this builds?! 2020-12-16 08:08:55 -06:00
Mike Griese
3bef7bbb38 Get all the projects created and hooked up to the sln 2020-12-16 07:59:52 -06:00
568 changed files with 8192 additions and 25321 deletions

View File

@@ -1,14 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<!-- Dependencies that we can turn on to force override for testing purposes before uploading. -->
<add key="NuGet.org" value="https://api.nuget.org/v3/index.json" />
<!-- Add repositories here to the list of available repositories -->
<!-- Dependencies that we must carry because they're not on public nuget feeds right now. -->
<!--<add key="Static Package Dependencies" value="dep\packages" />-->
<!-- Use our own NuGet Feed -->
<add key="TerminalDependencies" value="https://pkgs.dev.azure.com/ms/terminal/_packaging/TerminalDependencies/nuget/v3/index.json" />
<!-- Temporarily? use the feeds from our friends in MUX for Helix test stuff -->
<add key="dotnetfeed" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" />
<add key="dnceng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="MUX-Dependencies" value="https://pkgs.dev.azure.com/ms/microsoft-ui-xaml/_packaging/MUX-Dependencies/nuget/v3/index.json" />
<!-- Internal NuGet feeds that may not be accessible outside Microsoft corporate network -->
<!--<add key="TAEF - internal" value="https://microsoft.pkgs.visualstudio.com/DefaultCollection/_packaging/Taef/nuget/v3/index.json" />
<add key="OpenConsole - Internal" value="https://microsoft.pkgs.visualstudio.com/_packaging/OpenConsole/nuget/v3/index.json" />-->
</packageSources>
<disabledPackageSources>
<clear />
</disabledPackageSources>
<config>
<add key="repositorypath" value=".\packages" />
</config>

File diff suppressed because it is too large Load Diff

View File

@@ -21,8 +21,7 @@
"/res/terminal/",
"/doc/specs/",
"/doc/cascadia/",
"/doc/user-docs/",
"/src/tools/MonarchPeasantSample/",
"/doc/user-docs/"
],
"SuffixFilters": [
".dbb",
@@ -39,5 +38,5 @@
".rec",
".err",
".xlsx"
]
]
}

View File

@@ -3,9 +3,9 @@
<!-- This file is read by XES, which we use in our Release builds. -->
<PropertyGroup Label="Version">
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
<XesBaseYearForStoreVersion>2021</XesBaseYearForStoreVersion>
<XesBaseYearForStoreVersion>2020</XesBaseYearForStoreVersion>
<VersionMajor>1</VersionMajor>
<VersionMinor>7</VersionMinor>
<VersionMinor>6</VersionMinor>
<VersionInfoProductName>Windows Terminal</VersionInfoProductName>
</PropertyGroup>
</Project>

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,17 +48,3 @@ 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.

View File

@@ -1,397 +0,0 @@
# 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
When adding a setting to the UI, make sure you follow the [UWP design guidance](https://docs.microsoft.com/windows/uwp/design/).
#### 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

@@ -76,11 +76,9 @@
"copy",
"duplicateTab",
"find",
"findMatch",
"moveFocus",
"moveTab",
"newTab",
"newWindow",
"nextTab",
"openNewTabDropdown",
"openSettings",
@@ -95,8 +93,6 @@
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"scrollToBottom",
"scrollToTop",
"sendInput",
"setColorScheme",
"setTabColor",
@@ -107,7 +103,6 @@
"toggleFocusMode",
"toggleFullscreen",
"togglePaneZoom",
"toggleReadOnlyMode",
"toggleShaderEffects",
"wt",
"unbound"
@@ -140,13 +135,6 @@
],
"type": "string"
},
"FindMatchDirection": {
"enum": [
"next",
"prev"
],
"type": "string"
},
"SplitState": {
"enum": [
"vertical",
@@ -381,13 +369,6 @@
"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"
}
}
}
@@ -568,35 +549,6 @@
}
]
},
"FindMatchAction": {
"description": "Arguments corresponding to a Find Match Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "findMatch" },
"direction": {
"$ref": "#/definitions/FindMatchDirection",
"default": "prev",
"description": "The direction to search in. \"prev\" will search upwards in the buffer, and \"next\" will search downwards."
}
}
}
],
"required": [ "direction" ]
},
"NewWindowAction": {
"description": "Arguments corresponding to a New Window Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{ "$ref": "#/definitions/NewTerminalArgs" },
{
"properties": {
"action": { "type":"string", "pattern": "newWindow" }
}
}
]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
@@ -621,8 +573,6 @@
{ "$ref": "#/definitions/ScrollUpAction" },
{ "$ref": "#/definitions/ScrollDownAction" },
{ "$ref": "#/definitions/MoveTabAction" },
{ "$ref": "#/definitions/FindMatchAction" },
{ "$ref": "#/definitions/NewWindowAction" },
{ "type": "null" }
]
},
@@ -670,26 +620,11 @@
"description": "When set to true, tabs are always displayed. When set to false and \"showTabsInTitlebar\" is set to false, tabs only appear after opening a new tab.",
"type": "boolean"
},
"centerOnLaunch": {
"default": false,
"description": "When set to `true`, the terminal window will auto-center itself on the display it opens on. The terminal will use the \"initialPosition\" to determine which display to open on.",
"type": "boolean"
},
"inputServiceWarning": {
"default": true,
"description": "Warning if 'Touch Keyboard and Handwriting Panel Service' is disabled.",
"type": "boolean"
},
"copyOnSelect": {
"default": false,
"description": "When set to true, a selection is immediately copied to your clipboard upon creation. When set to false, the selection persists and awaits further action.",
"type": "boolean"
},
"focusFollowMouse": {
"default": false,
"description": "When set to true, the terminal will focus the pane on mouse hover.",
"type": "boolean"
},
"copyFormatting": {
"default": true,
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
@@ -714,10 +649,6 @@
"description": "Sets the default profile. Opens by clicking the \"+\" icon or typing the key binding assigned to \"newTab\".",
"type": "string"
},
"startupActions": {
"description": "Sets the list of actions to apply if no command line is provided. Uses the same format as command line arguments",
"type": "string"
},
"disabledProfileSources": {
"description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.",
"items": {
@@ -862,16 +793,6 @@
"type": "string"
}
]
},
"windowingBehavior": {
"default": "useNew",
"description": "Controls how new terminal instances attach to existing windows. \"useNew\" will always create a new window. \"useExisting\" will create new tabs in the most recently used window on this virtual desktop, and \"useAnyExisting\" will create tabs in the most recent window on any desktop.",
"enum": [
"useNew",
"useExisting",
"useAnyExisting"
],
"type": "string"
}
},
"required": [
@@ -1001,10 +922,9 @@
},
"cursorShape": {
"default": "bar",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"doubleUnderscore\" ( ‗ )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"description": "Sets the shape of the cursor. Possible values:\n -\"bar\" ( ┃, default )\n -\"emptyBox\" ( ▯ )\n -\"filledBox\" ( █ )\n -\"underscore\" ( ▁ )\n -\"vintage\" ( ▃ )",
"enum": [
"bar",
"doubleUnderscore",
"emptyBox",
"filledBox",
"underscore",
@@ -1137,11 +1057,6 @@
"type": "boolean",
"default": false
},
"tabColor": {
"$ref": "#/definitions/Color",
"description": "Sets the color of the profile's tab. Using the tab color picker will override this color.",
"type": ["string", "null"]
},
"tabTitle": {
"description": "If set, will replace the name as the title to pass to the shell on startup. Some shells (like bash) may choose to ignore this initial value, while others (cmd, powershell) may use this value over the lifetime of the application.",
"type": ["string", "null"]

View File

@@ -1,302 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-11-23
last updated: 2020-12-15
issue id: #2871
---
# Focus Pane Actions
## Abstract
Currently, the Terminal only allows users to navigate through panes
_directionally_. However, we might also want to allow a user to navigate through
panes in most recently used order ("MRU" order), or to navigate directly to a
specific pane. This spec proposes some additional actions in order to enable
these sorts of scenarios.
## Background
### Inspiration
`tmux` allows the user to navigate through panes using its `select-pane`
command. The `select-pane` command works in the following way:
```
select-pane [-DLlMmRU] [-T title] [-t target-pane]
Make pane target-pane the active pane in window target-window, or set its
style (with -P). If one of -D, -L, -R, or -U is used, respectively the
pane below, to the left, to the right, or above the target pane is used.
-l is the same as using the last-pane command.
-m and -M are used to set and clear the marked pane. There is one marked
pane at a time, setting a new marked pane clears the last. The marked pane
is the default target for -s to join-pane, swap-pane and swap-window.
```
_from `man tmux`_.
The Terminal currently allows the user to navigate through panes with the
`moveFocus` action, which only accepts a `direction` to move in.
Additionally, the Terminal allows movement between tabs with the `nextTab` and
`prevTab` actions, who move between tabs either in-order or in MRU order.
Furthermore, these actions may or may not display the "tab switcher" user
interface, based on the value of `tabSwitcherMode`.
### User Stories
* **Scenario 1**: A user who wants to be able to split the window into 4 equal
corners from the commandline. Currently this isn't possible, because the user
cannot move focus during the startup actions - `split-pane` actions always end
up splitting the current leaf in the tree of panes. (see [#5464])
* **Scenario 2**: A user who wants to quickly navigate to the previous pane they
had opened. (see [#2871])
* **Scenario 3**: A user who wants to bind a keybinding like <kbd>alt+1</kbd>,
<kbd>alt+2</kbd>, etc to immediately focus the first, second, etc. pane in a
tab. (see [#5803])
### Future Considerations
There's been talk of updating the advanced tab switcher to also display panes,
in addition to just tabs. This would allow users to navigate through the ATS
directly to a pane, and see all the panes in a tab. Currently, `tabSwitcherMode`
changes the behavior of `nextTab`, `prevTab` - should we just build the
`paneSwitcherMode` directly into the action we end up designing?
## Solution Design
Does using the pane switcher with a theoretical `focusPane(target=id)` action
even make sense? Certainly not! That's like `switchToTab(index=id)`, the user
already knows which tab they want to go to, there's no reason to pop an
ephemeral UI in front of them.
Similarly, it almost certainly doesn't make sense to display the pane switcher
while moving focus directionally. Consider moving focus with a key bound to the
arrow keys. Displaying another UI in front of them while moving focus with the
arrow keys would be confusing.
Addressing Scenario 1 is relatively easy. So long as we add any of the proposed
actions, including the existing `moveFocus` action as a subcommand that can be
passed to `wt.exe`, then the user should be able to navigate through the panes
they've created with the startup commandline, and build the tree of panes
however they see fit.
Scenario 2 is more complicated, because MRU switching is always more
complicated. Without a UI of some sort, there's no way to switch to another pane
in the MRU order without also updating the MRU order as you go. So this would
almost certainly necessitate a "pane switcher", like the tab switcher.
### Proposal A: Add next, prev to moveFocus
* `moveFocus(direction="up|down|left|right|next|prev")`
* **Pros**:
- Definitely gets the "MRU Pane Switching" scenario working
* **Cons**:
- Doesn't really address any of the other scenarios
- How will it play with pane switching in the UI?
- MRU switching without a dialog to track & display the MRU stack doesn't
really work - this only allows to the user to navigate to the most recently
used pane, or through all the panes in least-recently-used order. This is
because switching to the MRU pane _will update the MRU pane_.
❌ This proposal is no longer being considered.
### Proposal B: focusNextPane, focusPrevPane with order, useSwitcher args
```json
// Focus pane 1
// - This is sensible, no arguments here
{ "command": { "action": "focusPane", "id": 1 } },
// Focus the next MRU pane
// - Without the switcher, this can only go one pane deep in the MRU stack
// - presumably once there's a pane switcher, it would default to enabled?
{ "command": { "action": "focusNextPane", "order": "mru" } },
// Focus the prev inOrder pane
// - this seems straightforward
{ "command": { "action": "focusPrevPane", "order": "inOrder" } },
// Focus the next pane, in mru order, explicitly disable the switcher
// - The user opted in to only being able to MRU switch one deep. That's fine, that's what they want.
{ "command": { "action": "focusNextPane", "order": "mru", "useSwitcher": false} },
// Focus the prev inOrder pane, explicitly with the switcher
// - Maybe they disabled the switcher globally, but what it on for this action?
{ "command": { "action": "focusPrevPane", "order": "inOrder", "useSwitcher": true } },
```
_From [discussion in the implementation
PR](https://github.com/microsoft/terminal/pull/8183#issuecomment-729672645)_
Boiled down, that's three actions:
* `focusPane(target=id)`
* `focusNextPane(order="inOrder|mru", useSwitcher=true|false)`
* `focusPrevPane(order="inOrder|mru", useSwitcher=true|false)`
* **Pros**:
- Everything is explicit, including the option to use the pane switcher (when
available)
- Adds support for in-order pane switching
- No "conditional parameters" - where providing one argument makes other
arguments invalid or ambiguous.
* **Cons**:
- Doesn't really address any of the other scenarios
- What does the "next most-recently-used tab" even mean? How is it different
than "previous most-recently-used tab"? Semantically, these are the same
thing!
- No one's even asked for in-order pane switching. Is that a UX that even
really makes sense?
❌ This proposal is no longer being considered.
> 👉 **NOTE**: At this point, we stopped considering navigating in both MRU
> "directions", since both the next and prev MRU pane are the same thing. We're
> now using "last" to mean "the previous MRU pane".
### Proposal C: One actions, combine the args
* `moveFocus(target=id|"up|down|left|right|last")`
* **Pros**:
- Absolutely the least complicated action to author. There's only one
parameter, `target`.
- No "conditional parameters".
* **Cons**:
- How do we express this in the Settings UI? Mixed-type enums work fine for
the font weight, where each enum value has a distinct integer value it maps
to, but in this case, using `id` is entirely different from the other
directional values
❌ This proposal is no longer being considered.
### Proposal D: Two actions
* `focusPane(target=id)`
* `moveFocus(direction="up|down|left|right|last")`
* **Pros**:
- Each action does explicitly one thing.
* **Cons**:
- two actions for _similar_ behavior
- This now forks the "Direction" enum into "MoveFocusDirection" and
"ResizeDirection" (because `resizePane(last)` doesn't make any sense).
This proposal doesn't really have any special consideration for the pane
switcher UX. Neither of these actions would summon the pane switcher UX.
### Proposal E: Three actions
* `focusPane(target=id)`
* `moveFocus(direction="up|down|left|right")`
* `focusLastPane(usePaneSwitcher=false|true)`
In this design, neither `focusPane` nor `moveFocus` will summon the pane
switcher UI (even once it's added). However, the `focusLastPane` one _could_,
and subsequent keypresses could pop you through the MRU stack, while it's
visible? The pane switcher could then display the panes for the tab in MRU
order, and the user could just use the arrow keys to navigate the list if they
so choose.
* **Pros**:
- Each action does explicitly one thing.
- Design accounts for future pane switcher UX
* **Cons**:
- Three separate actions for similar behavior
❌ This proposal is no longer being considered.
### Proposal F: It's literally just tmux
_Also known as the "one action to rule them all" proposal_
`focusPane(target=id, direction="up|down|left|right|last")`
Previously, this design was avoided, because what does `focusPane(target=4,
direction=down)` do? Does it focus pane 4, or does it move focus down?
`tmux` solves this in one action by just doing both!
```
Make pane target-pane the active pane ... If one of -D, -L, -R, or -U is used,
respectively the pane below, to the left, to the right, or above the target pane
is used.
```
_from `man tmux`_.
So `focusPane(target=1, direction=up)` will attempt to focus the pane above pane
1. This action would not summon the pane switcher UX, even for
`focusPane(direction=last)`
* **Pros**:
- Fewest redundant actions
* **Cons**:
- Is this intuitive? That combining the params would do both, with `target`
happening "first"?
- Assumes that there will be a separate action added in the future for "Open
the pane switcher (with some given ordering)"
> 👉 **NOTE**: At this point, the author considered "Do we even want a separate
> action to engage the tab switcher with panes expanded?" Perhaps panes being
> visible in the tab switcher is just part fo the tab switcher's behavior. Maybe
> there shouldn't be a separate "open the tab switcher with the panes expanded
> to the pane I'm currently on, and the panes listed in MRU order" action.
❌ This proposal is no longer being considered.
## Conclusion
After much discussion as a team, we decided that **Proposal D** would be the
best option. We felt that there wasn't a need to add any extra configuration to
invoke the "pane switcher" as anything different than the "tab switcher". The
"pane switcher" should really just exist as a part of the functionality of the
advanced tab switcher, not as it's own thing.
Additionally, we concurred that the new "direction" value should be `prev`, not
`last`, for consistency's sake.
## UI/UX Design
The only real UX being added with the agreed upon design is allowing the user to
execute an action to move to the previously active pane within a single tab. No
additional UX (including the pane switcher) is being prescribed in this spec at
this time.
## Potential Issues
<table>
<tr>
<td><strong>Compatibility</strong></td>
<td>
We've only adding a single enum value to an existing enum. Since we're not
changing the meaning of any of the existing values, we do not expect any
compatibility issues there. Additionally, we're not changing the default value
of the `direction` param of the `moveFocus` action, so there are no further
compatibility concerns there. Furthermore, no additional parameters are being
added to the `moveFocus` action that would potentially give it a different
meaning.
</td>
</tr>
</table>
In the current design, there's no way to move through all the panes with a
single keybinding. For example, if a user wanted to bind <kbd>Alt+]</kbd> to
move to the "next" pane, and <kbd>Alt+[</kbd> to move to the "previous" one.
These movements would necessarily need to be in-order traversals, since there's
no way of doing multiple MRU steps.
Fortunately, no one's really asked for traversing the panes in-order, so we're
not really worried about this. Otherwise, it would maybe make sense for `last`
to be the "previous MRU pane", and reserve `next`/`prev` for in-order traversal.
[#2871]: https://github.com/microsoft/terminal/issues/2871
[#5464]: https://github.com/microsoft/terminal/issues/5464
[#5803]: https://github.com/microsoft/terminal/issues/5803

View File

@@ -1,128 +0,0 @@
---
author: Pankaj Bhojwani, pabhojwa@microsoft.com
created on: 2020-11-20
last updated: 2021-2-5
issue id: #8345
---
# Appearance configuration objects for profiles
## Abstract
This spec outlines how we can support 'configuration objects' in our profiles, which
will allow us to render differently depending on the state of the control. For example, a
control can be rendered differently if it's focused as compared to when it's unfocused.
## Inspiration
Reference: [#3062](https://github.com/microsoft/terminal/issues/3062)
Users want there to be a more visible indicator than the one we have currently for which
pane is focused and which panes are unfocused. This change would grant us that feature.
## Solution Design
The implementation design for appearance config objects centers around the recent change where inheritance was added to the
`TerminalSettings` class in the Terminal Settings Model - i.e. different `TerminalSettings` objects can inherit from each other.
The reason for this change was that we did not want a settings reload to erase any overrides `TermControl` may have made
to the settings during runtime. By instead passing a child of the `TerminalSettings` object to the control, we can change
the parent of the child during a settings reload without the overrides being erased (since those overrides live in the child).
The idea behind unfocused appearance configurations is similar. We will pass in another `TerminalSettings` object to the control,
which is simply a child that already has some overrides in it. When the control gains or loses focus, it simply switches between
the two settings objects appropriately.
### Allowed parameters
For now, these states are meant to be entirely appearance-based. So, not all parameters which can be
defined in a `Profile` can be defined in this new object (for example, we do not want parameters which
would cause a resize in this object.) Here is the list of parameters we will allow:
- Anything regarding colors: `colorScheme`, `foreground`, `background`, `cursorColor` etc
- Anything regarding background image: `path`, `opacity`, `alignment`, `stretchMode`
- `cursorShape`
We may wish to allow further parameters in these objects in the future (like `bellStyle`?). The addition
of further parameters can be discussed in the future and is out of scope for this spec.
### Inheritance
The inheritance model can be thought of as an 'all-or-nothing' approach in the sense that the `unfocusedAppearance` object
is considered as a *single* setting instead of an object with many settings. We have chosen this model because it is cleaner
and easier to understand than the alternative, where each setting within an `unfocusedAppearance` object has a parent from which
it obtains its value.
Note that when `TerminalApp` initializes a control, it creates a `TerminalSettings` object for that profile and passes the
control a child of that object (so that the control can store overrides in the child, as described earlier). If an unfocused
config is defined in the profile (or in globals/profile defaults), then `TerminalApp` will create a *child of that child*,
put the relevant overrides in it, and pass that into the control as well. Thus, the inheritance of any undefined parameters
in the unfocused config will be as follows:
1. The unfocused config specified in the profile (or in globals/profile defaults)
2. Overrides made by the terminal control
3. The parent profile
## UI/UX Design
Users will be able to add a new setting to their profiles that will look like this:
```
"unfocusedAppearance":
{
"colorScheme": "Campbell",
"cursorColor": "#888",
"cursorShape": "emptyBox",
"foreground": "#C0C0C0",
"background": "#000000"
}
```
When certain appearance settings are changed via OSC sequences (such as the background color), only the focused/regular
appearance will change and the unfocused one will remain unchanged. However, since the unfocused settings object inherits
from the regular one, it will still apply the change (provided it does not define its own value for that setting).
## Capabilities
### Accessibility
Does not affect accessibility.
### Security
Does not affect security.
### Reliability
This is another location in the settings where parsing/loading the settings may fail. However, this is the case
for any new setting we add so I would say that this is a reasonable cost for this feature.
### Compatibility
Should not affect compatibility.
### Performance, Power, and Efficiency
Rapidly switching between many panes, causing several successive appearance changes in a short period of time, could
potentially impact performance. However, regular/reasonable pane switching should not have a noticeable effect.
## Potential Issues
Inactive tabs will be 'rendered' in the background with the `UnfocusedRenderingParams` object, we need to make
sure that switching to an inactive tab (and so causing the renderer to update with the 'normal' parameters)
does not cause the window to flash/show a jarring indicator that the rendering values changed.
## Future considerations
We will need to decide how this will look in the settings UI.
We may wish to add more states in the future (like 'elevated'). When that happens, we will need to deal with how
these appearance objects can scale/layer over each other. We had a lot of discussion about this and could not find
a suitable solution to the problem of multiple states being valid at the same time (like unfocused and elevated).
This, along with the fact that it is uncertain if there even will be more states we would want to add led us to
the conclusion that we should only support the unfocused state for now, and come back to this issue later. If there
are no more states other than unfocused and elevated, we could allow combining them (like having an 'unfocused elevated' state).
If there are more states, we could do the implementation as an extension rather than inherently supporting it.
## Resources

View File

@@ -1,562 +0,0 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-10-30
last updated: 2020-02-05
issue id: #4472
---
# Windows Terminal Session Management
## Abstract
This document is intended to serve as an addition to the [Process Model 2.0
Spec]. That document provides a big-picture overview of changes to the entirety
of the Windows Terminal process architecture, including both the split of
window/content processes, as well as the introduction of monarch/peasant
processes. The focus of that document was to identify solutions to a set of
scenarios that were closely intertwined, and establish these solutions would
work together, without preventing any one scenario from working. What that
document did not do was prescribe specific solutions to the given scenarios.
This document offers a deeper dive on a subset of the issues in [#5000], to
describe specifics for managing multiple windows with the Windows Terminal. This
includes features such as:
* Run `wt` in the current window ([#4472])
* Single Instance Mode ([#2227])
## Solution Design
### Monarch and Peasant Processes
This document assumes the reader is already familiar with the "Monarch and
Peasant" architecture as detailed in the [Windows Terminal Process Model 2.0
Spec]. As a quick summary:
* Every Windows Terminal window is a "Peasant" process.
* One of the Windows Terminal window processes is also the "Monarch" process.
The Monarch is picked randomly from the Terminal windows, and there is only
ever one Monarch process at a time.
* Peasants can communicate with the monarch when certain state changes (such as
their window being activated), and the monarch can send commands to any of the
peasants.
This architecture will be used to enable each of the following scenarios.
### Scenario: Open new tabs in most recently used window
A common feature of many browsers is that when a web URL is clicked somewhere,
the web page is opened as a new tab in the most recently used window of the
browser. This functionality is often referred to as "glomming", as the new tab
"gloms" onto the existing window.
Currently, the terminal does not support such a feature - every `wt` invocation
creates a new window. With the monarch/peasant architecture, it'll now be
possible to enable such a scenario.
As each window is activated, it will call a method on the `Monarch` object
(hosted by the monarch process) which will indicate that "I am peasant N, and
I've been focused". The monarch will use those method calls to update its own
internal stack of the most recently used windows.
Whenever a new `wt.exe` process is launched, that process will _first_ ask the
monarch if it should run the commandline in an existing window, or create its
own window.
![auto-glom-wt-exe](auto-glom-wt-exe.png)
If glomming is enabled, the monarch will dispatch the commandline to the
appropriate window for them to handle instead. To the user, it'll seem as if the
tab just opened in the most recent window.
Users should certainly be able to specify if they want new instances to glom
onto the MRU window or not. You could imagine that currently, we default to the
hypothetical value `"windowingBehavior": "useNew"`, meaning that each new wt gets
its own new window.
If glomming is disabled, then the Monarch will call back to the peasant and tell
it to run the provided commandline. The monarch will use the return value of
`ExecuteCommandline` to indicate that the calling process should create a window
and become a peasant process, and run the commandline itself.
#### Glomming within the same virtual desktop
When links are opened in the new Edge browser, they will only glom onto an
existing window if that window is open in the current virtual desktop. This
seems like a good idea of a feature for the Terminal to follow as well.
There must be some way for an application to determine which virtual desktop it
is open on. We could use that information to have the monarch track the last
active window _per-desktop_, and only glom when there's one on the current
desktop.
We could make the `windowingBehavior` property accept a variety of
configurations:
- `"useExisting"`: always glom to the most recent window, regardless of desktop.
- `"useExistingOnSameDesktop"`: Only glom if there's an existing window on this
virtual desktop, otherwise create a new window. This will be the new default
value.
- `"useNew"`: Never glom, always create a new window. This is technically the
current behavior of the Terminal.
### Handling the current working directory
Consider the following scenario: the user runs `wt -d .` in the address bar of
explorer, and the monarch determines that this new tab should be created in an
existing window. For clarity during this example, we will label the existing
window WT[1], and the second `wt.exe` process WT[2].
An example of this scenario is given in the following diagram:
![single-instance-mode-cwd](single-instance-mode-cwd.png)
In this scenario, we want the new tab to be spawned in the current working
directory of WT[2], not WT[1]. So when WT[1] is about to run the commands that
were passed to WT[2], WT[1] will need to:
* First, stash its own CWD
* Change to the CWD of WT[2]
* Run the commands from WT[2]
* Then return to its original CWD.
So, as a part of the interface that a peasant uses to communicate the startup
commandline to the monarch, we should also include the current working
directory.
### Scenario: Run `wt` in the current window
One often requested scenario is the ability to run a `wt.exe` commandline in the
current window, as opposed to always creating a new window. Presume we have the
ability to communicate between different window processes. The logical extension
of this scenario would be "run a `wt` commandline in _any_ given WT window".
Each window process will have its own unique ID assigned to it by the monarch.
This ID will be a positive number. Windows can also have names assigned to them.
These names are strings that the user specifies. A window will always have an
ID, but not necessarily a name. Running a command in a given window with ID N
should be as easy as something like:
```sh
wt.exe --window N new-tab ; split-pane
```
(or for shorthand, `wt -w N new-tab ; split-pane`).
More formally, we will add the following parameter to the top-level `wt`
command:
#### `--window,-w <window-id>`
Run these commands in the given Windows Terminal session. This enables opening
new tabs, splits, etc. in already running Windows Terminal windows.
* If `window-id` is `0`, run the given commands in _the current window_.
* If `window-id` is a negative number, or the reserved name `new`, run the
commands in a _new_ Terminal window.
* If `window-id` is the ID or name of an existing window, then run the
commandline in that window.
* If `window-id` is _not_ the ID or name of an existing window, create a new
window. That window will be assigned the ID or name provided in the
commandline. The provided subcommands will be run in that new window.
* If `window-id` is omitted, then obey the value of `windowingBehavior` when
determining which window to run the command in.
_Whenever_ `wt.exe` is started, it must _always_ pass the provided commandline
first to the monarch process for handling. This is important for glomming
scenarios (as noted above). The monarch will parse the commandline, determine
which window the commandline is destined for, then call `ExecuteCommandline` on
that peasant, who will then run the command.
#### Running commands in the current window:`wt --window 0`
If `wt -w 0 <commands>` is run _outside_ a WT instance, it could attempt to glom
onto _the most recent WT window_ instead. This seems more logical than something
like `wt --window last` or some other special value indicating "run this in the
MRU window".<sup>[[2]](#footnote-2)</sup>
That might be a simple, but **wrong**, implementation for "the current window".
If the peasants always raise an event when their window is focused, and the
monarch keeps track of the MRU order for peasants, then one could naively assume
that the execution of `wt -w 0 <commands>` would always return the window the
user was typing in, the current one. However, if someone were to do something
like `sleep 10 ; wt -w 0 <commands>`, then the user could easily focus another
WT window during the sleep, which would cause the MRU window to not be the same
as the window executing the command.
To solve this issue, we'll other than
attempting to use the `WT_SESSION` environment variable. If a `wt.exe` process
is spawned and that's in its environment variables, it could try and ask the
monarch for the peasant who's hosting the session corresponding to that GUID.
This is more of a theoretical solution than anything else.
In the past we've been reluctant to rely too heavily on `WT_SESSION`. However,
an environment variable does seem to be the only reliable way to be confident
where the window was created from. We could introduce another environment
variable instead - `WT_WINDOW_ID`. That would allow us to shortcut the session
ID lookup. However, I worry about exposing the window ID as an environment
variable. If we do that, users will inevitably use that instead of the `wt -0`
alias, which should take care of the work for them. Additionally, `WT_WINDOW_ID`
wouldn't update in the child processes as tabs are torn out of windows to create
new windows.
Both solutions are prone to the user changing the value of the variable to some
garbage value. If they do that, this lookup will most certainly not work as
expected. Using the session ID (a GUID) instead of the window ID (an int) makes
it less likely that they guess the ID of an existing instance.
#### Running commands in a new window:`wt --window -1` / `wt --window new`
If the user passes a negative number, or the reserved name `new` to the
`--window` parameter, then we will always create a new window for that
commandline, regardless of the value of `windowingBehavior`. This will allow
users to do something like `wt -w -1 new-tab` to _always_ create a new window.
#### `--window` in subcommands
The `--window` parameter is a setting to `wt.exe` itself, not to one of its
subcommands (like `new-tab` or `split-pane`). This means that all of the
subcommands in a particular `wt` commandline will all be handled by the same
session. For example, let us consider a user who wants to open a new tab in
window 2, and split a new pane in window 3, all at once. The user _cannot_ do
something like:
```cmd
wt -w 2 new-tab ; -w 3 split-pane
```
Instead, the user will need to separate the commands (by whatever their shell's
own command delimiter is) and run two different `wt.exe` instances:
```cmd
wt -w 2 new-tab & wt -w 3 split-pane
```
This is done to make the parsing of the subcommands easier, and for the internal
passing of arguments simpler. If the `--window` parameter were a part of each
subcommand, then each individual subcommand's parser would need to be
enlightened about that parameter, and then it would need to be possible for any
single part of the commandline to call out to another process. It would be
especially tricky then to coordinate the work being done across process here.
The source process would need some sort of way to wait for the other process to
notify the source that a particular subcommand completed, before allowing the
source to dispatch the next part of the commandline.
Overall, this is seen as unnecessarily complex, and dispatching whole sets of
commands as a simpler solution.
### Naming Windows
It's not user-friendly to rely on automatically generated, invisible numbers to
identify windows. There's not a great way of identifying which window is which.
The user would need to track the IDs in their head manually. Instead, we'll
allow the user to provide a string name for the window. This name can be used to
address a window in addition to the ID.
Names can be provided on the commandline, in the original commandline. For
example, `wt -w foo nt` would name the new window "foo". Names can also be set
with a new action, `NameWindow`<sup>[[3]](#footnote-3)</sup>. `name-window`
could also be used as a subcommand. For example, `wt -w 4 name-window bar` would
name window 4 "bar".
To keep identities mentally distinct, we will disallow names that are integers
(positive or negative). This will prevent users from renaming a window to `2`,
then having `wt -w 2` be ambiguous as to which window it refers to.
Names must also be unique. If a user attempts to set the name of the window to
an already-used name, we'll need to ignore the name change. We could also
display a "toast" or some other type of low-impact message to the user. That
message would have some text like: "Unable to rename window. Another window with
that name already exists".
The Terminal will reserve the name `new`. It will also reserve any names
starting with the character `_`. The user will not be allowed to set the window
name to any of these reserved names. Reserving `_*` allows us to add other
keywords in the future, without introducing a breaking change.
## UI/UX Design
### `windowingBehavior` details
The following list gives greater breakdown of the values of `windowingBehavior`,
and how they operate:
* `"windowingBehavior": "useExisting", "useExistingOnSameDesktop"`:
**Browser-like glomming**
- New instances open in the current window by default.
- `newWindow` opens a new window.
- Tabs can be torn out to create new windows.
- `wt -w -1` opens a new window.
* `"windowingBehavior": "useNew"`: No auto-glomming. This is **the current
behavior** of the Terminal.
- New instances open in new windows by default
- `newWindow` opens a new window
- Tabs can be torn out to create new windows.
- `wt -w -1` opens a new window.
We'll be changing the default behavior from `useNew` to
`useExistingOnSameDesktop`. This will be more consistent with other tabbed
applications.
## Concerns
<table>
<tr>
<td><strong>Accessibility</strong></td>
<td>
There is no expected accessibility impact from this feature. Each window will
handle UIA access as it normally does.
In the future, we could consider exposing the window IDs and/or names via UIA.
</td>
</tr>
<tr>
<td><strong>Security</strong></td>
<td>
Many security concerns have already be covered in greater detail in the parent
spec, [Process Model 2.0 Spec].
When attempting to instantiate the Monarch, COM will only return the object from
a server running at the same elevation level. We don't need to worry about
unelevated peasants connecting to the elevated Monarch, or vice-versa.
</td>
</tr>
<tr>
<td><strong>Reliability</strong></td>
<td>
We will need to be careful when working with objects hosted by another process.
Any work we do with it MUST be in a try/catch, because at _any_ time, the other
process could be killed. At any point, a window process could be killed. Both
the monarch and peasant code will need to be redundant to such a scenario, and
if the other process is killed, make sure to display an appropriate error and
either recover or exit gracefully.
In any and all these situations, we will want to try and be as verbose as
possible in the logging. This will make tracking which process had the error
occur easier.
</td>
</tr>
<tr>
<td><strong>Compatibility</strong></td>
<td>
We will be changing the default behavior of the Terminal to auto-glom to the
most-recently used window on the same desktop in the course of this work, which
will be a breaking UX change. This is behavior that can be reverted with the
`"windowingBehavior": "useNew"` setting.
We acknowledge that this is a pretty massive change to the default experience of
the Terminal. We're planning on doing some polling of users to determine which
behavior they want by default. Additionally, we'll be staging the rollout of
this feature, using the Preview builds of the Terminal. The release notes that
first include it will call extra attention to this feature. We'll ask that users
provide their feedback in a dedicated thread, so we have time to collect
opinions from users before rolling the change out to all users.
We may choose to only change the default to `useExistingOnSameDesktop` once tab
tear out is available, so users who are particularly unhappy about this change
can still tear out the tab (if they can't be bothered to change the setting).
</td>
</tr>
<tr>
<td><strong>Performance, Power, and Efficiency</strong></td>
<td>
There's no dramatic change expected here. There may be a minor delay in the
spawning of new terminal instances, due to requiring cross-process hops for the
communication between monarch and peasant processes.
</td>
</tr>
</table>
## Potential Issues
### Mixed Elevation Levels
As of December 2020, we're no longer pursuing a "mixed-elevation" scenario for
the Terminal. This makes many of the cross-elevation scenarios simpler. Elevated
and unelevated `wt` instances will always remain separate. The different
elevation levels will maintain separate lists of window IDs. If the user is
running both an elevated and unelevated window, then there will be two monarchs.
One elevated, and the other unelevated.
There will also be some edge cases when handling the commandline that will need
special care. Say the user wanted to open a new tab in the elevated window, from
and unelevated `explorer.exe`. That would be a commandline like:
```sh
wt -w 0 new-tab -d . --elevated
```
Typically we first determine which window the commandline is intended for, then
dispatch it to that window. In this case, the `-w 0` will cause us to pass the
commandline to the current unelevated window. Then, that window will try to open
an elevated tab, fail, and create a new `wt.exe` process. This second `wt.exe`
process will lose the `-w 0` context. It won't inform the elevated monarch that
this commandline should be run in the active session.
We will need to make sure that special care is taken when creating elevated
instances that we maintain the `--window` parameter passed to the Terminal.
### `wt` Startup Commandline Options
There are a few commandline options which can be provided to `wt.exe` which
don't make sense to pass to another session. These options include (but are not
limited to):
* `--initialSize r,c`
* `--initialPosition x,y`
* `--fullscreen`, `--maximized`, etc.
When we're passing a commandline to another instance to handle, these arguments
will be ignored. they only apply to the initial creation of a window.
`--initialSize 32, 120` doesn't make sense if the window already has a size.
On startup of a new window, we currently assume that the first command is always
`new-tab`. When passing commandlines to existing windows, we won't need to make
that assumption anymore. There will already be existing tabs.
### Monarch MRU Window Tracking
As stated above, the monarch is responsible for tracking the MRU window stack.
However, when the monarch is closed, this state will be lost. The new monarch
will be elected, but it will be unable to ask the old monarch for the MRU
order of the windows.
We had previously considered an _acceptable_ UX when this would occur. We would
randomize the order (with the new monarch becoming the MRU window). If someone
noticed this bug and complained, then we had a theoretical solution prepared.
The peasants could inform not only the monarch, but _all other peasants_ when
they become activated. This would mean all peasants are simultaneously tracking
the MRU stack. This would mean that any given peasant would be prepared always
to become the monarch.
A simpler solution though would be to not track the MRU stack in the Monarch at
all. Instead, each peasant could just track internally when they were last
activated. The Monarch wouldn't track any state itself. It would be distributed
across all the peasants. The Monarch could then iterate over the list of
peasants and find the one with the newest `LastActivated` timestamp.
Now, when a Monarch dies, the new Peasant doesn't have to come up with the stack
itself. All the other Peasants keep their state. The new Monarch can query them
and get the same answer the old Monarch would have.
We could further optimize this by having the Monarch also track the stack. Then,
the monarch could query the MRU window quickly. The `LastActivated` timestamps
would only be used by a new Monarch when it is elected, to reconstruct the MRU
stack.
## Implementation Plan
This is a list of actionable tasks generated as described by this spec:
* [ ] Add support for `wt.exe` processes to be Monarchs and Peasants, and
communicate that state between themselves. This task does not otherwise add
any user-facing features, merely an architectural update.
* [ ] Add support for the `windowingBehavior` setting as a boolean. Opening new
WT windows will conditionally glom to existing windows.
* [ ] Add support for per-desktop `windowingBehavior`, by adding the support for
the enum values `"useExisting"`, `"useExistingOnSameDesktop"` and `"useNew"`.
* [ ] Add support for `wt.exe` to pass commandlines intended for another window
to the monarch, then to the intended window, with the `--window,-w
window-id` commandline parameter.
* [ ] Add support for targeting and naming windows via the `-w` parameter on the
commandline
* [ ] Add a `NameWindow` action, subcommand that allows the user to set the name
for the window.
* [ ] Add an action that will cause all windows to briefly display a overlay
with the current window ID and name. This would be something like the
"identify" feature of the Windows "Display" settings.
## Future considerations
* What if the user wanted to pipe a command to a pane in an existing window?
```sh
man ping > wt -w 0 split-pane cat
```
Is there some way for WT to pass its stdin/out handles to the child process
it's creating? This is _not_ related to the current spec at hand, just
something the author considered while writing the spec. This likely belongs
over in [#492], or in its own spec.
- Or I suppose, with less confusion, someone could run `wt -w 0 split-pane --
man ping > cat`. That's certainly more sensible, and wouldn't require any
extra work.
* "Single Instance Mode" is a scenario in which there is only ever one single WT
window. A user might want this functionality to only ever allow a single
terminal window to be open on their desktop. This is especially frequently
requested in combination with "quake mode", as discussed in [#653]. When Single
Instance Mode is active, and the user runs a new `wt.exe` commandline, it will
always end up running in the existing window, if there is one.
An earlier version of this spec proposed a new value of `glomToLastWindow`.
(`glomToLastWindow` was later renamed `windowingBehavior`). The `always` value
would disable tab tear out<sup>[[1]](#footnote-1)</sup>. It would additionally
disable the `newWindow` action, and prevent `wt -w new` from opening a new
window.
In discussion, it was concluded that this setting didn't make sense. Why did the
`glomToLastWindow` setting change the behavior of tear out? Single Instance Mode
is most frequently requested in regards to quake mode. We're leaving the
implementation of true single instance mode to that spec.
* It was suggested in review that we could auto-generate names for windows, from
some list of words. Prior art could be the URLS for gfycat.com or
what3words.com, which use three random words. I believe `docker` also assigns
names from a random selection of `adjective`+`name`. This is an interesting
idea, and something that could be pursued in the future.
- This would be a massive pain to localize though, hence why this is left as
a future consideration.
* We will _need_ to provide a commandline tool to list windows and their IDs &
names. We're thinking a list of windows, their IDs, names, PIDs, and the title
of the window.
Currently we're stuck with `wt.exe` which is a GUI application, and cannot
print to the console. Our need is now fairly high for the ability to print
info to the console. To remedy this, we'll need to ship another helper exe as
a commandline tool for working with the terminal. The design for this is left
for the future.
## Footnotes
<a name="footnote-1"><a>[1]: While tear-out is a separate track of work from
session management in general, this setting could be implemented along with this
set of features, and later used to control tear out as well.
<a name="footnote-2"><a>[2]: Since we're reserving the keyword `new` to mean "a
new window", then we could also reserve `last` or `current` as an alias for "the
current window".
<a name="footnote-3"><a>[3]: We currently have two actions for renaming _tabs_
in the Terminal: `renameTab(name)`, and `openTabRenamer()`. We will likely
similarly need `nameWindow(name)` and `openWindowNamer()`. `openWindowNamer`
could display a dialog to allow the user to rename the current window at
runtime.
## Resources
* [Tab Tear-out in the community toolkit] - this document proved invaluable to
the background of tearing a tab out of an application to create a new window.
<!-- Footnotes -->
[#5000]: https://github.com/microsoft/terminal/issues/5000
[#1256]: https://github.com/microsoft/terminal/issues/1256
[#4472]: https://github.com/microsoft/terminal/issues/4472
[#2227]: https://github.com/microsoft/terminal/issues/2227
[#653]: https://github.com/microsoft/terminal/issues/653
[#1032]: https://github.com/microsoft/terminal/issues/1032
[#632]: https://github.com/microsoft/terminal/issues/632
[#492]: https://github.com/microsoft/terminal/issues/492
[#4000]: https://github.com/microsoft/terminal/issues/4000
[#7972]: https://github.com/microsoft/terminal/pull/7972
[#961]: https://github.com/microsoft/terminal/issues/961
[`30b8335`]: https://github.com/microsoft/terminal/commit/30b833547928d6dcbf88d49df0dbd5b3f6a7c879
[Tab Tear-out in the community toolkit]: https://github.com/windows-toolkit/Sample-TabView-TearOff
[Quake mode scenarios]: https://github.com/microsoft/terminal/issues/653#issuecomment-661370107
[`ISwapChainPanelNative2::SetSwapChainHandle`]: https://docs.microsoft.com/en-us/windows/win32/api/windows.ui.xaml.media.dxinterop/nf-windows-ui-xaml-media-dxinterop-iswapchainpanelnative2-setswapchainhandle
[Process Model 2.0 Spec]: https://github.com/microsoft/terminal/blob/main/doc/specs/%235000%20-%20Process%20Model%202.0/%235000%20-%20Process%20Model%202.0.md

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -1,95 +0,0 @@
---
author: Kayla Cinnamon @cinnamon-msft
created on: 2020-01-03
last updated: 2020-01-03
issue id: 597
---
# Tab Sizing
## Abstract
This spec outlines the tab sizing feature. This is an application-level feature that is not profile-specific (at least for now).
Global properties that encompass tab sizing:
* `tabWidthMode` (accepts pre-defined values for tab sizing behavior)
* `tabWidthMin` (can never be smaller than the icon width)
* `tabWidthMax` (can never be wider than the tab bar)
Acceptable values for `tabWidthMode`:
* [default] `equal` (all tabs are sized the same, regardless of tab title length)
* `titleLength` (width of tab contains entire tab title)
## Inspiration
Other browsers and terminals have varying tab width behavior, so we should give people options.
## Solution Design
`tabWidthMode` will be a global setting that will accept the following strings:
* `equal`
* All tabs are equal in width
* If the tab bar has filled, tabs will shrink as additional tabs are added
* Utilizes the `equal` setting from WinUI's TabView
* `titleLength`
* Tab width varies depending on title length
* Width of tab will fit the whole tab title
* Utilizes the `sizeToContent` setting from WinUI's TabView
In addition to `tabWidthMode`, the following global properties will also be available:
* `tabWidthMin`
* Accepts an integer
* Value correlates to the minimum amount of pixels the tab width can be
* If value is less than the width of the icon, the minimum width will be the width of the icon
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
* If not set, the tab will have the system-defined minimum width
* `tabWidthMax`
* Accepts an integer
* Value correlates to the maximum amount of pixels the tab width can be
* If value is less than the width of the icon, the minimum width will be the width of the icon
* If value is greater than the width of the tab bar, the maximum width will be the width of the tab bar
* If not set, the tab will have the system-defined maximum width
If `tabWidthMode` is set to `titleLength`, the tab widths will fall between the `tabWidthMin` and `tabWidthMax` values if they are set, depending on the length of the tab title.
If `tabWidthMode` isn't set, the default experience will be `equal`. Justification for the default experience is the results from this [twitter poll](https://twitter.com/cinnamon_msft/status/1203093459055210496).
## UI/UX Design
[This tweet](https://twitter.com/cinnamon_msft/status/1203094776117022720) displays how the `equal` and `titleLength` values behave for the `tabWidthMode` property.
## Capabilities
### Accessibility
This feature could impact accessibility if the tab title isn't stored within the metadata of the tab. If the tab width is the width of the icon, then the title isn't visible. The tab title will have to be accessible by a screen reader.
### Security
This feature will not impact security.
### Reliability
This feature will not impact reliability. It provides users with additional customization options.
### Compatibility
This feature will not break existing compatibility.
### Performance, Power, and Efficiency
## Potential Issues
This feature will not impact performance, power, nor efficiency.
## Future considerations
* Provide tab sizing options per-profile
* A `tabWidthMode` value that will evenly divide the entirety of the tab bar by the number of open tabs
* i.e. One tab will take the full width of the tab bar, two tabs will each take up half the width of the tab bar, etc.

View File

@@ -4,7 +4,7 @@
"type": "git",
"git": {
"repositoryUrl": "https://github.com/fmtlib/fmt",
"commitHash": "7bdf0628b1276379886c7f6dda2cef2b3b374f0b"
"commitHash": "f19b1a521ee8b606dedcadfda69fd10ddf882753"
}
}
}

View File

@@ -72,27 +72,43 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
static_assert(F::is_integer, "From must be integral");
static_assert(T::is_integer, "To must be integral");
if (detail::const_check(F::is_signed && !T::is_signed)) {
if (F::is_signed && !T::is_signed) {
// From may be negative, not allowed!
if (fmt::detail::is_negative(from)) {
ec = 1;
return {};
}
// From is positive. Can it always fit in To?
if (F::digits > T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
if (F::digits <= T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
ec = 1;
return {};
}
}
}
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
if (!F::is_signed && T::is_signed) {
// can from be held in To?
if (F::digits < T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
// outside range.
ec = 1;
return {};
}
}
}
return static_cast<To>(from); // Lossless conversion.
}
// reaching here means all is ok for lossless conversion.
return static_cast<To>(from);
} // function
template <typename To, typename From,
FMT_ENABLE_IF(std::is_same<From, To>::value)>
@@ -174,9 +190,11 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// safe conversion to IntermediateRep
IntermediateRep count =
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
if (ec) return {};
if (ec) {
return {};
}
// multiply with Factor::num without overflow or underflow
if (detail::const_check(Factor::num != 1)) {
if (Factor::num != 1) {
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (count > max1) {
ec = 1;
@@ -191,9 +209,17 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
count *= Factor::num;
}
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
return ec ? To() : To(tocount);
// this can't go wrong, right? den>0 is checked earlier.
if (Factor::den != 1) {
count /= Factor::den;
}
// convert to the to type, safely
using ToRep = typename To::rep;
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{tocount};
}
/**
@@ -325,11 +351,6 @@ inline std::tm localtime(std::time_t time) {
return lt.tm_;
}
inline std::tm localtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return localtime(std::chrono::system_clock::to_time_t(time_point));
}
// Thread-safe replacement for std::gmtime
inline std::tm gmtime(std::time_t time) {
struct dispatcher {
@@ -366,11 +387,6 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_;
}
inline std::tm gmtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return gmtime(std::chrono::system_clock::to_time_t(time_point));
}
namespace detail {
inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) {
@@ -383,17 +399,6 @@ inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
}
} // namespace detail
template <typename Char>
struct formatter<std::chrono::time_point<std::chrono::system_clock>, Char>
: formatter<std::tm, Char> {
template <typename FormatContext>
auto format(std::chrono::time_point<std::chrono::system_clock> val,
FormatContext& ctx) -> decltype(ctx.out()) {
std::tm time = localtime(val);
return formatter<std::tm, Char>::format(time, ctx);
}
};
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {

View File

@@ -463,16 +463,16 @@ template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
}
template <typename Char>
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}
template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts,
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_format_args<buffer_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
@@ -496,7 +496,7 @@ void vformat_to(buffer<Char>& buf, const text_style& ts,
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
@@ -504,22 +504,20 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
}
/**
\rst
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
**Example**::
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
vprint(f, ts, format_str,
fmt::make_args_checked<Args...>(format_str, args...));
detail::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
}
/**
@@ -560,42 +558,7 @@ template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, ts, format_str, args);
return detail::get_iterator(buf);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
detail::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -368,8 +368,7 @@ template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
constexpr const auto& get(const T& first, const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@@ -407,19 +406,6 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}};
}
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
using char_type = Char;
@@ -444,9 +430,7 @@ template <typename Char, typename T, int N> struct spec_field {
OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
const auto& vargs =
make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
basic_format_context<OutputIt, Char> ctx(out, {});
return fmt.format(arg, ctx);
}
};
@@ -505,17 +489,16 @@ constexpr auto parse_tail(T head, S format_str) {
template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt;
size_t end;
int next_arg_id;
};
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int arg_id) {
size_t pos) {
str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str, {}, arg_id + 1);
auto ctx = basic_format_parse_context<Char>(str);
auto f = formatter<T, Char>();
auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1, ctx.next_arg_id()};
return {f, pos + (end - str.data()) + 1};
}
// Compiles a non-empty format string and returns the compiled representation
@@ -535,8 +518,8 @@ constexpr auto compile_format_string(S format_str) {
format_str);
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2, ID);
return parse_tail<Args, result.end, result.next_arg_id>(
constexpr auto result = parse_specs<type>(str, POS + 2);
return parse_tail<Args, result.end, ID + 1>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
} else {
return unknown_format();
@@ -547,13 +530,8 @@ constexpr auto compile_format_string(S format_str) {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str);
}
}
@@ -609,7 +587,8 @@ template <typename CompiledFormat, typename... Args,
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
basic_memory_buffer<Char> buffer;
cf.format(detail::buffer_appender<Char>(buffer), args...);
detail::buffer<Char>& base = buffer;
cf.format(std::back_inserter(base), args...);
return to_string(buffer);
}
@@ -629,7 +608,8 @@ template <typename CompiledFormat, typename... Args,
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using context = buffer_context<Char>;
detail::cf::vformat_to<context>(detail::buffer_appender<Char>(buffer), cf,
detail::buffer<Char>& base = buffer;
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
@@ -638,13 +618,9 @@ template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
#ifdef __cpp_if_constexpr
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
}
#endif
constexpr basic_string_view<typename S::char_type> str = S();
if (str.size() == 2 && str[0] == '{' && str[1] == '}')
return fmt::to_string(detail::first(args...));
constexpr auto compiled = detail::compile<Args...>(S());
return format(compiled, std::forward<Args>(args)...);
}
@@ -667,30 +643,18 @@ OutputIt format_to(OutputIt out, const S&, const Args&... args) {
return format_to(out, compiled, args...);
}
template <typename OutputIt, typename CompiledFormat, typename... Args>
auto format_to_n(OutputIt out, size_t n, const CompiledFormat& cf,
const Args&... args) ->
typename std::enable_if<
detail::is_output_iterator<OutputIt,
typename CompiledFormat::char_type>::value &&
std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value,
format_to_n_result<OutputIt>>::type {
template <
typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
detail::basic_compiled_format, CompiledFormat>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf,
const Args&... args) {
auto it =
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()};
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, const S&,
const Args&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), compiled,
args...);
return {it.base(), it.count()};
}
template <typename CompiledFormat, typename... Args>
size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return format_to(detail::counting_iterator(), cf, args...).count();

View File

@@ -18,7 +18,7 @@
#include <vector>
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 70103
#define FMT_VERSION 70001
#ifdef __clang__
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
@@ -57,7 +57,6 @@
# define FMT_MSC_VER 0
# define FMT_SUPPRESS_MSC_WARNING(n)
#endif
#ifdef __has_feature
# define FMT_HAS_FEATURE(x) __has_feature(x)
#else
@@ -65,7 +64,7 @@
#endif
#if defined(__has_include) && !defined(__INTELLISENSE__) && \
(!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600)
!(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600)
# define FMT_HAS_INCLUDE(x) __has_include(x)
#else
# define FMT_HAS_INCLUDE(x) 0
@@ -100,7 +99,7 @@
#endif
#ifndef FMT_OVERRIDE
# if FMT_HAS_FEATURE(cxx_override_control) || \
# if FMT_HAS_FEATURE(cxx_override) || \
(FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
# define FMT_OVERRIDE override
# else
@@ -153,7 +152,7 @@
# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
# define FMT_DEPRECATED [[deprecated]]
# else
# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
# if defined(__GNUC__) || defined(__clang__)
# define FMT_DEPRECATED __attribute__((deprecated))
# elif FMT_MSC_VER
# define FMT_DEPRECATED __declspec(deprecated)
@@ -178,17 +177,9 @@
# endif
#endif
#ifndef FMT_USE_INLINE_NAMESPACES
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
(FMT_MSC_VER >= 1900 && !_MANAGED)
# define FMT_USE_INLINE_NAMESPACES 1
# else
# define FMT_USE_INLINE_NAMESPACES 0
# endif
#endif
#ifndef FMT_BEGIN_NAMESPACE
# if FMT_USE_INLINE_NAMESPACES
# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
FMT_MSC_VER >= 1900
# define FMT_INLINE_NAMESPACE inline namespace
# define FMT_END_NAMESPACE \
} \
@@ -278,7 +269,8 @@ struct monostate {};
namespace detail {
// A helper function to suppress "conditional expression is constant" warnings.
// A helper function to suppress bogus "conditional expression is constant"
// warnings.
template <typename T> constexpr T const_check(T value) { return value; }
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
@@ -307,8 +299,7 @@ template <typename T> struct std_string_view {};
#ifdef FMT_USE_INT128
// Do nothing.
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \
!(FMT_CLANG_VERSION && FMT_MSC_VER)
#elif defined(__SIZEOF_INT128__) && !FMT_NVCC
# define FMT_USE_INT128 1
using int128_t = __int128_t;
using uint128_t = __uint128_t;
@@ -515,18 +506,6 @@ template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> {
using type = typename result::value_type;
};
// Reports a compile-time error if S is not a valid format string.
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
FMT_INLINE void check_format_string(const S&) {
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(is_compile_string<S>::value,
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
"FMT_STRING.");
#endif
}
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
struct error_handler {
constexpr error_handler() = default;
constexpr error_handler(const error_handler&) = default;
@@ -566,9 +545,8 @@ class basic_format_parse_context : private ErrorHandler {
using iterator = typename basic_string_view<Char>::iterator;
explicit constexpr basic_format_parse_context(
basic_string_view<Char> format_str, ErrorHandler eh = {},
int next_arg_id = 0)
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {}
basic_string_view<Char> format_str, ErrorHandler eh = {})
: ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {}
/**
Returns an iterator to the beginning of the format string range being
@@ -638,24 +616,8 @@ template <typename T, typename Context>
using has_formatter =
std::is_constructible<typename Context::template formatter_type<T>>;
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
namespace detail {
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
}
/**
\rst
A contiguous memory buffer with an optional growing ability. It is an internal
@@ -678,8 +640,6 @@ template <typename T> class buffer {
size_(sz),
capacity_(cap) {}
~buffer() = default;
/** Sets the buffer data and capacity. */
void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
ptr_ = buf_data;
@@ -695,6 +655,7 @@ template <typename T> class buffer {
buffer(const buffer&) = delete;
void operator=(const buffer&) = delete;
virtual ~buffer() = default;
T* begin() FMT_NOEXCEPT { return ptr_; }
T* end() FMT_NOEXCEPT { return ptr_ + size_; }
@@ -714,26 +675,24 @@ template <typename T> class buffer {
/** Returns a pointer to the buffer data. */
const T* data() const FMT_NOEXCEPT { return ptr_; }
/**
Resizes the buffer. If T is a POD type new elements may not be initialized.
*/
void resize(size_t new_size) {
reserve(new_size);
size_ = new_size;
}
/** Clears this buffer. */
void clear() { size_ = 0; }
// Tries resizing the buffer to contain *count* elements. If T is a POD type
// the new elements may not be initialized.
void try_resize(size_t count) {
try_reserve(count);
size_ = count <= capacity_ ? count : capacity_;
}
// Tries increasing the buffer capacity to *new_capacity*. It can increase the
// capacity by a smaller amount than requested but guarantees there is space
// for at least one additional element either by increasing the capacity or by
// flushing the buffer if it is full.
void try_reserve(size_t new_capacity) {
/** Reserves space to store at least *capacity* elements. */
void reserve(size_t new_capacity) {
if (new_capacity > capacity_) grow(new_capacity);
}
void push_back(const T& value) {
try_reserve(size_ + 1);
reserve(size_ + 1);
ptr_[size_++] = value;
}
@@ -746,150 +705,32 @@ template <typename T> class buffer {
}
};
struct buffer_traits {
explicit buffer_traits(size_t) {}
size_t count() const { return 0; }
size_t limit(size_t size) { return size; }
};
class fixed_buffer_traits {
private:
size_t count_ = 0;
size_t limit_;
public:
explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
size_t count() const { return count_; }
size_t limit(size_t size) {
size_t n = limit_ > count_ ? limit_ - count_ : 0;
count_ += size;
return size < n ? size : n;
}
};
// A buffer that writes to an output iterator when flushed.
template <typename OutputIt, typename T, typename Traits = buffer_traits>
class iterator_buffer final : public Traits, public buffer<T> {
private:
OutputIt out_;
enum { buffer_size = 256 };
T data_[buffer_size];
protected:
void grow(size_t) final FMT_OVERRIDE {
if (this->size() == buffer_size) flush();
}
void flush();
public:
explicit iterator_buffer(OutputIt out, size_t n = buffer_size)
: Traits(n),
buffer<T>(data_, 0, buffer_size),
out_(out) {}
~iterator_buffer() { flush(); }
OutputIt out() {
flush();
return out_;
}
size_t count() const { return Traits::count() + this->size(); }
};
template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
protected:
void grow(size_t) final FMT_OVERRIDE {}
public:
explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
T* out() { return &*this->end(); }
};
// A buffer that writes to a container with the contiguous storage.
// A container-backed buffer.
template <typename Container>
class iterator_buffer<std::back_insert_iterator<Container>,
enable_if_t<is_contiguous<Container>::value,
typename Container::value_type>>
final : public buffer<typename Container::value_type> {
class container_buffer : public buffer<typename Container::value_type> {
private:
Container& container_;
protected:
void grow(size_t capacity) final FMT_OVERRIDE {
void grow(size_t capacity) FMT_OVERRIDE {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
public:
explicit iterator_buffer(Container& c)
explicit container_buffer(Container& c)
: buffer<typename Container::value_type>(c.size()), container_(c) {}
explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0)
: iterator_buffer(get_container(out)) {}
std::back_insert_iterator<Container> out() {
return std::back_inserter(container_);
}
};
// A buffer that counts the number of code units written discarding the output.
template <typename T = char> class counting_buffer final : public buffer<T> {
private:
enum { buffer_size = 256 };
T data_[buffer_size];
size_t count_ = 0;
protected:
void grow(size_t) final FMT_OVERRIDE {
if (this->size() != buffer_size) return;
count_ += this->size();
this->clear();
}
public:
counting_buffer() : buffer<T>(data_, 0, buffer_size) {}
size_t count() { return count_ + this->size(); }
};
// An output iterator that appends to the buffer.
// It is used to reduce symbol sizes for the common case.
template <typename T>
class buffer_appender : public std::back_insert_iterator<buffer<T>> {
using base = std::back_insert_iterator<buffer<T>>;
public:
explicit buffer_appender(buffer<T>& buf) : base(buf) {}
buffer_appender(base it) : base(it) {}
buffer_appender& operator++() {
base::operator++();
return *this;
}
buffer_appender operator++(int) {
buffer_appender tmp = *this;
++*this;
return tmp;
}
};
// Maps an output iterator into a buffer.
template <typename T, typename OutputIt>
iterator_buffer<OutputIt, T> get_buffer(OutputIt);
template <typename T> buffer<T>& get_buffer(buffer_appender<T>);
template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) {
return out;
}
template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) {
return get_container(out);
}
template <typename Buffer>
auto get_iterator(Buffer& buf) -> decltype(buf.out()) {
return buf.out();
}
template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) {
return buffer_appender<T>(buf);
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline Container& get_container(std::back_insert_iterator<Container> it) {
using bi_iterator = std::back_insert_iterator<Container>;
struct accessor : bi_iterator {
accessor(bi_iterator iter) : bi_iterator(iter) {}
using bi_iterator::container;
};
return *accessor(it).container;
}
template <typename T, typename Char = char, typename Enable = void>
@@ -918,8 +759,7 @@ template <typename Char> struct named_arg_info {
template <typename T, typename Char, size_t NUM_ARGS, size_t NUM_NAMED_ARGS>
struct arg_data {
// args_[0].named_args points to named_args_ to avoid bloating format_args.
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)];
named_arg_info<Char> named_args_[NUM_NAMED_ARGS];
template <typename... U>
@@ -931,8 +771,7 @@ struct arg_data {
template <typename T, typename Char, size_t NUM_ARGS>
struct arg_data<T, Char, NUM_ARGS, 0> {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];
T args_[NUM_ARGS != 0 ? NUM_ARGS : 1];
template <typename... U>
FMT_INLINE arg_data(const U&... init) : args_{init...} {}
@@ -1120,8 +959,6 @@ enum { long_short = sizeof(long) == sizeof(int) };
using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
struct unformattable {};
// Maps formatting arguments to core types.
template <typename Context> struct arg_mapper {
using char_type = typename Context::char_type;
@@ -1230,7 +1067,15 @@ template <typename Context> struct arg_mapper {
return map(val.value);
}
unformattable map(...) { return {}; }
int map(...) {
constexpr bool formattable = sizeof(Context) == 0;
static_assert(
formattable,
"Cannot format argument. To make type T formattable provide a "
"formatter<T> specialization: "
"https://fmt.dev/latest/api.html#formatting-user-defined-types");
return 0;
}
};
// A type constant after applying arg_mapper<Context>.
@@ -1354,25 +1199,15 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg(
return vis(monostate());
}
template <typename T> struct formattable : std::false_type {};
// Checks whether T is a container with contiguous storage.
template <typename T> struct is_contiguous : std::false_type {};
template <typename Char>
struct is_contiguous<std::basic_string<Char>> : std::true_type {};
template <typename Char>
struct is_contiguous<detail::buffer<Char>> : std::true_type {};
namespace detail {
// A workaround for gcc 4.8 to make void_t work in a SFINAE context.
template <typename... Ts> struct void_t_impl { using type = void; };
template <typename... Ts>
using void_t = typename detail::void_t_impl<Ts...>::type;
template <typename It, typename T, typename Enable = void>
struct is_output_iterator : std::false_type {};
template <typename It, typename T>
struct is_output_iterator<
It, T,
void_t<typename std::iterator_traits<It>::iterator_category,
decltype(*std::declval<It>() = std::declval<T>())>>
: std::true_type {};
template <typename OutputIt>
struct is_back_insert_iterator : std::false_type {};
template <typename Container>
@@ -1384,9 +1219,6 @@ struct is_contiguous_back_insert_iterator : std::false_type {};
template <typename Container>
struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>>
: is_contiguous<Container> {};
template <typename Char>
struct is_contiguous_back_insert_iterator<buffer_appender<Char>>
: std::true_type {};
// A type-erased reference to an std::locale to avoid heavy <locale> include.
class locale_ref {
@@ -1418,24 +1250,13 @@ FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) {
return arg;
}
template <typename T> int check(unformattable) {
static_assert(
formattable<T>(),
"Cannot format an argument. To make type T formattable provide a "
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
return 0;
}
template <typename T, typename U> inline const U& check(const U& val) {
return val;
}
// The type template parameter is there to avoid an ODR violation when using
// a fallback formatter in one translation unit and an implicit conversion in
// another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
inline value<Context> make_arg(const T& val) {
return check<T>(arg_mapper<Context>().map(val));
return arg_mapper<Context>().map(val);
}
template <bool IS_PACKED, typename Context, type, typename T,
@@ -1535,13 +1356,13 @@ template <typename OutputIt, typename Char> class basic_format_context {
template <typename Char>
using buffer_context =
basic_format_context<detail::buffer_appender<Char>, Char>;
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
using format_context = buffer_context<char>;
using wformat_context = buffer_context<wchar_t>;
// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164.
// Workaround a bug in gcc: https://stackoverflow.com/q/62767544/471164.
#define FMT_BUFFER_CONTEXT(Char) \
basic_format_context<detail::buffer_appender<Char>, Char>
basic_format_context<std::back_insert_iterator<detail::buffer<Char>>, Char>
/**
\rst
@@ -1593,7 +1414,7 @@ class format_arg_store
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references to
Constructs an `~fmt::format_arg_store` object that contains references to
arguments and can be implicitly converted to `~fmt::format_args`. `Context`
can be omitted in which case it defaults to `~fmt::context`.
See `~fmt::arg` for lifetime considerations.
@@ -1605,27 +1426,6 @@ inline format_arg_store<Context, Args...> make_format_args(
return {args...};
}
/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``format_str`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
inline auto make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(format_str);
return {args...};
}
/**
\rst
Returns a named argument to be used in a formatting function. It should only
@@ -1913,7 +1713,7 @@ template <typename Context> class basic_format_args {
}
template <typename Char> int get_id(basic_string_view<Char> name) const {
if (!has_named_args()) return -1;
if (!has_named_args()) return {};
const auto& named_args =
(is_packed() ? values_[-1] : args_[-1].value_).named_args;
for (size_t i = 0; i < named_args.size; ++i) {
@@ -1929,14 +1729,7 @@ template <typename Context> class basic_format_args {
}
};
#ifdef FMT_ARM_ABI_COMPATIBILITY
/** An alias to ``basic_format_args<format_context>``. */
// Separate types would result in shorter symbols but break ABI compatibility
// between clang and gcc on ARM (#1919).
using format_args = basic_format_args<format_context>;
using wformat_args = basic_format_args<wformat_context>;
#else
// DEPRECATED! These are kept for ABI compatibility.
/** An alias to ``basic_format_args<context>``. */
// It is a separate type rather than an alias to make symbols readable.
struct format_args : basic_format_args<format_context> {
template <typename... Args>
@@ -1945,10 +1738,32 @@ struct format_args : basic_format_args<format_context> {
struct wformat_args : basic_format_args<wformat_context> {
using basic_format_args::basic_format_args;
};
#endif
namespace detail {
// Reports a compile-time error if S is not a valid format string.
template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)>
FMT_INLINE void check_format_string(const S&) {
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(is_compile_string<S>::value,
"FMT_ENFORCE_COMPILE_STRING requires all format strings to use "
"FMT_STRING.");
#endif
}
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
template <typename... Args, typename S, typename Char = char_t<S>>
inline format_arg_store<buffer_context<Char>, remove_reference_t<Args>...>
make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args) {
static_assert(count<(std::is_base_of<view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
check_format_string<Args...>(format_str);
return {args...};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
std::basic_string<Char> vformat(
basic_string_view<Char> format_str,
@@ -1957,10 +1772,9 @@ std::basic_string<Char> vformat(
FMT_API std::string vformat(string_view format_str, format_args args);
template <typename Char>
void vformat_to(
typename FMT_BUFFER_CONTEXT(Char)::iterator vformat_to(
buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
detail::locale_ref loc = {});
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args);
template <typename Char, typename Args,
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
@@ -1975,80 +1789,26 @@ inline void vprint_mojibake(std::FILE*, string_view, format_args) {}
/** Formats a string and writes the output to ``out``. */
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
template <typename OutputIt, typename S, typename Char = char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> typename std::enable_if<enable, OutputIt>::type {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, to_string_view(format_str), args);
return detail::get_iterator(buf);
}
/**
\rst
Formats arguments, writes the result to the output iterator ``out`` and returns
the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out), "{}", 42);
\endrst
*/
// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3.
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to(OutputIt out, const S& format_str, Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, to_string_view(format_str), vargs);
}
template <typename OutputIt> struct format_to_n_result {
/** Iterator past the end of the output range. */
OutputIt out;
/** Total (not truncated) output size. */
size_t size;
};
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
inline format_to_n_result<OutputIt> vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> format_str,
template <
typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator<OutputIt>::value)>
OutputIt vformat_to(
OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
n);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
auto& c = detail::get_container(out);
detail::container_buffer<remove_reference_t<decltype(c)>> buf(c);
detail::vformat_to(buf, to_string_view(format_str), args);
return out;
}
/**
\rst
Formats arguments, writes up to ``n`` characters of the result to the output
iterator ``out`` and returns the total output size and the iterator past the
end of the output range.
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to_n(OutputIt out, size_t n, const S& format_str,
const Args&... args) ->
typename std::enable_if<enable, format_to_n_result<OutputIt>>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to_n(out, n, to_string_view(format_str), vargs);
}
/**
Returns the number of characters in the output of
``format(format_str, args...)``.
*/
template <typename... Args>
inline size_t formatted_size(string_view format_str, Args&&... args) {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::counting_buffer<> buf;
detail::vformat_to(buf, format_str, vargs);
return buf.count();
template <typename Container, typename S, typename... Args,
FMT_ENABLE_IF(
is_contiguous<Container>::value&& detail::is_string<S>::value)>
inline std::back_insert_iterator<Container> format_to(
std::back_insert_iterator<Container> out, const S& format_str,
Args&&... args) {
return vformat_to(out, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename Char = char_t<S>>
@@ -2072,7 +1832,7 @@ FMT_INLINE std::basic_string<Char> vformat(
// std::basic_string<char_t<S>> to reduce the symbol size.
template <typename S, typename... Args, typename Char = char_t<S>>
FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
return detail::vformat(to_string_view(format_str), vargs);
}
@@ -2092,7 +1852,7 @@ FMT_API void vprint(std::FILE*, string_view, format_args);
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline void print(std::FILE* f, const S& format_str, Args&&... args) {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
return detail::is_unicode<Char>()
? vprint(f, to_string_view(format_str), vargs)
: detail::vprint_mojibake(f, to_string_view(format_str), vargs);
@@ -2111,7 +1871,7 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) {
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline void print(const S& format_str, Args&&... args) {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
const auto& vargs = detail::make_args_checked<Args...>(format_str, args...);
return detail::is_unicode<Char>()
? vprint(to_string_view(format_str), vargs)
: detail::vprint_mojibake(stdout, to_string_view(format_str),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,12 +15,22 @@
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
args, detail::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(buffer, format_str, args, detail::locale_ref(loc));
detail::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace detail
@@ -35,28 +45,32 @@ inline std::basic_string<Char> vformat(
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return detail::vformat(loc, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
return detail::vformat(
loc, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
typename Char = enable_if_t<
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
format_args_t<type_identity_t<OutputIt>, Char> args) {
using af = detail::arg_formatter<OutputIt, Char>;
return vformat_to<af>(out, to_string_view(format_str), args,
detail::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value>
inline auto format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
detail::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE

View File

@@ -29,8 +29,7 @@
#if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h>
#endif
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
#if FMT_HAS_INCLUDE("fcntl.h") && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
@@ -279,8 +278,7 @@ class file {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND) // Open in append mode.
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist.
};
// Constructs a file object which doesn't represent any file.
@@ -345,69 +343,36 @@ class file {
// Returns the memory page size.
long getpagesize();
namespace detail {
class direct_buffered_file;
struct buffer_size {
size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
template <typename S, typename... Args>
void print(direct_buffered_file& f, const S& format_str,
const Args&... args);
struct ostream_params {
int oflag = file::WRONLY | file::CREATE;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int oflag) : ostream_params(params...) {
this->oflag = oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
};
} // namespace detail
static constexpr detail::buffer_size buffer_size;
// A fast output stream which is not thread-safe.
class ostream final : private detail::buffer<char> {
// A buffered file with a direct buffer access and no synchronization.
class direct_buffered_file {
private:
file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
if (pos_ == 0) return;
file_.write(buffer_, pos_);
pos_ = 0;
}
FMT_API void grow(size_t) override final;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
int free_capacity() const { return buffer_size - pos_; }
public:
ostream(ostream&& other)
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
other.set(nullptr, 0);
}
~ostream() {
flush();
delete[] data();
}
direct_buffered_file(cstring_view path, int oflag)
: file_(path, oflag), pos_(0) {}
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
~direct_buffered_file() {
flush();
}
void close() {
flush();
@@ -415,20 +380,25 @@ class ostream final : private detail::buffer<char> {
}
template <typename S, typename... Args>
void print(const S& format_str, const Args&... args) {
format_to(detail::buffer_appender<char>(*this), format_str, args...);
friend void print(direct_buffered_file& f, const S& format_str,
const Args&... args) {
// We could avoid double buffering.
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, args...);
auto remaining_pos = 0;
auto remaining_size = buf.size();
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
auto size = f.free_capacity();
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
f.pos_ += size;
f.flush();
remaining_pos += size;
remaining_size -= size;
}
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
f.pos_ += static_cast<int>(remaining_size);
}
};
/**
Opens a file for writing. Supported parameters passed in `params`:
* ``<integer>``: Output flags (``file::WRONLY | file::CREATE`` by default)
* ``buffer_size=<integer>``: Output buffer size
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE

View File

@@ -49,26 +49,16 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
}
};
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
void_t<> operator<<(converter);
};
// Hide all operator<< from std::basic_ostream<Char>.
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
};
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
@@ -113,7 +103,7 @@ void format_value(buffer<Char>& buf, const T& value,
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.try_resize(buf.size());
buf.resize(buf.size());
}
// Formats an object of type T that has an overloaded ostream operator<<.
@@ -170,7 +160,7 @@ template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
detail::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE

View File

@@ -181,7 +181,7 @@ template <typename Char> class printf_width_handler {
template <typename Char, typename Context>
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(buffer_appender<Char>(buf), format, args).format();
Context(std::back_inserter(buf), format, args).format();
}
} // namespace detail
@@ -598,7 +598,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
template <typename Char>
using basic_printf_context_t =
basic_printf_context<detail::buffer_appender<Char>, Char>;
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>;
using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>;

View File

@@ -157,9 +157,6 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
}
template <typename Range>
using value_type = remove_cvref_t<decltype(*std::declval<Range>().begin())>;
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
typename std::decay<Arg>::type>::value)>
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
@@ -185,6 +182,7 @@ FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
return add_space ? L" '{}'" : L"'{}'";
}
} // namespace detail
template <typename T> struct is_tuple_like {
@@ -248,18 +246,9 @@ template <typename T, typename Char> struct is_range {
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
&&
(has_formatter<detail::value_type<T>, format_context>::value ||
detail::has_fallback_formatter<detail::value_type<T>,
format_context>::value)
#endif
>> {
template <typename RangeT, typename Char>
struct formatter<RangeT, Char,
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
formatting_range<Char> formatting;
template <typename ParseContext>
@@ -268,7 +257,8 @@ struct formatter<
}
template <typename FormatContext>
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
typename FormatContext::iterator format(const RangeT& values,
FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0;
auto it = values.begin();

View File

@@ -23,36 +23,6 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
// DEPRECATED! This function exists for ABI compatibility.
template <typename Char>
typename basic_format_context<std::back_insert_iterator<buffer<Char>>,
Char>::iterator
vformat_to(buffer<Char>& buf, basic_string_view<Char> format_str,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>>
args) {
using iterator = std::back_insert_iterator<buffer<char>>;
using context = basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<Char>>>,
type_identity_t<Char>>;
auto out = iterator(buf);
format_handler<iterator, Char, context> h(out, format_str, args, {});
parse_format_string<false>(format_str, h);
return out;
}
template basic_format_context<std::back_insert_iterator<buffer<char>>,
char>::iterator
vformat_to(buffer<char>&, string_view,
basic_format_args<basic_format_context<
std::back_insert_iterator<buffer<type_identity_t<char>>>,
type_identity_t<char>>>);
} // namespace detail
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;
@@ -74,9 +44,9 @@ template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*);
template FMT_API void detail::vformat_to(
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to(
detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
basic_format_args<FMT_BUFFER_CONTEXT(char)>);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&);

View File

@@ -62,7 +62,7 @@ using RWResult = int;
inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#elif FMT_USE_FCNTL
#else
// Return type of read and write functions.
using RWResult = ssize_t;
@@ -124,8 +124,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
if (result != 0) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
format_to(buffer_appender<char>(out), "{}: {}", message,
utf8_message);
format_to(std::back_inserter(out), "{}: {}", message, utf8_message);
return;
}
break;
@@ -289,12 +288,12 @@ void file::pipe(file& read_end, file& write_end) {
}
buffered_file file::fdopen(const char* mode) {
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
// Don't retry as fdopen doesn't return EINTR.
#if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
# else
#else
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
# endif
#endif
if (!f)
FMT_THROW(
system_error(errno, "cannot associate stream with file descriptor"));
@@ -314,9 +313,5 @@ long getpagesize() {
return size;
# endif
}
FMT_API void ostream::grow(size_t) {
if (this->size() == this->capacity()) flush();
}
#endif // FMT_USE_FCNTL
FMT_END_NAMESPACE

Binary file not shown.

Binary file not shown.

View File

@@ -17,5 +17,5 @@ Please consult the [license](https://raw.githubusercontent.com/microsoft/cascadi
### Fonts Included
* Cascadia Code, Cascadia Mono (2102.25)
* from microsoft/cascadia-code@911dc421f333e3b72b97381d16fee5b71eb48f04
* Cascadia Code, Cascadia Mono (2009.21)
* from microsoft/cascadia-code@32f84124db1970fa5d032f0fe9019e6922961beb

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 13H16V6H2C0.9 6 0 6.9 0 8V13Z" fill="#CCCCCC"/>
<path d="M32 6H16V13H32V6Z" fill="#999999"/>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="foreground"><stop stop-color="#000000"/></linearGradient>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -1,42 +0,0 @@
// A simple animated shader that fades the terminal background back and forth between two colours
// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings
{
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
};
// pi and tau (2 * pi) are useful constants when using trigonometric functions
#define TAU 6.28318530718
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 sample = shaderTexture.Sample(samplerState, tex);
// The number of seconds the breathing effect should span
float duration = 5.0;
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
// Set background colour based on the time
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
// Draw the terminal graphics over the background
return lerp(backgroundColor, sample, sample.w);
}

View File

@@ -1,45 +0,0 @@
// A simple animated shader that draws an inverted line that scrolls down the screen
// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings
{
// The number of seconds since the pixel shader was enabled
float Time;
// UI Scale
float Scale;
// Resolution of the shaderTexture
float2 Resolution;
// Background color as rgba
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 color = shaderTexture.Sample(samplerState, tex);
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
// the timer to count to five repeatedly. We then divide the result by five again
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
float linePosition = Time % 5 / 5;
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
// texture to find out the size of a single pixel
float lineWidth = 1.0 / Resolution.y;
// If the current texture coordinate is in the range of our line on the Y axis:
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
{
// Invert the sampled color
color.rgb = 1.0 - color.rgb;
}
return color;
}

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
// Time since pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,9 +16,8 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
// Time since pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,9 +16,8 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

View File

@@ -20,7 +20,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
// Time since pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -30,9 +30,8 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
@@ -50,22 +49,16 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
Save this file as `C:\temp\invert.hlsl`, then update a profile with the setting:
```
"experimental.pixelShaderPath": "C:\\temp\\invert.hlsl"
"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl"
```
Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen!
Default Terminal | Inverted Terminal
---------|---------
![Default Terminal](Screenshots/TerminalDefault.PNG) | ![Inverted Terminal](Screenshots/TerminalInvert.PNG)
If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the `settings.json` file again, or open a new tab, and the Terminal will try loading the shader again.
## HLSL
The language we use to write pixel shaders is called `HLSL`. It's a `C`-like language, with some restrictions. You can't allocate memory, use pointers or recursion.
The language we use to write pixel shaders is called `HLSL`. It a `C`-like language, with some restrictions.You can't allocate memory, use pointers or recursion.
What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility.
[shadertoy](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). Converting from `GLSL` to `HLSL` isn't overly hard once you gotten the hang of it.
@@ -83,7 +76,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
// Time since pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -93,9 +86,8 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
@@ -138,83 +130,7 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable.
![Rasterbars](Screenshots/TerminalRasterbars.PNG)
## Retro Terminal Effect
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory.
![Retro](Screenshots/TerminalRetro.PNG)
## Animated Effects
You can use the `Time` value in the shader input settings to drive animated effects. `Time` is the number of seconds since the shader first loaded. Heres a simple example with a line of inverted pixels that scrolls down the terminal (`Animate_scan.hlsl`):
```hlsl
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 color = shaderTexture.Sample(samplerState, tex);
// Here we spread the animation over 5 seconds. We use time modulo 5 because we want
// the timer to count to five repeatedly. We then divide the result by five again
// to get a value between 0.0 and 1.0, which maps to our texture coordinate.
float linePosition = Time % 5 / 5;
// Since TEXCOORD ranges from 0.0 to 1.0, we need to divide 1.0 by the height of the
// texture to find out the size of a single pixel
float lineWidth = 1.0 / Resolution.y;
// If the current texture coordinate is in the range of our line on the Y axis:
if (tex.y > linePosition - lineWidth && tex.y < linePosition)
{
// Invert the sampled color
color.rgb = 1.0 - color.rgb;
}
return color;
}
```
What if we want an animation that goes backwards and forwards? In this example (`Animate_breathe.hlsl`), we'll make the background fade between two colours. Our `Time` value only ever goes up, so we need a way to generate a value that sweeps back and forth from `0.0` to `1.0`. Trigonometric functions like cosine are perfect for this and are very frequently used in shaders.
`cos()` outputs a value between `-1.0` and `1.0`. We can adjust the wave with the following formula:
```
a * cos(b * (x - c)) + d
```
Where `a` adjusts the amplitude, `b` adjusts the wavelength/frequency, `c` adjusts the offset along the x axis, and `d` adjusts the offset along the y axis. You can use a graphing calculator (such as the Windows Calculator) to help visualize the output and experiment:
![Cosine](Screenshots/GraphCosine.png)
As shown above, by halving the output and then adding `0.5`, we can shift the range of the function to `0.0` - `1.0`. Because `cos()` takes input in radians, if we multiply `x` (`Time`) by tau (`2*pi`), we are effectively setting the wavelength to `1.0`.
In other words, our full animation will be one second long. We can modify this duration by dividing tau by the number of seconds we want the animation to run for. In this case, well go for five seconds.
Finally we use linear interpolation to achieve our breathing effect by selecting a color between our two chosen colors based on the output from our cosine.
```hlsl
// pi and tau (2 * pi) are useful constants when using trigonometric functions
#define TAU 6.28318530718
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
float4 sample = shaderTexture.Sample(samplerState, tex);
// The number of seconds the breathing effect should span
float duration = 5.0;
float3 color1 = float3(0.3, 0.0, 0.5); // indigo
float3 color2 = float3(0.1, 0.1, 0.44); // midnight blue
// Set background colour based on the time
float4 backgroundColor = float4(lerp(color1, color2, 0.5 * cos(TAU / duration * Time) + 0.5), 1.0);
// Draw the terminal graphics over the background
return lerp(backgroundColor, sample, sample.w);
}
```
Feel free to modify and experiment!
As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory. Feel free to modify and experiment!

View File

@@ -6,7 +6,7 @@ SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
// The number of seconds since the pixel shader was enabled
// Time since pixel shader was enabled
float Time;
// UI Scale
float Scale;
@@ -16,9 +16,8 @@ cbuffer PixelShaderSettings {
float4 Background;
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
// A pixel shader is a program that given a texture coordinate (tex) produces a color
// Just ignore the pos parameter
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

View File

@@ -43,7 +43,6 @@
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Server" Name="1A541C01-589A-496E-85A7-A9E02170166D"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser" Name="c9ba2a84-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.Render.VtEngine" Name="c9ba2a95-d3ca-5e19-2bd6-776a0910cb9d"/>
<EventProvider Id="EventProvider-Microsoft.Windows.Console.UIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
<!-- Now define some profiles. We'll call them by ID when collecting. Also, the Base is where it is inheriting from and is a .wprpi file built... -->
<!-- ... into WPR automatically. Go look in the WPR install directory or in the documentation to find it. -->
<Profile Id="ConsolePerfProfile.Verbose.File" Base="GeneralProfile.Light.File" LoggingMode="File" Name="ConsolePerfProfile" DetailLevel="Verbose" Description="Console Performance default profile">
@@ -67,7 +66,6 @@
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Server"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.VirtualTerminal.Parser"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.Render.VtEngine"/>
<EventProviderId Value="EventProvider-Microsoft.Windows.Console.UIA"/>
</EventProviders>
</EventCollectorId>
</Collectors>

View File

@@ -11,16 +11,10 @@
// - attr - the default text attribute
// Return Value:
// - constructed object
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
// Note: will throw exception if unable to allocate memory for text attribute storage
ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr)
{
try
{
_list.emplace_back(TextAttributeRun(cchRowWidth, attr));
}
catch (...)
{
FAIL_FAST_CAUGHT_EXCEPTION();
}
_list.push_back(TextAttributeRun(cchRowWidth, attr));
_cchRowWidth = cchRowWidth;
}
@@ -31,7 +25,7 @@ ATTR_ROW::ATTR_ROW(const UINT cchRowWidth, const TextAttribute attr) noexcept
void ATTR_ROW::Reset(const TextAttribute attr)
{
_list.clear();
_list.emplace_back(TextAttributeRun(_cchRowWidth, attr));
_list.push_back(TextAttributeRun(_cchRowWidth, attr));
}
// Routine Description:
@@ -184,15 +178,15 @@ size_t ATTR_ROW::FindAttrIndex(const size_t index, size_t* const pApplies) const
// Routine Description:
// - Finds the hyperlink IDs present in this row and returns them
// Return value:
// - The hyperlink IDs present in this row
std::vector<uint16_t> ATTR_ROW::GetHyperlinks()
// - An unordered set containing the hyperlink IDs present in this row
std::unordered_set<uint16_t> ATTR_ROW::GetHyperlinks()
{
std::vector<uint16_t> ids;
std::unordered_set<uint16_t> ids;
for (const auto& run : _list)
{
if (run.GetAttributes().IsHyperlink())
{
ids.emplace_back(run.GetAttributes().GetHyperlinkId());
ids.emplace(run.GetAttributes().GetHyperlinkId());
}
}
return ids;
@@ -408,7 +402,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;
decltype(_list) newRun;
std::vector<TextAttributeRun> newRun;
newRun.reserve(cNewRun);
// We will start analyzing from the beginning of our existing run.
@@ -601,7 +595,8 @@ std::vector<TextAttributeRun> ATTR_ROW::PackAttrs(const std::vector<TextAttribut
{
if (runs.empty() || runs.back().GetAttributes() != attr)
{
runs.emplace_back(TextAttributeRun(1, attr));
const TextAttributeRun run(1, attr);
runs.push_back(run);
}
else
{

Some files were not shown because too many files have changed in this diff Show More