Repository: microsoft/terminal (language: cpp)

Issue:
[discussion] Initialize Terminal Preview settings from Terminal settings 
If Terminal Preview is installed for the first time, can it initialize settings from Terminal settings?

Usecase is trying out Preview with same settings as regular version. Currently it's reset to default settings.

In any case, this is not a big issue


Discussion / hints:
I don't think this is a terrible idea, just needs someone to go write the migration code.
One non-intrusive way could be implementing a context-menu item: "Replace current WT Preview settings by settings from WT" and not changing default behaviors (or implement complex settings merging). It could even be just "wtp.exe import-settings" command or sth like this.
Or the even simpler solution - when the Terminal settings are empty, do a quick check if we're the Preview version. If we are, and there's also a `settings.json` file under the non-preview path, then just copy that instead of using the `userDefaults.json`
Reset may still be useful for a (actually my) situation: I generally use non-preview. But occasionally install fresh Preview to check if a particular bug was fixed. Then settings import function (at least being able to fire it manually) is useful even when a previous version of Preview was installed
Hi, this is my first time making a contribution to an open source project and I would like to work on this! 
@Dannihu01 awesome, go for it! I don't think you'd be stepping on anyone's toes here. Let us know if you need any pointers ☺️ 
I was wondering if I could get some guidance on this issue - my partner and I edited existing commands and tried to add a new one in AppCommandLineArgs.cpp but nothing changed when we built and ran. What could we be doing wrong here?
... That might be overkill in this case. I'd start here:

https://github.com/microsoft/terminal/blob/fe618fd2ae1eb07a963aa58ceaa23f53aa5db9ba/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp#L713-L728

That's the function that loads the settings from the `settings.json` file. It can tell if the settings file was initially empty/missing entirely. When the settings are missing (`firstTimeSetup`), and we're a Preview build of the Terminal, we should try loading the settings from the "stable" file path, if they exist. I believe we have a preprocessor define (`WT_BRANDING_PREVIEW`) that's set when we're a preview build. We're using that elsewhere like so: https://github.com/microsoft/terminal/search?q=WT_BRANDING_PREVIEW

That's where I'd start at least. Does that make sense @Dannihu01?
I would say that one also needs a manual button in settings "Import settings from non-preview installation" (or sth like that)
Thank you, that did make sense. We have made good progress with that info above - 

However, what is the most efficient way to extract the stable file path?

Will all the package names always end in "8wekyb3d8bbwe?" For example at the end of "\\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe?" I understand that this is Microsoft's publisher ID, but will that ever change? If it never changes then our first idea was to hard code the stable file path and extract settings.json from there.

But we think that hard coding the stable file path and doing string manipulation isn't the right direction. What else can we do to extract the correct file path?

Fix patch:
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
index daa724a0f4c..737fa8075a8 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h
@@ -144,6 +144,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
 
     private:
         static const std::filesystem::path& _settingsPath();
+        static const std::filesystem::path& _releaseSettingsPath();
 
         winrt::com_ptr<implementation::Profile> _createNewProfile(const std::wstring_view& name) const;
         Model::Profile _getProfileForCommandLine(const winrt::hstring& commandLine) const;
diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
index 60bd730477a..91725ce9bce 100644
--- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
+++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp
@@ -766,8 +766,28 @@ void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator
 Model::CascadiaSettings CascadiaSettings::LoadAll()
 try
 {
-    const auto settingsString = ReadUTF8FileIfExists(_settingsPath()).value_or(std::string{});
-    const auto firstTimeSetup = settingsString.empty();
+    auto settingsString = ReadUTF8FileIfExists(_settingsPath()).value_or(std::string{});
+    auto firstTimeSetup = settingsString.empty();
+
+    // If it's the firstTimeSetup and a preview build, then try to
+    // read settings.json from the Release stable file path if it exists.
+    // Otherwise use default settings file provided from original settings file
+    bool releaseSettingExists = false;
+    if (firstTimeSetup)
+    {
+#if defined(WT_BRANDING_PREVIEW)
+        {
+            try
+            {
+                settingsString = ReadUTF8FileIfExists(_releaseSettingsPath()).value_or(std::string{});
+                releaseSettingExists = settingsString.empty() ? false : true;
+            }
+            catch (...)
+            {
+            }
+        }
+#endif
+    }
 
     // GH#11119: If we find that the settings file doesn't exist, or is empty,
     // then let's quick delete the state file as well. If the user does have a
@@ -780,7 +800,9 @@ try
         ApplicationState::SharedInstance().Reset();
     }
 
