mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 13:01:52 +00:00
Find Rider installations
This commit is contained in:
@@ -0,0 +1,214 @@
|
|||||||
|
GlitchyEditor/src/CodeEditors/RiderPathLocator.bf (and the platform specific implementations)
|
||||||
|
are a Beef port of the Rider path locator from the JetBrains "Rider Source Code Access" Unreal Engine plugin:
|
||||||
|
|
||||||
|
https://github.com/JetBrains/RiderSourceCodeAccess
|
||||||
|
Source/RiderSourceCodeAccess/Private/RiderPathLocator/
|
||||||
|
|
||||||
|
The original work and this port are licensed under the Apache License, Version 2.0,
|
||||||
|
reproduced in full below.
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
// Beef port of JetBrains "Rider Source Code Access":
|
||||||
|
// https://github.com/JetBrains/RiderSourceCodeAccess/tree/main/Source/RiderSourceCodeAccess/Private/RiderPathLocator
|
||||||
|
//
|
||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0 - see LICENSE-RiderPathLocator.txt
|
||||||
|
//
|
||||||
|
// Changes from the original: rewritten in Beef against corlib instead of the
|
||||||
|
// Unreal Engine types (FString/TArray/TOptional/FPaths/FRegexPattern/FJsonSerializer/...).
|
||||||
|
// The "plugins/rider-cpp" requirement, the ESupportUproject state and the
|
||||||
|
// RiderLocations.txt resource-file lookup were removed because they only make sense for Unreal projects.
|
||||||
|
//
|
||||||
|
// This file holds the platform independent part, the platform specific parts live in the extensions (e.g. RiderPathLocatorWindows.bf).
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using Beefy.utils;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.CodeEditors;
|
||||||
|
|
||||||
|
/// A dot-separated version number, e.g. "2024.1.2" or the build number "241.15989.155".
|
||||||
|
struct RiderVersion : IHashable
|
||||||
|
{
|
||||||
|
public const int32 InvalidVersion = -1;
|
||||||
|
|
||||||
|
private const int32 MaxParts = 4;
|
||||||
|
|
||||||
|
private int32[MaxParts] _parts;
|
||||||
|
private int32 _count;
|
||||||
|
|
||||||
|
public bool IsInitialized => _count != 0;
|
||||||
|
|
||||||
|
public int32 Major => _count >= 1 ? _parts[0] : InvalidVersion;
|
||||||
|
public int32 Minor => _count >= 2 ? _parts[1] : InvalidVersion;
|
||||||
|
public int32 Patch => _count >= 3 ? _parts[2] : InvalidVersion;
|
||||||
|
|
||||||
|
public static RiderVersion Parse(StringView versionString)
|
||||||
|
{
|
||||||
|
RiderVersion version = .();
|
||||||
|
|
||||||
|
for (StringView part in versionString.Split('.'))
|
||||||
|
{
|
||||||
|
if (version._count >= MaxParts)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Note: the original uses FCString::Atoi, which yields 0 for garbage instead of failing.
|
||||||
|
int32 value = 0;
|
||||||
|
if (int32.Parse(part) case .Ok(let parsedValue))
|
||||||
|
value = parsedValue;
|
||||||
|
|
||||||
|
version._parts[version._count] = value;
|
||||||
|
version._count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two versions element by element.
|
||||||
|
/// @returns A negative value if lhs is older than rhs, a positive value if it is newer; otherwise 0.
|
||||||
|
public static int Compare(Self lhs, Self rhs)
|
||||||
|
{
|
||||||
|
int32 sharedCount = Math.Min(lhs._count, rhs._count);
|
||||||
|
|
||||||
|
for (int32 i < sharedCount)
|
||||||
|
{
|
||||||
|
if (lhs._parts[i] != rhs._parts[i])
|
||||||
|
return lhs._parts[i] < rhs._parts[i] ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal prefix: the shorter version is the older one, so that "5.0" is older than "5.0.1".
|
||||||
|
return lhs._count <=> rhs._count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator<(Self lhs, Self rhs) => Compare(lhs, rhs) < 0;
|
||||||
|
|
||||||
|
public static bool operator==(Self lhs, Self rhs) => Compare(lhs, rhs) == 0;
|
||||||
|
|
||||||
|
public static bool operator!=(Self lhs, Self rhs) => Compare(lhs, rhs) != 0;
|
||||||
|
|
||||||
|
public int GetHashCode()
|
||||||
|
{
|
||||||
|
int hash = 0;
|
||||||
|
|
||||||
|
for (int32 i < _count)
|
||||||
|
hash = (hash * 31) + (int)_parts[i];
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ToString(String strBuffer)
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
strBuffer.Append("<unknown>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32 i < _count)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
strBuffer.Append('.');
|
||||||
|
|
||||||
|
_parts[i].ToString(strBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How a Rider installation was set up.
|
||||||
|
enum RiderInstallType
|
||||||
|
{
|
||||||
|
/// Installed by the regular Rider installer.
|
||||||
|
Installed,
|
||||||
|
/// Installed through the JetBrains Toolbox.
|
||||||
|
Toolbox,
|
||||||
|
/// Provided by the user.
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single Rider installation found on this machine.
|
||||||
|
class RiderInstallInfo
|
||||||
|
{
|
||||||
|
/// The full path of the Rider executable (rider64.exe).
|
||||||
|
public String Path ~ delete _;
|
||||||
|
public RiderVersion Version;
|
||||||
|
public RiderInstallType InstallType;
|
||||||
|
|
||||||
|
public this(StringView path, RiderVersion version, RiderInstallType installType)
|
||||||
|
{
|
||||||
|
Path = new String(path);
|
||||||
|
Version = version;
|
||||||
|
InstallType = installType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locates the JetBrains Rider installations on this machine.
|
||||||
|
static class RiderPathLocator
|
||||||
|
{
|
||||||
|
/// Maximum directory depth we descend into while looking for the Rider executable.
|
||||||
|
/// The original search is unbounded, but it searches locations like "%LOCALAPPDATA%/Programs".
|
||||||
|
protected const int MaxSearchDepth = 8;
|
||||||
|
/// Maximum number of directories visited by a single recursive search.
|
||||||
|
protected const int MaxSearchedDirectories = 4096;
|
||||||
|
|
||||||
|
/// Creates the installation info for the given Rider executable.
|
||||||
|
/// @param riderExePath The path of a rider64.exe.
|
||||||
|
/// @returns The installation info; or .Err if the given path is not a usable Rider installation.
|
||||||
|
public static extern Result<RiderInstallInfo> GetInstallInfoFromRiderPath(StringView riderExePath, RiderInstallType installType);
|
||||||
|
|
||||||
|
/// Collects every Rider installation that can be found on this machine.
|
||||||
|
/// The entries are deduplicated by path and sorted, newest version first.
|
||||||
|
/// @param outInstalls Receives the installations. The caller takes ownership of the entries.
|
||||||
|
public static extern void CollectAllPaths(List<RiderInstallInfo> outInstalls);
|
||||||
|
|
||||||
|
/// Gets the file name of the Rider executable on this platform (e.g. "rider64.exe").
|
||||||
|
protected static extern StringView GetRiderExecutableName();
|
||||||
|
|
||||||
|
/// Gets the default location the Toolbox V2 installs IDEs into.
|
||||||
|
protected static extern Result<void> GetDefaultIdeInstallLocationForToolboxV2(String outPath);
|
||||||
|
|
||||||
|
/// Moves the located installations into outInstalls, dropping the ones that were found more
|
||||||
|
/// than once, and sorts them so that the newest installation comes first.
|
||||||
|
/// @param found The located installations. The entries are moved into outInstalls or deleted.
|
||||||
|
/// @param outInstalls Receives the installations. The caller takes ownership of the entries.
|
||||||
|
protected static void DeduplicateAndSort(List<RiderInstallInfo> found, List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
// The same installation usually shows up through more than one source.
|
||||||
|
for (int i < found.Count)
|
||||||
|
{
|
||||||
|
RiderInstallInfo install = found[i];
|
||||||
|
|
||||||
|
bool isDuplicate = false;
|
||||||
|
|
||||||
|
for (RiderInstallInfo existing in outInstalls)
|
||||||
|
{
|
||||||
|
if (IsSamePath(existing.Path, install.Path))
|
||||||
|
{
|
||||||
|
isDuplicate = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDuplicate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
outInstalls.Add(install);
|
||||||
|
// Ownership moved to outInstalls, make sure the caller's cleanup doesn't delete it.
|
||||||
|
found[i] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newest first, so that the caller can simply take the first entry.
|
||||||
|
outInstalls.Sort(scope (lhs, rhs) => RiderVersion.Compare(rhs.Version, lhs.Version));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the installation directory of Rider from the path of its executable.
|
||||||
|
/// The executable is expected to live in a "bin" directory, i.e. "<riderDirectory>/bin/rider64.exe".
|
||||||
|
protected static Result<void> GetRiderDirectory(StringView riderExePath, String outDirectory)
|
||||||
|
{
|
||||||
|
// The original matches the regex "(.*)(?:\\|/)bin" against the executable path,
|
||||||
|
// which is the same as taking the grandparent directory of a file inside "bin".
|
||||||
|
String binDirectory = scope .();
|
||||||
|
Try!(Path.GetDirectoryPath(riderExePath, binDirectory));
|
||||||
|
|
||||||
|
String binDirectoryName = scope .();
|
||||||
|
Path.GetFileName(binDirectory, binDirectoryName);
|
||||||
|
|
||||||
|
if (StringView.Compare(binDirectoryName, "bin", true) != 0)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
return Path.GetDirectoryPath(binDirectory, outDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares two paths, ignoring case and the difference between the directory separators.
|
||||||
|
protected static bool IsSamePath(StringView left, StringView right)
|
||||||
|
{
|
||||||
|
if (left.Length != right.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i < left.Length)
|
||||||
|
{
|
||||||
|
char8 leftChar = left[i];
|
||||||
|
char8 rightChar = right[i];
|
||||||
|
|
||||||
|
if (Path.IsDirectorySeparatorChar(leftChar) && Path.IsDirectorySeparatorChar(rightChar))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (leftChar.ToLower != rightChar.ToLower)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects the Rider installations managed by the JetBrains Toolbox at the given location.
|
||||||
|
protected static void CollectPathsFromToolbox(StringView toolboxPath, List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
if (toolboxPath.IsEmpty || !Directory.Exists(toolboxPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
String customInstallLocation = scope .();
|
||||||
|
ExtractPathFromSettingsJson(toolboxPath, customInstallLocation);
|
||||||
|
|
||||||
|
if (!customInstallLocation.IsEmpty)
|
||||||
|
{
|
||||||
|
// Toolbox V1 puts the IDEs into an "apps" subdirectory of the install location.
|
||||||
|
String appsPath = scope .();
|
||||||
|
Path.Combine(appsPath, customInstallLocation, "apps");
|
||||||
|
|
||||||
|
int countBefore = outInstalls.Count;
|
||||||
|
CollectPathsFromToolboxDirectory(appsPath, outInstalls);
|
||||||
|
|
||||||
|
if (outInstalls.Count != countBefore)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Toolbox V2 puts them directly into the install location.
|
||||||
|
CollectPathsFromToolboxDirectory(customInstallLocation, outInstalls);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbox V1 default install location.
|
||||||
|
String defaultAppsPath = scope .();
|
||||||
|
Path.Combine(defaultAppsPath, toolboxPath, "apps");
|
||||||
|
|
||||||
|
int countBefore = outInstalls.Count;
|
||||||
|
CollectPathsFromToolboxDirectory(defaultAppsPath, outInstalls);
|
||||||
|
|
||||||
|
if (outInstalls.Count != countBefore)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Toolbox V2 default install location.
|
||||||
|
String defaultInstallLocation = scope .();
|
||||||
|
if (GetDefaultIdeInstallLocationForToolboxV2(defaultInstallLocation) case .Ok)
|
||||||
|
CollectPathsFromToolboxDirectory(defaultInstallLocation, outInstalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Searches the given directory for Rider executables and creates the installation infos for them.
|
||||||
|
protected static void CollectPathsFromToolboxDirectory(StringView toolboxRiderRootPath, List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
if (toolboxRiderRootPath.IsEmpty || !Directory.Exists(toolboxRiderRootPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<String> riderPaths = scope .();
|
||||||
|
defer { ClearAndDeleteItems!(riderPaths); }
|
||||||
|
|
||||||
|
int searchedDirectories = 0;
|
||||||
|
FindFilesRecursive(toolboxRiderRootPath, GetRiderExecutableName(), riderPaths, MaxSearchDepth, ref searchedDirectories);
|
||||||
|
|
||||||
|
// Reused across the iterations, see the note in FindFilesRecursive.
|
||||||
|
String historyJsonPath = scope .();
|
||||||
|
|
||||||
|
for (String riderPath in riderPaths)
|
||||||
|
{
|
||||||
|
if (!(GetInstallInfoFromRiderPath(riderPath, .Toolbox) case .Ok(let installInfo)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The Toolbox leaves the directories of old versions behind after an update, so we
|
||||||
|
// check which build it actually considers the current one for this installation.
|
||||||
|
historyJsonPath.Clear();
|
||||||
|
|
||||||
|
if (GetHistoryJsonPath(riderPath, historyJsonPath) case .Ok)
|
||||||
|
{
|
||||||
|
RiderVersion lastBuildVersion = GetLastBuildVersion(historyJsonPath);
|
||||||
|
|
||||||
|
if (lastBuildVersion.IsInitialized && installInfo.Version != lastBuildVersion)
|
||||||
|
{
|
||||||
|
delete installInfo;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outInstalls.Add(installInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively searches the given directory for files with the given name.
|
||||||
|
/// @remarks corlib's Directory.EnumerateFiles doesn't search subdirectories.
|
||||||
|
protected static void FindFilesRecursive(StringView directory, StringView fileName, List<String> outPaths,
|
||||||
|
int remainingDepth, ref int searchedDirectories)
|
||||||
|
{
|
||||||
|
if (remainingDepth <= 0 || searchedDirectories >= MaxSearchedDirectories)
|
||||||
|
return;
|
||||||
|
|
||||||
|
searchedDirectories++;
|
||||||
|
|
||||||
|
for (FileFindEntry entry in Directory.EnumerateFiles(directory, fileName))
|
||||||
|
{
|
||||||
|
String path = new String();
|
||||||
|
entry.GetFilePath(path);
|
||||||
|
outPaths.Add(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reused across the iterations: a "scope" allocation inside a loop body lives until the
|
||||||
|
// enclosing method returns, so allocating per iteration would grow the stack unboundedly.
|
||||||
|
String entryName = scope .();
|
||||||
|
String subDirectory = scope .();
|
||||||
|
|
||||||
|
for (FileFindEntry entry in Directory.EnumerateDirectories(directory))
|
||||||
|
{
|
||||||
|
entryName.Clear();
|
||||||
|
entry.GetFileName(entryName);
|
||||||
|
|
||||||
|
if (entryName == "." || entryName == "..")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
subDirectory.Clear();
|
||||||
|
entry.GetFilePath(subDirectory);
|
||||||
|
|
||||||
|
FindFilesRecursive(subDirectory, fileName, outPaths, remainingDepth - 1, ref searchedDirectories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the custom install location out of the Toolbox' ".settings.json".
|
||||||
|
protected static void ExtractPathFromSettingsJson(StringView toolboxPath, String outInstallLocation)
|
||||||
|
{
|
||||||
|
String settingsJsonPath = scope .();
|
||||||
|
Path.Combine(settingsJsonPath, toolboxPath, ".settings.json");
|
||||||
|
|
||||||
|
if (!File.Exists(settingsJsonPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
StructuredData data = scope .();
|
||||||
|
if (data.Load(settingsJsonPath) case .Err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
data.GetString("install_location", outInstallLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the version of a Rider installation out of its "product-info.json".
|
||||||
|
protected static RiderVersion ParseProductInfoJson(StringView productInfoJsonPath)
|
||||||
|
{
|
||||||
|
StructuredData data = scope .();
|
||||||
|
if (data.Load(productInfoJsonPath) case .Err)
|
||||||
|
return .();
|
||||||
|
|
||||||
|
String buildNumber = scope .();
|
||||||
|
data.GetString("buildNumber", buildNumber);
|
||||||
|
|
||||||
|
return RiderVersion.Parse(buildNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walks up from the given Rider executable looking for the Toolbox' ".history.json".
|
||||||
|
protected static Result<void> GetHistoryJsonPath(StringView riderPath, String outPath)
|
||||||
|
{
|
||||||
|
String directory = scope .();
|
||||||
|
Try!(Path.GetDirectoryPath(riderPath, directory));
|
||||||
|
|
||||||
|
String parentDirectory = scope .();
|
||||||
|
String historyPath = scope .();
|
||||||
|
|
||||||
|
// Bounded so that we don't walk all the way up to the drive root.
|
||||||
|
for (int i < 10)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
break;
|
||||||
|
|
||||||
|
historyPath.Clear();
|
||||||
|
Path.Combine(historyPath, directory, ".history.json");
|
||||||
|
|
||||||
|
if (File.Exists(historyPath))
|
||||||
|
{
|
||||||
|
outPath.Append(historyPath);
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentDirectory.Clear();
|
||||||
|
if (Path.GetDirectoryPath(directory, parentDirectory) case .Err)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (parentDirectory.IsEmpty || parentDirectory == directory)
|
||||||
|
break;
|
||||||
|
|
||||||
|
directory.Set(parentDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads the build number of the most recent entry of the Toolbox' ".history.json".
|
||||||
|
protected static RiderVersion GetLastBuildVersion(StringView historyJsonPath)
|
||||||
|
{
|
||||||
|
StructuredData data = scope .();
|
||||||
|
if (data.Load(historyJsonPath) case .Err)
|
||||||
|
return .();
|
||||||
|
|
||||||
|
String lastBuild = scope .();
|
||||||
|
|
||||||
|
// The most recent build is the last entry of the "history" array.
|
||||||
|
for (data.Enumerate("history"))
|
||||||
|
{
|
||||||
|
lastBuild.Clear();
|
||||||
|
|
||||||
|
using (data.Open("item"))
|
||||||
|
{
|
||||||
|
data.GetString("build", lastBuild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RiderVersion.Parse(lastBuild);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
// Windows implementation of the RiderPathLocator, see RiderPathLocator.bf for the platform independent part and the license of the original code.
|
||||||
|
|
||||||
|
#if BF_PLATFORM_WINDOWS
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace GlitchyEditor.CodeEditors;
|
||||||
|
|
||||||
|
extension RiderPathLocator
|
||||||
|
{
|
||||||
|
private const String RiderExecutableName = "rider64.exe";
|
||||||
|
|
||||||
|
/// The maximum length of a registry key name.
|
||||||
|
private const int MaxRegistryKeyNameLength = 255;
|
||||||
|
|
||||||
|
[Import("advapi32.lib"), CLink, CallingConvention(.Stdcall)]
|
||||||
|
private static extern int32 RegEnumKeyExW(Windows.HKey hKey, uint32 dwIndex, char16* lpName,
|
||||||
|
uint32* lpcchName, uint32* lpReserved, char16* lpClass, uint32* lpcchClass,
|
||||||
|
void* lpftLastWriteTime);
|
||||||
|
|
||||||
|
protected static override StringView GetRiderExecutableName() => RiderExecutableName;
|
||||||
|
|
||||||
|
public static override Result<RiderInstallInfo> GetInstallInfoFromRiderPath(StringView riderExePath, RiderInstallType installType)
|
||||||
|
{
|
||||||
|
if (!File.Exists(riderExePath))
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
String riderDirectory = scope .();
|
||||||
|
Try!(GetRiderDirectory(riderExePath, riderDirectory));
|
||||||
|
|
||||||
|
RiderVersion version = .();
|
||||||
|
|
||||||
|
String productInfoPath = scope .();
|
||||||
|
Path.Combine(productInfoPath, riderDirectory, "product-info.json");
|
||||||
|
|
||||||
|
if (File.Exists(productInfoPath))
|
||||||
|
version = ParseProductInfoJson(productInfoPath);
|
||||||
|
|
||||||
|
if (!version.IsInitialized)
|
||||||
|
{
|
||||||
|
// Toolbox installations are laid out as "<...>/<build number>/bin/rider64.exe",
|
||||||
|
// so the name of the installation directory is the version.
|
||||||
|
String directoryName = scope .();
|
||||||
|
Path.GetFileName(riderDirectory, directoryName);
|
||||||
|
|
||||||
|
version = RiderVersion.Parse(directoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RiderInstallInfo(riderExePath, version, installType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static override void CollectAllPaths(List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
const String UninstallKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
|
||||||
|
const String UninstallKey32 = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
|
||||||
|
const String RiderKey = @"SOFTWARE\JetBrains\Rider";
|
||||||
|
const String RiderKey32 = @"SOFTWARE\WOW6432Node\JetBrains\Rider";
|
||||||
|
|
||||||
|
List<RiderInstallInfo> found = scope .();
|
||||||
|
// Everything that isn't moved into outInstalls (i.e. the duplicates) is deleted here.
|
||||||
|
defer { ClearAndDeleteItems!(found); }
|
||||||
|
|
||||||
|
// Regular installations register themselves in the uninstall list.
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, UninstallKey, "InstallLocation", "Rider", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, UninstallKey, "InstallLocation", "Rider", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, UninstallKey32, "InstallLocation", "Rider", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, UninstallKey32, "InstallLocation", "Rider", found);
|
||||||
|
|
||||||
|
// dotUltimate installations use their own key.
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, RiderKey, "InstallDir", "", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, RiderKey, "InstallDir", "", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_CURRENT_USER, RiderKey32, "InstallDir", "", found);
|
||||||
|
CollectPathsFromRegistry(Windows.HKEY_LOCAL_MACHINE, RiderKey32, "InstallDir", "", found);
|
||||||
|
|
||||||
|
// Toolbox installations aren't registered anywhere, we have to go looking for them.
|
||||||
|
String defaultToolboxPath = scope .();
|
||||||
|
if (GetToolboxPathFromEnvironment(defaultToolboxPath) case .Ok)
|
||||||
|
CollectPathsFromToolbox(defaultToolboxPath, found);
|
||||||
|
|
||||||
|
CollectPathsFromToolboxRegistry(Windows.HKEY_CURRENT_USER, @"Software\JetBrains\Toolbox\", found);
|
||||||
|
CollectPathsFromToolboxRegistry(Windows.HKEY_LOCAL_MACHINE, @"Software\JetBrains\Toolbox\", found);
|
||||||
|
CollectPathsFromToolboxRegistry(Windows.HKEY_CURRENT_USER, @"Software\JetBrains s.r.o.\JetBrainsToolbox\", found);
|
||||||
|
CollectPathsFromToolboxRegistry(Windows.HKEY_LOCAL_MACHINE, @"Software\JetBrains s.r.o.\JetBrainsToolbox\", found);
|
||||||
|
|
||||||
|
DeduplicateAndSort(found, outInstalls);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumerates the subkeys of the given registry key and creates an installation info for
|
||||||
|
/// every subkey that points at a Rider installation.
|
||||||
|
/// @param rootKey The registry hive to look in.
|
||||||
|
/// @param subKeyPath The key whose subkeys will be enumerated.
|
||||||
|
/// @param valueName The name of the value that holds the installation directory.
|
||||||
|
/// @param keyNameFilter If not empty, only subkeys whose name contains this string are looked at.
|
||||||
|
private static void CollectPathsFromRegistry(Windows.HKey rootKey, StringView subKeyPath,
|
||||||
|
StringView valueName, StringView keyNameFilter, List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
Windows.HKey key;
|
||||||
|
if (Windows.RegOpenKeyExW(rootKey, subKeyPath.ToScopedNativeWChar!(), 0, (.)Windows.KEY_READ, out key) != Windows.ERROR_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
defer Windows.RegCloseKey(key);
|
||||||
|
|
||||||
|
List<String> subKeyNames = scope .();
|
||||||
|
defer { ClearAndDeleteItems!(subKeyNames); }
|
||||||
|
|
||||||
|
if (EnumerateSubKeys(key, subKeyNames) case .Err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// All of these are reused across the iterations: a "scope" allocation inside a loop body
|
||||||
|
// lives until the enclosing method returns, and the uninstall key has hundreds of subkeys.
|
||||||
|
char16[MaxRegistryKeyNameLength + 1] subKeyNameBuffer = ?;
|
||||||
|
String installLocation = scope .();
|
||||||
|
String exePath = scope .();
|
||||||
|
|
||||||
|
for (String subKeyName in subKeyNames)
|
||||||
|
{
|
||||||
|
if (!keyNameFilter.IsEmpty && !subKeyName.Contains(keyNameFilter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int encodedLength = UTF16.GetEncodedLen(subKeyName);
|
||||||
|
if (encodedLength > MaxRegistryKeyNameLength)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
UTF16.Encode(subKeyName, &subKeyNameBuffer, encodedLength).IgnoreError();
|
||||||
|
subKeyNameBuffer[encodedLength] = 0;
|
||||||
|
|
||||||
|
Windows.HKey subKey;
|
||||||
|
if (Windows.RegOpenKeyExW(key, &subKeyNameBuffer, 0, (.)Windows.KEY_READ, out subKey) != Windows.ERROR_SUCCESS)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
installLocation.Clear();
|
||||||
|
Result<void> valueResult = subKey.GetValue(valueName, installLocation);
|
||||||
|
|
||||||
|
Windows.RegCloseKey(subKey);
|
||||||
|
|
||||||
|
if (valueResult case .Err)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (installLocation.IsWhiteSpace)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
exePath.Clear();
|
||||||
|
Path.Combine(exePath, installLocation, "bin", RiderExecutableName);
|
||||||
|
|
||||||
|
if (GetInstallInfoFromRiderPath(exePath, .Installed) case .Ok(let installInfo))
|
||||||
|
outInstalls.Add(installInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumerates the names of all subkeys of the given registry key.
|
||||||
|
/// @remarks Windows.HKey.EnumValue enumerates values (and is unimplemented), there is no
|
||||||
|
/// counterpart for subkeys in corlib.
|
||||||
|
private static Result<void> EnumerateSubKeys(Windows.HKey key, List<String> outNames)
|
||||||
|
{
|
||||||
|
// Reused across the iterations, see the note in CollectPathsFromRegistry.
|
||||||
|
char16[MaxRegistryKeyNameLength + 1] nameBuffer = ?;
|
||||||
|
|
||||||
|
for (uint32 index = 0; true; index++)
|
||||||
|
{
|
||||||
|
uint32 nameLength = nameBuffer.Count;
|
||||||
|
|
||||||
|
int32 result = RegEnumKeyExW(key, index, &nameBuffer, &nameLength, null, null, null, null);
|
||||||
|
|
||||||
|
if (result == Windows.ERROR_NO_MORE_ITEMS)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (result != Windows.ERROR_SUCCESS)
|
||||||
|
return .Err;
|
||||||
|
|
||||||
|
// RegEnumKeyExW null-terminates, but be explicit about it since we reuse the buffer.
|
||||||
|
nameBuffer[Math.Min((int)nameLength, MaxRegistryKeyNameLength)] = 0;
|
||||||
|
|
||||||
|
outNames.Add(new String(&nameBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the default location of the JetBrains Toolbox ("%LOCALAPPDATA%/JetBrains/Toolbox").
|
||||||
|
private static Result<void> GetToolboxPathFromEnvironment(String outPath)
|
||||||
|
{
|
||||||
|
String localAppData = scope .();
|
||||||
|
Try!(Environment.GetEnvironmentVariable("LOCALAPPDATA", localAppData));
|
||||||
|
|
||||||
|
Path.Combine(outPath, localAppData, "JetBrains", "Toolbox");
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static override Result<void> GetDefaultIdeInstallLocationForToolboxV2(String outPath)
|
||||||
|
{
|
||||||
|
String localAppData = scope .();
|
||||||
|
Try!(Environment.GetEnvironmentVariable("LOCALAPPDATA", localAppData));
|
||||||
|
|
||||||
|
Path.Combine(outPath, localAppData, "Programs");
|
||||||
|
|
||||||
|
return .Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Looks up the location of the JetBrains Toolbox in the registry and collects the Rider
|
||||||
|
/// installations it manages.
|
||||||
|
private static void CollectPathsFromToolboxRegistry(Windows.HKey rootKey, StringView subKeyPath, List<RiderInstallInfo> outInstalls)
|
||||||
|
{
|
||||||
|
Windows.HKey key;
|
||||||
|
if (Windows.RegOpenKeyExW(rootKey, subKeyPath.ToScopedNativeWChar!(), 0, (.)Windows.KEY_READ, out key) != Windows.ERROR_SUCCESS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
defer Windows.RegCloseKey(key);
|
||||||
|
|
||||||
|
// The default value of the key contains the path of the Toolbox' "bin" directory.
|
||||||
|
String toolboxBinPath = scope .();
|
||||||
|
if (key.GetValue("", toolboxBinPath) case .Err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (toolboxBinPath.IsWhiteSpace)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String binDirectoryName = scope .();
|
||||||
|
Path.GetFileName(toolboxBinPath, binDirectoryName);
|
||||||
|
|
||||||
|
if (StringView.Compare(binDirectoryName, "bin", true) != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
String toolboxPath = scope .();
|
||||||
|
if (Path.GetDirectoryPath(toolboxBinPath, toolboxPath) case .Err)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CollectPathsFromToolbox(toolboxPath, outInstalls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -604,7 +604,13 @@ namespace GlitchyEditor.EditWindows
|
|||||||
|
|
||||||
if (ImGui.MenuItem("Open C# Project..."))
|
if (ImGui.MenuItem("Open C# Project..."))
|
||||||
{
|
{
|
||||||
RiderIdeAdapter.OpenScriptProject();
|
switch (Application.Instance.Settings.ScriptSettings.SelectedIde)
|
||||||
|
{
|
||||||
|
case .VisualStudio:
|
||||||
|
VisualStudioIdeAdapter.OpenScriptProject();
|
||||||
|
case .Rider:
|
||||||
|
RiderIdeAdapter.OpenScriptProject();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui.AttachTooltip("Opens the C# Solution of this project.");
|
ImGui.AttachTooltip("Opens the C# Solution of this project.");
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System;
|
|||||||
using GlitchyEngine;
|
using GlitchyEngine;
|
||||||
using Bon;
|
using Bon;
|
||||||
using GlitchyEditor;
|
using GlitchyEditor;
|
||||||
|
using GlitchyEditor.CodeEditors;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -69,6 +70,7 @@ class ScriptSettings
|
|||||||
public ScriptIde SelectedIde;
|
public ScriptIde SelectedIde;
|
||||||
|
|
||||||
private bool _lookedForVs = false;
|
private bool _lookedForVs = false;
|
||||||
|
private bool _lookedForRider = false;
|
||||||
|
|
||||||
public void Apply()
|
public void Apply()
|
||||||
{
|
{
|
||||||
@@ -78,6 +80,11 @@ class ScriptSettings
|
|||||||
FindVisualStudio();
|
FindVisualStudio();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!_lookedForRider && String.IsNullOrWhiteSpace(RiderPath))
|
||||||
|
{
|
||||||
|
FindRider();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FindVisualStudio()
|
void FindVisualStudio()
|
||||||
@@ -154,6 +161,63 @@ class ScriptSettings
|
|||||||
Log.EngineLogger.Error("Couldn't automatically determine location of Visual Studio: Failed to launch vswhere.exe");
|
Log.EngineLogger.Error("Couldn't automatically determine location of Visual Studio: Failed to launch vswhere.exe");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FindRider()
|
||||||
|
{
|
||||||
|
_lookedForRider = true;
|
||||||
|
|
||||||
|
Thread thread = new Thread(new => FindRiderImpl);
|
||||||
|
thread.AutoDelete = true;
|
||||||
|
thread.IsBackground = true;
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FindRiderImpl()
|
||||||
|
{
|
||||||
|
List<RiderInstallInfo> installs = new .();
|
||||||
|
|
||||||
|
RiderPathLocator.CollectAllPaths(installs);
|
||||||
|
|
||||||
|
// Don't touch the settings or the log from this thread: the editor's log window appends to
|
||||||
|
// a plain List and isn't synchronized, so logging from here corrupts it.
|
||||||
|
Application.Instance.InvokeOnMainThread(new () => ReportRiderInstalls(installs));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Logs the located Rider installations and stores the newest one in RiderPath.
|
||||||
|
/// @param installs The located installations, newest first. Takes ownership.
|
||||||
|
bool ReportRiderInstalls(List<RiderInstallInfo> installs)
|
||||||
|
{
|
||||||
|
defer { DeleteContainerAndItems!(installs); }
|
||||||
|
|
||||||
|
for (RiderInstallInfo install in installs)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Info($"Found Rider {install.Version} at \"{install.Path}\" ({install.InstallType})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installs.IsEmpty)
|
||||||
|
{
|
||||||
|
Log.EngineLogger.Warning("Couldn't automatically determine location of Rider: No installation found.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CollectAllPaths sorts the installations, newest first.
|
||||||
|
RiderInstallInfo newestInstall = installs.Front;
|
||||||
|
|
||||||
|
if (RiderPath == null)
|
||||||
|
{
|
||||||
|
RiderPath = new String(newestInstall.Path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RiderPath..Clear().Append(newestInstall.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.EngineLogger.Info($"Located Rider: {RiderPath}");
|
||||||
|
|
||||||
|
Apply();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Reflect]
|
[Reflect]
|
||||||
|
|||||||
@@ -299,24 +299,62 @@ namespace GlitchyEditor
|
|||||||
|
|
||||||
_settingsChanged = true;
|
_settingsChanged = true;
|
||||||
}
|
}
|
||||||
|
case when fieldType.IsEnum:
|
||||||
|
|
||||||
|
int64 underlyingValue = 0;
|
||||||
|
|
||||||
/*if (setting.EditorType == .FilePath)
|
// Read the enum fields value from the settings-object
|
||||||
|
var enumValueResult = fieldInfo.GetValueReference(setting.SettingsObject);
|
||||||
|
|
||||||
|
if (enumValueResult not case .Ok(var enumValueReference))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Get underlying integer value from field reference
|
||||||
|
switch (fieldInfo.FieldType.Size)
|
||||||
{
|
{
|
||||||
ImGui.SameLine();
|
case 1: underlyingValue = *(int8*)enumValueReference.DataPtr;
|
||||||
|
case 2: underlyingValue = *(int16*)enumValueReference.DataPtr;
|
||||||
|
case 4: underlyingValue = *(int32*)enumValueReference.DataPtr;
|
||||||
|
case 8: underlyingValue = *(int64*)enumValueReference.DataPtr;
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.Button("..."))
|
// TODO: handle multiple Flags (as a separate enum type handler though!)
|
||||||
|
|
||||||
|
StringView currentName = "";
|
||||||
|
|
||||||
|
for (let (name, value) in Enum.GetEnumerator(fieldInfo.FieldType))
|
||||||
|
{
|
||||||
|
if (value == underlyingValue)
|
||||||
{
|
{
|
||||||
OpenFileDialog ofd = scope .();
|
currentName = name;
|
||||||
ofd.InitialDirectory = value;
|
break;
|
||||||
ofd.SetFilter("All Files (*.*)|*.*");
|
}
|
||||||
ofd.Multiselect = false;
|
}
|
||||||
|
|
||||||
if (ofd.ShowDialog() case .Ok(let result) && result == .OK)
|
if (ImGui.BeginCombo(scope $"##{setting.Name}", currentName.ToScopeCStr!()))
|
||||||
|
{
|
||||||
|
for (let (name, value) in Enum.GetEnumerator(fieldInfo.FieldType))
|
||||||
|
{
|
||||||
|
bool isSelected = (name == currentName);
|
||||||
|
|
||||||
|
if (ImGui.Selectable(name.ToScopeCStr!(), isSelected))
|
||||||
{
|
{
|
||||||
SetSettingValue!(new String(ofd.FileNames[0]));
|
// Set value in field reference
|
||||||
|
switch (fieldInfo.FieldType.Size)
|
||||||
|
{
|
||||||
|
case 1: *(int8*)enumValueReference.DataPtr = (int8)value;
|
||||||
|
case 2: *(int16*)enumValueReference.DataPtr = (int16)value;
|
||||||
|
case 4: *(int32*)enumValueReference.DataPtr = (int32)value;
|
||||||
|
case 8: *(int64*)enumValueReference.DataPtr = (int64)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSelected)
|
||||||
|
_settingsChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setting.Name == _SettingToHighlight)
|
if (setting.Name == _SettingToHighlight)
|
||||||
|
|||||||
Reference in New Issue
Block a user