terminal/doc/specs/#754 - Cascading Default Settings.md

718 lines
36 KiB
Markdown
Raw Permalink Normal View History

Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
---
author: Mike Griese @zadjii-msft
created on: 2019-05-31
last updated: 2019-07-31
issue id: 754
---
# Cascading Default + User Settings
## Abstract
This spec outlines adding support for a cascading settings model. In this model,
there are two settings files, instead of one.
1. The default settings file
2. The user's settings file
The default settings file would be a static, read-only file shipped with the
terminal. The user settings file would then contain all the user's chosen
customizations to the settings. These two files would then be composed together
when the app is launched, so that the runtime settings are the union of both the
defaults and whatever modifications the user has chosen. This will enable the
app to always use a default schema that it knows will be valid, and minimize the
settings that the user needs to customize.
Should the settings schema ever change, the defaults file will change, without
needing to re-write the user's settings file.
It also outlines a mechanism by which profiles could be dynamically added or
hidden from the profiles list, based on some external source.
## Inspiration
Largely inspired by the settings model that both VS Code (and Sublime Text) use.
### Goal: Minimize Re-Serializing `profiles.json`
We want to re-serialize the user settings file, `profiles.json`, as little as
2019-09-25 00:34:26 +02:00
possible. Each time we serialize the file, there's the possibility that we've
Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
re-ordered the keys, as `jsoncpp` provides no ordering guarantee of the keys.
This isn't great, as each write of the file will randomly re-order the file.
One of our overarching goals with this change should be to re-serialize the user
settings file as little as possible.
### Goal: Minimize Content in `profiles.json`
We want the user to only have to make the minimal number of changes possible to
the user settings file. Additionally, the user should only have to have the
settings that they've changed in that file. If the user wants to change only the
`cursorColor` of a profile, they should only need to set that property in the
user settings file, and not need an entire copy of the `Profile` object in their
user settings file. That would create additional noise that's not relevant to
the user.
### Goal: Remove the Need to Reset Settings Entirely to get New Settings
One problem with the current settings design is that we only generate "default"
settings for the user when there's no settings file present at all. So, when we
want to do things like update the default profiles to have an icon, or add
support for generating WSL profiles, it will only apply to users for fresh
installs. Otherwise, a user needs to completely delete the settings file to have
the terminal re-generate the default settings.
This is fairly annoying to the end-user, so ideally we'll find a way to be able
to prevent this scenario.
### Goal: Prevent Roaming Settings from Failing
Another problem currently is that when settings roam to another machine, it's
possible that the second machine doesn't have the same applications installed as
the first, and some profiles might be totally invalid on the second machine.
Take for example, profiles for WSL distros. If you have and Ubuntu profile on
your first machine, and roam that profile to a second machine without Ubuntu
installed, then the Ubuntu profile would be totally broken on the second
machine.
While we won't be able to non-destructively prevent all failures of this case,
we should be able to catch it in certain scenarios.
## Solution Design
The settings are now composed from two files: a "Default" settings file, and a
"User" settings file.
When we load the settings, we'll perform the following steps, each mentioned in
greater detail below:
1. Load from disk the `defaults.json` (the default settings) -> DefaultsJson
1. Load from disk the `profiles.json` (the user settings) -> UserJson
1. Parse DefaultsJson to create all the default profiles, schemes, keybindings.
1. [Not covered in this spec] Check the UserJson to find the list of dynamic
profile sources that should run.
1. Run all the _enabled_ dynamic profile generators. Those profiles will be
added to the set of profiles.
- During this step, check if any of the profiles added here don't exist in
UserJson. If they _don't_, the generator created a profile that didn't
exist before. Return a value indicating the user settings should be
re-saved (with the new profiles added).
1. [Not covered in this spec] Layer the UserJson.globals.defaults settings to
every profile in the set, both the defaults, and generated profiles.
1. Apply the user settings from UserJson. Layer the profiles on top of the
existing profiles if possible (if both `guid` and `source` match). If a
profile from the user settings does not already exist, make sure to apply the
UserJson.globals.defaults settings first. Also layer Color schemes and
keybindings.
- If a profile has a `source` key, but there is not an existing profile with
a matching `guid` and `source`, don't create a new Profile object for it.
Either that generator didn't run, or the generator wanted to delete that
profile, so we'll effectively hide the profile.
1. Re-order the list of profiles, to match the ordering in the UserJson. If a
profile doesn't exist in UserJson, it should follow all the profiles in the
UserJson. If a profile listed in UserJson doesn't exist, we can skip it
safely in this step (the profile will be a dynamic profile that didn't get
populated.)
1. Validate the settings.
1. If requested in step 5, write the modified settings back to `profiles.json`.
### Default Settings
We'll have a static version of the "Default" file **hardcoded within the
application package**. This `defaults.json` file will live within the
application's package, which will prevent users from being able to edit it.
```json
// This is an auto-generated file. Place any modifications to your settings in "profiles.json"
```
This disclaimer will help identify that the file shouldn't be modified. The file
won't actually be generated, but because it's shipped with our app, it'll be
overridden each time the app is updated. "Auto-generated" should be good enough
to indicate to users that it should not be modified.
Because the `defaults.json` file is hardcoded within our application, we can use
its text directly, without loading the file from disk. This should help save
some startup time, as we'll only need to load the user settings from disk.
When we make changes to the default settings, or we make changes to the settings
schema, we should make sure that we update the hardcoded `defaults.json` with
the new values. That way, the `defaults.json` file will always have the complete
set of settings in it.
### Layering settings
When we load the settings, we'll do it in three stages. First, we'll deserialize
the default settings that we've hardcoded. We'll then generate any profiles that
might come from dynamic profile sources. Then, we'll intelligently layer the
user's setting upon those we've already loaded. If a user wants to make changes
to some objects, like the default profiles, we'll need to make sure to load from
the user settings into the existing objects we created from the default
settings.
* We'll need to make sure that any profile in the user settings that has a GUID
matching a default profile loads the user settings into the object created
from the defaults.
* We'll need to make sure that there's only one action bound to each key chord
for a keybinding. If there are any key chords in the user settings that match
a default key chord, we should bind them to the action from the user settings
instead.
* For any color schemes whose name matches the name of a default color scheme,
we'll need to apply the user settings to the existing color scheme. For
example, a user could override the `red` entry of the "Campbell" scheme to be
`#ff9900` if they want. This would then apply to all profiles using the
"Campbell" scheme.
* For profiles that were created from a dynamic profile source, they'll have
both a `guid` and `source` guid that must _both_ match. If a user profile with
a `source` set does not find a matching profile at load time, the profile will
be ignored. See more details in the [Dynamic Profiles](#dynamic-profiles)
section.
### Hiding Default Profiles
What if a user doesn't want to see one of the profiles that we've included in
the default profiles?
We will add a `hidden` key to each profile, which defaults to false. When we
want to mark a profile as hidden, we'd just set that value to `true`, instead of
trying to look up the profile's guid.
So, if someone wanted to hide the default cmd.exe profile, all they'd have to do
is add `"hidden": true` to the cmd.exe entry in their user settings, like so:
```js
{
"profiles": [
{
// Make changes here to the cmd.exe profile
"guid": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}",
"hidden": true
}
],
```
#### Hidden Profiles and the Open New Tab shortcuts
Currently, there are keyboard shortcuts for "Open New Tab With Profile
&lt;N&gt;". These shortcuts will open up the Nth profile in the new tab
dropdown. Considering we're adding the ability to remove profiles from that
list, but keep them in the overall list of profiles, we'll need to make sure
that the handler for that event still opens the Nth _visible_ profile.
### Serializing User Settings
How can we tell that a setting should be written back to the user settings file?
If the value of the setting isn't the same as the defaults, then it could easily
be added to the user's `profiles.json`. We'll have to do a smart serialization
of the various settings models. We'll pass in the default version **of that
model** during the serialization. If that object finds that a particular setting
is the same as a default setting, then we'll skip serializing it.
What happens if a user has chosen to set the value to _coincidentally_ the same
value as the default value? We should keep that key in the user's settings file,
even though it is the same.
In order to facilitate this, we'll need to keep the originally parsed user
settings around in memory. When we go to serialize the settings, we'll check if
either the setting exists already in the user settings file, or the setting has
changed. If either is true, then we'll make sure to write that setting back out.
For serializing settings for the default profiles, we'll check if the setting is
in the user settings file, or if the value of the setting is different from the
version of that `Profile` from the default settings. For user-created profiles,
we'll compare the value of the setting with the value of the _default
constructed_ `Profile` object. This will help ensure that each profile in the
user's settings file maintains the minimal amount of info necessary.
When we're adding profiles due to their generation in a dynamic profile
generator, we'll need to serialize them, then insert them back into the
originally parsed json object to be serialized. We don't want the automatic
creation of a new profile to automatically trigger re-writing the entire user
settings file, but we do want newly created dynamic profiles to have an entry
the user can easily edit.
### Dynamic Profiles
Sometimes, we may want to auto-generate a profile on the user's behalf. Consider
the case of WSL distros on their machine, or VMs running in Azure they may want
to auto-connect to. These _dynamic_ profiles have a source that might be added
or removed after the app is installed, and they will be different from user to
user.
Currently, these profiles are only generated when a user first launches the
Terminal. If they already have a `profiles.json` file, then we won't run the
auto-generation behavior. This is obviously not great - if any new types of
dynamic profiles are added, then users that already have the Terminal installed
2019-09-25 00:34:26 +02:00
won't get any of these dynamic profiles. Furthermore, if any of the sources of
Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
these dynamic profiles are removed, then the app won't auto-remove the
associated profile.
In the new model, with a combined defaults & user settings, how should these
dynamic profiles work?
I propose we add functionality to automatically search for these profile sources
and add/remove them on _every_ Terminal launch. To make this functionality work
appropriately, we'll need to introduce a constraint on dynamic profiles.
**For any dynamic profiles, they must be able to be generated using a stable
GUID**. For example, any time we try adding the "Ubuntu" profile, we must be
able to generate the same GUID every time. This way, when a dynamic profile
generator runs, it can check if that profile source already has a profile
associated with it, and do nothing (as to not create many duplicate "Ubuntu"
profiles, for example).
Additionally, each dynamic profile generator **must have a unique source guid**
to associate with the profile. When a dynamic profile is generated, the source's
guid will be added to the profile, to make sure the profile is correlated with
the source it came from.
We'll generate these dynamic profiles immediately after parsing the default
profiles and settings. When a generator runs, it'll be able to create unique
profile GUIDs for each source it wants to generate a profile for. It'll hand
back a list of Profile objects, with settings set up how the generator likes,
with GUIDs set.
After a dynamic profile generator runs, we will determine what new profiles need
to be added to the user settings, so we can append those to the list of
profiles. The deserializer will look at the list of generated profiles and check
if each and every one already has a entry in the user settings. The generator
will just blind hand back a list of profiles, and the deserializer will figure
out if any of them need to be added to the user settings. We'll store some sort
of result indicating that we want a save operation to occur. After the rest of
the deserializing is done, the app will then save the `profiles.json` file,
including these new profiles.
When we're serializing the settings, instead of comparing a dynamic profile to
the default-constructed `Profile`, we'll compare it to the state of the
`Profile` after the dynamic profile generator created it. It'd then only
serialize settings that are different from the auto-generated version. It will
also always make sure that the `guid` of the dynamic profile is included in the
user settings file, as a point for the user to add customizations to the dynamic
profile to. Additionally, we'll also make sure the `source` is always serialized
as well, to keep the profile correlated with the generator that created it.
We'll need to keep the state of these dynamically generated profiles around in
memory during runtime to be able to ensure the only state we're serializing is
that which is different from the initially generated dynamic profile.
When the generator is run, and determines that a new profile has been added,
we'll need to make sure to add the profile to the user's settings file. This
will create an easy point for users to customize the dynamic profiles. When
added to the user settings, all that will be added is the `name`, `guid`, and
`source`.
Additionally, a user might not want a dynamic profile generator to always run.
They might want to keep their Azure connections visible in the list of profiles,
even if its no longer a valid target. Or they might want to not automatically
connect to Azure to find new instances every time they launch the terminal. To
enable scenarios like this, we'll add an additional setting,
`disabledProfileSources`. This is an array of guids. If any guids are in that
list, then those dynamic profile generators _won't_ be run, suppressing those
profiles from appearing in the profiles list.
If a dynamic profile generator needs to "delete" a profile, this will also work
naturally with the above rules. Lets examine the case where the user has
uninstalled the Ubuntu distro. When the WSL generator runs, it won't create the
Ubuntu profile. When we get to the Ubuntu profile in the user's settings, it'll
have a `source`, but we won't already have a profile with that `guid` and
`source`. So we'll just ignore it, because whatever source for that profile
doesn't want it anymore. Effectively, this will act like it was "deleted",
though the artifacts still remain untouched in the user's json.
#### What if a dynamic profile is removed, but it's the default?
I'll direct our attention to [#1348] - Display a specific error for not finding
the default profile. When we're done loading, and we determine that the default
profile doesn't exist in the finalized list of profiles, we'll display a dialog
to the user. This includes both hidden profiles and dynamic profiles that have
been "deleted". We'll temporarily use the _first_ profile instead.
#### Dynamic profile GUID generation
In order to help facilitate the generation of stable, unique GUIDs for
dynamically generated profiles, we'll enforce a few methods on each generator.
The Generator should implement a method that returns its _unique_ namespace for
profiles it generates:
```c++
class IDynamicProfileGenerator
{
...
virtual std::wstring GetNamespace() = 0;
...
}
```
For example, the WSL generator would return `Microsoft.Terminal.WSL`. The
Powershell Core generator would return `Microsoft.Terminal.PowershellCore`.
We'll use these names to be able to generate uuidv5 GUIDs that will be unique
(so long as the names are unique).
The generator should also be able to ask the app for two other pieces of
functionality:
* The generator should be able to ask the app for the generator's own namespace
GUID
* The generator should be able to ask the app for a uuidv5 in the generator's
namespace, given a specific name key.
These two functions will be exposed to the generator like so:
```c++
GUID GetNamespaceGuid(IDynamicProfileGenerator& generator);
GUID GetGuidForName(IDynamicProfileGenerator& generator, std::wstring& name);
```
The generator does not _need_ to use `GetGuidForName` to generate guids for it's
profiles. If the generator can determine another way to generate stable GUIDs
for its profiles, it's free to use whatever method it wants. `GetGuidForName` is
provided as a convenience.
It's not the responsibility of the dynamic profile generator to fill in the
`source` of the profiles it generates. The deserializer will make sure to go
through and fill in the guid for the generated profiles given the generator's
namespace GUID.
### Powershell Core & the Defaults
How do we handle the potential existence of Powershell Core in this model?
Powershell core is unique as far as the default profiles goes - it may or may
not exist on the user's system. Not only that, but depending on the user's
install of Powershell Core, it might have a path in either `Program Files` or
`Program Files(x86)`.
Additionally, if it _is_ installed, we set it as the default profile instead of
Windows Powershell.
Powershell core acts much like a dynamic profile. It has an installation source
that may or not be there. So we'll add a dynamic profile generator for
Powershell Core. This will automatically create a profile for Powershell Core if
necessary.
Unlike the other dynamic profiles, if Powershell Core is present on
_first_ launch of the terminal, we set that as the default profile. This can
still be done - we'll need to do some special-case work when we're loading the
user settings and we _don't_ find any existing settings. When that happens,
we'll generate all the default user settings. Before we commit them, we'll check
if the Powershell Core profile exists, and if it does, we'll set that as the
default profile before writing the settings to disk.
### Unbinding a Keybinding
How can a user unbind a key that's part of the default keybindings? What if a
user really wants <kbd>ctrl</kbd>+<kbd>t</kbd> to fall through to the
commandline application attached to the shell, instead of opening a new tab?
We'll need to introduce a new keybinding command that should indicate that the
key is unbound. We'll load the user keybindings and layer them on the defaults
as described above. If during the deserializing we find an entry that's bound to
the command `"unbound"` or any other string that we don't understand, instead of
trying to _set_ the keybinding, we'll _clear_ the keybinding with a new method
`AppKeyBindings::ClearKeyBinding(chord)`.
### Removing the Globals Object
As a part of #[1005](https://github.com/microsoft/terminal/pull/1005), all the
global settings were moved to their own object within the serialized settings.
This was to try and make the file easier to parse as a user, considering global
settings would be intermingled with profiles, keybindings, color schemes, etc.
Since this change will make the user settings dramatically easier to navigate,
we should probably remove the `globals` object, and have globals at the root
level again.
### Default `profiles.json`
Below is an example of what the default user settings file might look like when
it's first generated, taking all the above points into consideration.
```js
// To view the default settings, open <path-to-app-package>\defaults.json
{
"defaultProfile" : "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"profiles": [
{
// Make changes here to the cmd.exe profile
"guid": "{6239a42c-1de4-49a3-80bd-e8fdd045185c}"
},
{
// Make changes here to the Windows Powershell profile
"guid": "{086a83cd-e4ef-418b-89b1-3f6523ff9195}",
},
{
"guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}",
"name" : "Powershell Core",
"source": "{2bde4a90-d05f-401c-9492-e40884ead1d8}",
}
],
// Add custom color schemes to this array
"schemes": [],
// Add any keybinding overrides to this array.
// To unbind a default keybinding, set the command to "unbound"
"keybindings": []
}
```
Note the following:
* cmd.exe and powershell.exe are both in the file, as to give users an easy
point to extend the settings for those default profiles.
* Powershell Core is included in the file, and the default profile has been set
to its GUID. The `source` has been set, indicating that it came from a dynamic profile source.
* There are a few helpful comments scattered throughout the file to help point
the user in the right direction.
### Re-ordering profiles
Since there are shortcuts to open the Nth profile in the list of profiles, we
need to expose a way for the user to change the order of the profiles. This was
not a problem when there was only a single list of profiles, but if the defaults
are applied _first_ to the list of profiles, then the user wouldn't be able to
change the order of the default profiles. Additionally, any profiles they add
would _always_ show up after the defaults.
To remedy this, we could scan the user profiles in the user settings first, and
create `Profile` objects for each of those profiles first. These `Profile`s
would only be initialized with their GUID temporarily, but they'd be placed into
the list of profiles in the order they appear in the user's settings. Then, we'd
load all the default settings, overlaying any default profiles on the `Profile`
objects that might already exist in the list of profiles. If there are any
default profiles that don't appear in the user's settings, they'll appear
_after_ any profiles in the user's settings. Then, we'll overlay the full user
settings on top of the defaults.
## UI/UX Design
### Opening `defaults.json`
How do we open both these files to show to the user (for the interim period
before a proper Settings UI is created)? Currently, the "Settings" button only
opens a single json file, `profiles.json`. We could keep that button doing the
same thing, though we want the user to be able to also view the default settings
file, to be able to inspect what settings they wish to change.
We could have the "Settings" button open _both_ files at the same
time. I'm not sure that `ShellExecute` (which is used to open these files)
provides any ordering guarantees, so it's possible that the `defaults.json`
would open in the foreground of the default json editor, while making in unclear
that there's another file they should be opening instead. Additionally, if
there's _no_ `.json` editor for the user, I believe the shell will attempt
_twice_ to ask the user to select a program to open the file with, and it might
not be clear that they need to select a program in both dialogs.
Alternatively, we could make the defaults file totally inaccessible from the
Terminal UI, and instead leave a comment in the auto-generated `profiles.json`
like so:
```json
// To view the default settings, open the defaults.json file in this directory
```
The "Settings" button would then only open the file the user needs to edit, and
provide them instructions on how to open the defaults file.
There could alternatively be a hidden option for the "Open Settings" button,
where holding <kbd>Alt</kbd> while clicking on the button would open the
`defaults.json` instead.
We could additionally add a `ShortcutAction` (to be bound to a keybinding) that
would `openDefaultSettings`, and we could bind that to
<kbd>ctrl</kbd>+<kbd>alt</kbd>+<kbd>\`</kbd>, similar to `openSettings` on
<kbd>ctrl</kbd>+<kbd>\`</kbd>.
### How does this work with the settings UI?
If we only have one version of the settings models (Globals, Profiles,
ColorSchemes, Keybindings) at runtime, and the user changes one of the settings
Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
with the settings UI, how can we tell that settings changed?
Fortunately, this should be handled cleanly by the algorithm proposed above, in
the "Serializing User Settings" section. We'll only be serializing settings that
have changed from the defaults, so only the actual changes they've made will be
persisted back to the user settings file.
## Capabilities
### Security
I don't think this will introduce any new security issues that weren't already
present
### Reliability
I don't think this will introduce any new reliability concerns that weren't
already present. We will likely improve our reliability, as dynamic profiles
that no longer exist will not cause the terminal to crash on startup anymore.
### Performance, Power, and Efficiency
By not writing the defaults to disk, we'll theoretically marginally improve the
load and save times for the `profiles.json` file, by simply having a smaller
file to load. However we'll also be doing more work to process the layering of
defaults and user settings, which will likely slightly increase the load times.
Overall, I expect the difference to be negligible due to these factors.
One potential concern is long-running dynamic profile generators. Because
they'll need to run on startup, they could negatively impact startup time. You
can read more below, in "Dynamic Profile Generators Need to be Enabled".
### Accessibility
N/A
## Potential Issues
### Profiles with the same `guid` as a dynamic profile but not the same `source`
What happens if the User settings has a profile with a `guid` that matches a
dynamic or default profile, but the user profile doesn't have a matching source?
This could happen trivially easily if the user deletes the `source` key from a
profile that has dynamically generated.
We could:
1. Treat the profile as an entirely separate profile
- There's lots of other code that assumes each profile has only a unique GUID,
so we'd have to change the GUID of this profile. This would mean writing out
the user settings, which we'd like to avoid.
- We'll still end up generating the entry for the dynamic profile in the
user's settings, so we'll need to write out the user settings anyways.
- This other profile will likely not have a commandline set, so it might not
work at all.
1. Ignore the profile entirely.
- When the dynamic profile generator runs, we're not going to find another
entry in the user profiles with both a matching `guid` and a matching
`source`. So we'll end up creating _another_ entry in the user profiles for
the dynamic profile.
- How could the user know that the profile is being ignored? There's nothing
in the file itself that indicates obviously that this profile is now
invalid.
1. Treat the user settings as part of the dynamic profile
- In this scenario, the user profile continues to exist as part of the dynamic profile.
- When the dynamic profile generator runs, we're not going to find another
entry in the user profiles with both a matching `guid` and a matching
`source`. So we'll end up creating _another_ entry in the user profiles for
the dynamic profile.
- These two entries will each be layered upon the dynamically generated
profile, so the settings in the second profile entry will override
settings from the first.
- If the user disables the generator, or the profile source is removed, the
dynamic profile will cease to exist. However, the profile without the
`source` entry will remain, though likely will not work.
- How do we order these profiles for the user? When we're parsing the user
profiles list to build an ordering of profiles, do we use the first entry as
the index for that profile?
1. (Variant of the above) Treat the profile as part of the dynamic profile, and
re-insert the `source` key.
- This will re-connect the user profile to the dynamic one.
- We'll need to make sure to do this before determining the new dynamic
profiles to add to the user settings.
- Given all the scenarios are going to cause a user settings write anyways,
this isn't terrible.
- If the user _really_ wants to split the profile in their user settings from
the dynamic one, they're free to always generate a new guid _and_ delete the
`source` key.
Given the drawbacks associated with options 1-3, I propose we choose option 4 as
our solution to this case.
### Migrating Existing Settings
I believe that existing `profiles.json` files will smoothly update to this
model, without breaking. While in the new model, the `profiles.json` file can be
much more sparse, users who have existing `profiles.json` files will have full
settings in their user settings. We'll leave their files largely untouched, as
we won't touch keys that have the same values as defaults that are currently in
the `profiles.json` file. Fortunately though, users should be able to remove
much of the boilerplate from their `profiles.json` files, and trim it down just
to their modifications.
#### Migrating Powershell Core
Right now, default-generated Powershell Core profiles exist with a stable guid
we've generated for them. However, when we move Powershell Core to being a
dynamically generated profile, we'll have to ensure that we don't create a
duplicated "dynamic" entry for that profile. If we want to convert the existing
Powershell Core profiles into a dynamic profile, we'll need to make sure to add
a `source` key to the profile. Everything else in the profile can remain the
same. Once the `source` is added, we'll know to treat it as a dynamic profile,
and it'll respond dynamically.
This is actually something that will automatically be covered by the scenario
mentioned above in "Profiles with the same `guid` as a dynamic profile but not
the same `source`". When we encounter the existing Powershell Core profiles that
don't have a `source`, we'll automatically think they're the dynamically
generated ones, and auto-migrate them.
#### Migrating Existing WSL Profiles
Similar to the above, so long as we ensure the WSL dynamic profile generator
generates the _same_ GUIDs as it does currently, all the existing WSL profiles
will automatically be migrated to dynamic profiles.
### Dynamic Profile Generators Need to be Enabled
With the current proposal, profiles that are generated by a dynamic profile
generator _need_ that generator to be enabled for the profile to appear in the
list of profiles. If the generator isn't enabled, then the important parts of
the profile (name, commandline) will never be set, and the profile's settings
from the user settings will be ignored at runtime.
For generators where the generation of profiles might be a lengthy process, this
could negatively impact startup time. Take for example, some hypothetical
generator that needs to make web requests to generate dynamic profiles. Because
we need the finalized settings to be able to launch the terminal, we'll be stuck
loading until that generator is complete.
However, if the user disables that generator entirely, we'll never display that
profile to the user, even if they've done that setup before.
So the trade-off with this design is that non-existent dynamic profiles will
never roam to machines where they don't exist and aren't valid, but the
generators _must_ be enabled to use the dynamic profiles.
## Future considerations
* It's possible that a very similar layering loading mechanism could be used to
layer per-machine settings with roaming settings. Currently, there's only one
settings file, and it roams to all your devices. This could be problematic,
for example, if one of your machines has a font installed, but another
doesn't. A proposed solution to that problem was to have both roaming settings
and per-machine settings. The code to layer settings from the defaults and the
user settings could be re-used to handle layer the roaming and per-machine
settings.
* What if an extension wants to generate their own dynamic profiles? We've
already outlined a contract that profile generators would have to follow to
behave correctly. It's possible that we could abstract our implementation into
a WinRT interface that extensions could implement, and be triggered just like
other dynamic profile generators.
* **Multiple settings files** - This could enable us to place color schemes into
a separate file (like `colorschemes.json`) and put keybindings into their own
Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
file as well, and reduce the number of settings in the user's `profiles.json`.
It's unclear if this is something that we need quite yet, but the same
layering functionality that enables this scenario could also enable more than
two sources for settings.
* **Global Default Profile Settings** - Say a user wants to override what the
defaults for a profile are, so that they can set settings for _all_ their
profiles at once? We could maybe introduce a profile in the user settings file
with a special guid set to `"default`, that we look for first, and treat
specially. We wouldn't include it in the list of profiles. When we're creating
profiles, we'll start with that profile as our prototype, instead of using the
default-constructed `Profile`. When we're serializing profiles, we'd again use
that as the point of comparison to check if a setting's value has changed.
There may be more unknowns with this proposal, so I leave it for a future
feature spec.
- We'll also want to make sure that when we're serializing default/dynamic
profiles, we take into account the state from the global defaults, and we
2019-09-25 00:34:26 +02:00
don't duplicate that information into the entries for those types of profiles
Draft Spec for Cascading Default + User Settings (#1258) * Start working on drafting this spec * Really add a LOT of notes * More spec updates. * Remove `hiddenProfiles` in favor of `profile.hidden` * Add info on how layering will work * add more powershell core info * Finish remaining TODO sections * Apply suggestions from code review Fix simple typos Co-Authored-By: Dustin L. Howett (MSFT) <duhowett@microsoft.com> * Lots of feedback from PR * Try and make dynamic settings a bit clearer * more clearly call out serializing only what's different from a default- constructed `Profile` * Add more goals * add a blurb for user-default profile objects * Add updates concerning dynamic profile generation (#1321) * Add updates concerning dynamic profile generation This is based on discussion with @dhowett-msft we had o*line. We're trying to work through a way to prevent dynamic profiles from roaming to machines the dynamic profiles might not exist on. After writing this up, I'm not totally sure that it's a better design. * Add some initial updates from discussion * Pushing some updates here. I haven't given it a once over to ensure it's all consistent but it's worth reviewing @dhowett-msft * Some minor updates from Dustin * Fix a bunch of slightly more minor points in the spec * Move "Profile Ordering" to "Future considerations" * Add some notes on migrating profiles, GUID generation, de-duping profiles, and O R A N G E * Fix the indenting here * Update powershell core to be a dynamic profile, don't even mention other options. * Remaining PR feedback * Apply suggestions from code review Co-Authored-By: Michael Niksa <miniksa@microsoft.com> * remove a dead comment
2019-08-20 15:53:30 +02:00
in the user profiles.
* **Re-ordering profiles** - Under "Solution Design", we provide an algorithm
for decoding the settings. One of the steps mentioned is parsing the user
settings to determine the ordering of the profiles. It's possible in the
future we may want to give the user more control over this ordering. Maybe
we'll want to allow the user to manually index the profiles. Or, as discussed
in issues like #1571, we may want to allow the user to further customize the
new tab dropdown, beyond just the order of profiles. The re-ordering step
would be a great place to add code to support this re-ordering, with whatever
algorithm we eventually land on. Determining such an algorithm is outside the
scope of this spec, however.
## Resources
N/A
<!-- Footnotes -->
[#1348]: https://github.com/microsoft/terminal/issues/1348