diff --git a/doc/cascadia/Cascading-Default-Settings.md b/doc/cascadia/Cascading-Default-Settings.md new file mode 100644 index 000000000..781646845 --- /dev/null +++ b/doc/cascadia/Cascading-Default-Settings.md @@ -0,0 +1,717 @@ +--- +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 +possible. Each time we serialize the file, there's the possiblity that we've +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 +<N>". 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 +won't get any of these dynamic profiles. Furthemore, if any of the sources of +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 ctrl+t 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 \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 Alt 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 +ctrl+alt+\`, similar to `openSettings` on +ctrl+\`. + +### How does this work with the settings UI? + +If we only have one version of the settings models (Globals, Profiles, +ColorShemes, Keybindings) at runtime, and the user changes one of the settings +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 seperate file (like `colorschemes.json`) and put keybindings into their own + 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 + don't duplicate that inormation into the entries for those types of profiles + 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 + + + + +[#1348]: https://github.com/microsoft/terminal/issues/1348