feat: add editor project switching workflow
This commit is contained in:
165
editor/src/Core/ProjectRootResolver.h
Normal file
165
editor/src/Core/ProjectRootResolver.h
Normal file
@@ -0,0 +1,165 @@
|
||||
#pragma once
|
||||
|
||||
#include "Platform/Win32Utf8.h"
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline fs::path NormalizePath(const fs::path& path, const fs::path& basePath = {}) {
|
||||
if (path.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
fs::path absolutePath = path;
|
||||
std::error_code ec;
|
||||
if (absolutePath.is_relative()) {
|
||||
absolutePath = basePath.empty()
|
||||
? fs::absolute(absolutePath, ec)
|
||||
: fs::absolute(basePath / absolutePath, ec);
|
||||
if (ec) {
|
||||
absolutePath = basePath.empty() ? absolutePath : (basePath / path);
|
||||
}
|
||||
}
|
||||
|
||||
ec.clear();
|
||||
const fs::path canonicalPath = fs::weakly_canonical(absolutePath, ec);
|
||||
if (!ec) {
|
||||
return canonicalPath.lexically_normal();
|
||||
}
|
||||
|
||||
return absolutePath.lexically_normal();
|
||||
}
|
||||
|
||||
inline bool IsWorkspaceRoot(const fs::path& candidate) {
|
||||
std::error_code ec;
|
||||
return fs::exists(candidate / L"CMakeLists.txt", ec) &&
|
||||
fs::is_directory(candidate / L"editor", ec) &&
|
||||
fs::is_directory(candidate / L"engine", ec);
|
||||
}
|
||||
|
||||
inline bool IsEditorProjectRoot(const fs::path& candidate) {
|
||||
std::error_code ec;
|
||||
return fs::exists(candidate / L"Project.xcproject", ec) ||
|
||||
fs::is_directory(candidate / L"Assets", ec);
|
||||
}
|
||||
|
||||
inline fs::path ResolveWorkspaceDefaultProjectRoot(const fs::path& workspaceRoot) {
|
||||
if (workspaceRoot.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const fs::path preferredProjectRoot = workspaceRoot / L"project";
|
||||
if (IsEditorProjectRoot(preferredProjectRoot)) {
|
||||
return NormalizePath(preferredProjectRoot);
|
||||
}
|
||||
|
||||
if (IsEditorProjectRoot(workspaceRoot)) {
|
||||
return NormalizePath(workspaceRoot);
|
||||
}
|
||||
|
||||
return NormalizePath(preferredProjectRoot);
|
||||
}
|
||||
|
||||
inline std::optional<fs::path> FindWorkspaceRoot(fs::path start) {
|
||||
if (start.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
start = NormalizePath(start);
|
||||
for (fs::path current = start; !current.empty(); ) {
|
||||
if (IsWorkspaceRoot(current)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const fs::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
inline std::optional<fs::path> ParseCommandLineProjectOverride(const fs::path& workingDirectory) {
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (!argv || argc <= 1) {
|
||||
if (argv) {
|
||||
LocalFree(argv);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<fs::path> projectOverride;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
const std::wstring arg = argv[i] ? argv[i] : L"";
|
||||
if ((arg == L"--project" || arg == L"-p") && i + 1 < argc) {
|
||||
projectOverride = NormalizePath(fs::path(argv[++i]), workingDirectory);
|
||||
break;
|
||||
}
|
||||
|
||||
static const std::wstring kProjectArgPrefix = L"--project=";
|
||||
if (arg.rfind(kProjectArgPrefix, 0) == 0) {
|
||||
projectOverride = NormalizePath(fs::path(arg.substr(kProjectArgPrefix.size())), workingDirectory);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LocalFree(argv);
|
||||
return projectOverride;
|
||||
}
|
||||
|
||||
inline std::string PathToUtf8(const fs::path& path) {
|
||||
return Platform::WideToUtf8(path.wstring());
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline std::string ResolveEditorProjectRootUtf8() {
|
||||
std::error_code ec;
|
||||
const fs::path workingDirectory = detail::NormalizePath(fs::current_path(ec));
|
||||
const fs::path executableDirectory = detail::NormalizePath(fs::path(Platform::Utf8ToWide(Platform::GetExecutableDirectoryUtf8())));
|
||||
|
||||
if (const auto projectOverride = detail::ParseCommandLineProjectOverride(workingDirectory.empty() ? executableDirectory : workingDirectory)) {
|
||||
return detail::PathToUtf8(*projectOverride);
|
||||
}
|
||||
|
||||
if (const auto workspaceRoot = detail::FindWorkspaceRoot(workingDirectory)) {
|
||||
return detail::PathToUtf8(detail::ResolveWorkspaceDefaultProjectRoot(*workspaceRoot));
|
||||
}
|
||||
|
||||
if (const auto workspaceRoot = detail::FindWorkspaceRoot(executableDirectory)) {
|
||||
return detail::PathToUtf8(detail::ResolveWorkspaceDefaultProjectRoot(*workspaceRoot));
|
||||
}
|
||||
|
||||
if (!workingDirectory.empty()) {
|
||||
return detail::PathToUtf8(workingDirectory);
|
||||
}
|
||||
|
||||
return detail::PathToUtf8(executableDirectory);
|
||||
}
|
||||
|
||||
inline bool SetEditorWorkingDirectory(const std::string& projectRootUtf8) {
|
||||
if (projectRootUtf8.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
fs::current_path(fs::path(Platform::Utf8ToWide(projectRootUtf8)), ec);
|
||||
return !ec;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user