-    const auto settingsStringView = firstTimeSetup ? UserSettingsJson : settingsString;
+    // Only uses default settings when firstTimeSetup is true and releaseSettingExists is false
+    // Otherwise use existing settingsString
+    const auto settingsStringView = (firstTimeSetup && !releaseSettingExists) ? UserSettingsJson : settingsString;
     auto mustWriteToDisk = firstTimeSetup;
 
     SettingsLoader loader{ settingsStringView, DefaultJson };
@@ -791,7 +813,8 @@ try
 
     // ApplyRuntimeInitialSettings depends on generated profiles.
     // --> ApplyRuntimeInitialSettings must be called after GenerateProfiles.
-    if (firstTimeSetup)
+    // Doesn't run when there is a Release settings.json that exists
+    if (firstTimeSetup && !releaseSettingExists)
     {
         loader.ApplyRuntimeInitialSettings();
     }
@@ -953,6 +976,18 @@ const std::filesystem::path& CascadiaSettings::_settingsPath()
     return path;
 }
 
+// Method Description:
+// - Returns the path of the settings.json file from stable file path
+// Arguments:
+// - <none>
+// Return Value:
+// - Path to stable settings
+const std::filesystem::path& CascadiaSettings::_releaseSettingsPath()
+{
+    static const auto path = GetReleaseSettingsPath() / SettingsFilename;
+    return path;
+}
+
 // function Description:
 // - Returns the full path to the settings file, either within the application
 //   package, or in its unpackaged location. This path is under the "Local
diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.cpp b/src/cascadia/TerminalSettingsModel/FileUtils.cpp
index c9a27344a9a..8fc28be6e65 100644
--- a/src/cascadia/TerminalSettingsModel/FileUtils.cpp
+++ b/src/cascadia/TerminalSettingsModel/FileUtils.cpp
@@ -14,6 +14,7 @@
 
 static constexpr std::string_view Utf8Bom{ "\xEF\xBB\xBF", 3 };
 static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
+static constexpr std::wstring_view ReleaseSettingsFolder{ L"Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\" };
 
 namespace winrt::Microsoft::Terminal::Settings::Model
 {
@@ -43,6 +44,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model
         return baseSettingsPath;
     }
 
+    // Returns a path like C:\Users\<username>\AppData\Local\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState
+    // to the path of the stable release settings
+    std::filesystem::path GetReleaseSettingsPath()
+    {
+        static std::filesystem::path baseSettingsPath = []() {
+            wil::unique_cotaskmem_string localAppDataFolder;
+            // We're using KF_FLAG_NO_PACKAGE_REDIRECTION to ensure that we always get the
+            // user's actual local AppData directory.
+            THROW_IF_FAILED(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_PACKAGE_REDIRECTION, nullptr, &localAppDataFolder));
+
+            // Returns a path like C:\Users\<username>\AppData\Local
+            std::filesystem::path parentDirectoryForSettingsFile{ localAppDataFolder.get() };
+
+            //Appending \Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState to the settings path
+            parentDirectoryForSettingsFile /= ReleaseSettingsFolder;
+
+            if (!IsPackaged())
+            {
+                parentDirectoryForSettingsFile /= UnpackagedSettingsFolderName;
+            }
+
+            return parentDirectoryForSettingsFile;
+        }();
+        return baseSettingsPath;
+    }
+
     // Function Description:
     // - Checks the permissions on this file, to make sure it can only be opened
     //   for writing by admins. We will be checking to see if the file is owned
diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.h b/src/cascadia/TerminalSettingsModel/FileUtils.h
index 082da66110b..f898c6ba5bb 100644
--- a/src/cascadia/TerminalSettingsModel/FileUtils.h
+++ b/src/cascadia/TerminalSettingsModel/FileUtils.h
@@ -4,6 +4,7 @@
 namespace winrt::Microsoft::Terminal::Settings::Model
 {
     std::filesystem::path GetBaseSettingsPath();
+    std::filesystem::path GetReleaseSettingsPath();
     std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
     std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
     void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content, const bool elevatedOnly = false);


Test patch: