Files
Gitree/src/managers/application_manager.cpp
2026-06-18 23:46:08 -05:00

552 lines
24 KiB
C++

#include "managers/application_manager.h"
#include <izo/Environment.hpp>
#include <izo/Paths.hpp>
#include <izo/Process.hpp>
#include <algorithm>
namespace
{
std::filesystem::path environmentPath(const char *name)
{
const auto value = izo::GetEnvVar(name);
return value && !value->empty() ? std::filesystem::path(*value) : std::filesystem::path{};
}
std::filesystem::path under(const std::filesystem::path &root, const char *relative)
{
return root.empty() ? std::filesystem::path{} : root / relative;
}
std::filesystem::path firstExisting(std::initializer_list<std::filesystem::path> candidates)
{
std::error_code error;
for (const auto &candidate : candidates)
{
if (!candidate.empty() && std::filesystem::is_regular_file(candidate, error))
return candidate;
}
return {};
}
std::filesystem::path findExecutable(const std::filesystem::path &root, const char *filename)
{
std::error_code error;
if (root.empty() || !std::filesystem::is_directory(root, error))
return {};
for (std::filesystem::recursive_directory_iterator iterator(
root, std::filesystem::directory_options::skip_permission_denied, error),
end;
iterator != end && !error; iterator.increment(error))
{
if (iterator->is_regular_file(error) && iterator->path().filename() == filename)
return iterator->path();
}
return {};
}
}
ApplicationManager::ApplicationManager()
{
const auto local = izo::GetKnownPath(izo::KnownPath::LocalAppData);
const auto roaming = izo::GetKnownPath(izo::KnownPath::AppData);
const auto program_files = environmentPath("ProgramFiles");
const auto program_files_x86 = environmentPath("ProgramFiles(x86)");
const auto program_data = environmentPath("ProgramData");
const auto user_profile = environmentPath("USERPROFILE");
const auto windows = environmentPath("WINDIR");
const auto chocolatey = environmentPath("ChocolateyInstall");
auto add = [this](ExternalApplicationId id, const char *name, std::filesystem::path executable,
std::vector<std::string> arguments = {})
{
const bool available = !executable.empty();
targets_.push_back({{id, name, available, std::move(executable)}, std::move(arguments)});
applications_.push_back(targets_.back().info);
};
// -------------------------------------------------------------------------
// VS Code-style editors
// -------------------------------------------------------------------------
add(ExternalApplicationId::visual_studio_code, "VS Code", firstExisting({
under(local, "Programs/Microsoft VS Code/Code.exe"),
under(program_files, "Microsoft VS Code/Code.exe"),
under(program_files_x86, "Microsoft VS Code/Code.exe"),
under(user_profile, "scoop/apps/vscode/current/Code.exe"),
under(chocolatey, "lib/vscode/tools/Code.exe"),
}));
add(ExternalApplicationId::visual_studio_code_insiders, "VS Code Insiders", firstExisting({
under(local, "Programs/Microsoft VS Code Insiders/Code - Insiders.exe"),
under(program_files, "Microsoft VS Code Insiders/Code - Insiders.exe"),
under(user_profile, "scoop/apps/vscode-insiders/current/Code - Insiders.exe"),
}));
add(ExternalApplicationId::vscodium, "VSCodium", firstExisting({
under(local, "Programs/VSCodium/VSCodium.exe"),
under(program_files, "VSCodium/VSCodium.exe"),
under(program_files_x86, "VSCodium/VSCodium.exe"),
under(user_profile, "scoop/apps/vscodium/current/VSCodium.exe"),
under(chocolatey, "lib/vscodium/tools/VSCodium.exe"),
}));
add(ExternalApplicationId::cursor, "Cursor", firstExisting({
under(local, "Programs/cursor/Cursor.exe"),
under(local, "Programs/Cursor/Cursor.exe"),
under(program_files, "Cursor/Cursor.exe"),
under(user_profile, "scoop/apps/cursor/current/Cursor.exe"),
}));
add(ExternalApplicationId::windsurf, "Windsurf", firstExisting({
under(local, "Programs/Windsurf/Windsurf.exe"),
under(program_files, "Windsurf/Windsurf.exe"),
under(user_profile, "scoop/apps/windsurf/current/Windsurf.exe"),
}));
add(ExternalApplicationId::trae, "Trae", firstExisting({
under(local, "Programs/Trae/Trae.exe"),
under(program_files, "Trae/Trae.exe"),
}));
add(ExternalApplicationId::zed, "Zed", firstExisting({
under(local, "Programs/Zed/Zed.exe"),
under(program_files, "Zed/Zed.exe"),
under(user_profile, "scoop/apps/zed/current/Zed.exe"),
}));
add(ExternalApplicationId::antigravity, "Antigravity", firstExisting({
under(local, "Programs/Antigravity/Antigravity.exe"),
under(local, "Antigravity/Antigravity.exe"),
under(program_files, "Antigravity/Antigravity.exe"),
}));
// -------------------------------------------------------------------------
// Microsoft IDEs
// -------------------------------------------------------------------------
add(ExternalApplicationId::visual_studio, "Visual Studio", firstExisting({
under(program_files, "Microsoft Visual Studio/2022/Community/Common7/IDE/devenv.exe"),
under(program_files, "Microsoft Visual Studio/2022/Professional/Common7/IDE/devenv.exe"),
under(program_files, "Microsoft Visual Studio/2022/Enterprise/Common7/IDE/devenv.exe"),
under(program_files, "Microsoft Visual Studio/2022/Preview/Common7/IDE/devenv.exe"),
under(program_files_x86, "Microsoft Visual Studio/2019/Community/Common7/IDE/devenv.exe"),
under(program_files_x86, "Microsoft Visual Studio/2019/Professional/Common7/IDE/devenv.exe"),
under(program_files_x86, "Microsoft Visual Studio/2019/Enterprise/Common7/IDE/devenv.exe"),
under(program_files_x86, "Microsoft Visual Studio/2019/Preview/Common7/IDE/devenv.exe"),
}));
// -------------------------------------------------------------------------
// GitHub / Windows tools
// -------------------------------------------------------------------------
std::filesystem::path github_desktop = firstExisting({
under(local, "GitHubDesktop/GitHubDesktop.exe"),
under(local, "GitHub Desktop/GitHubDesktop.exe"),
});
if (github_desktop.empty())
github_desktop = findExecutable(under(local, "GitHubDesktop"), "GitHubDesktop.exe");
add(ExternalApplicationId::github_desktop, "GitHub Desktop", std::move(github_desktop));
add(ExternalApplicationId::file_explorer, "File Explorer", firstExisting({
under(windows, "explorer.exe"),
}));
add(ExternalApplicationId::terminal, "Terminal", firstExisting({
under(local, "Microsoft/WindowsApps/wt.exe"),
under(windows, "System32/wt.exe"),
}), {"-d"});
add(ExternalApplicationId::git_bash, "Git Bash", firstExisting({
under(program_files, "Git/git-bash.exe"),
under(program_files_x86, "Git/git-bash.exe"),
under(user_profile, "scoop/apps/git/current/git-bash.exe"),
}), {"--cd="});
add(ExternalApplicationId::wsl, "WSL", firstExisting({
under(windows, "System32/wsl.exe"),
}), {"--cd"});
// -------------------------------------------------------------------------
// Android
// -------------------------------------------------------------------------
add(ExternalApplicationId::android_studio, "Android Studio", firstExisting({
under(program_files, "Android/Android Studio/bin/studio64.exe"),
under(local, "Programs/Android Studio/bin/studio64.exe"),
under(user_profile, "scoop/apps/android-studio/current/bin/studio64.exe"),
}));
// -------------------------------------------------------------------------
// JetBrains IDEs
// -------------------------------------------------------------------------
std::filesystem::path idea = firstExisting({
under(program_files, "JetBrains/IntelliJ IDEA 2026.1/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA 2025.3/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA 2025.2/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA 2025.1/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA 2024.3/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA Community Edition 2026.1/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA Community Edition 2025.3/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA Community Edition 2025.2/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA Community Edition 2025.1/bin/idea64.exe"),
under(program_files, "JetBrains/IntelliJ IDEA Community Edition 2024.3/bin/idea64.exe"),
under(local, "Programs/IntelliJ IDEA Ultimate/bin/idea64.exe"),
under(local, "Programs/IntelliJ IDEA Community/bin/idea64.exe"),
});
if (idea.empty())
idea = findExecutable(under(local, "JetBrains/Toolbox/apps"), "idea64.exe");
add(ExternalApplicationId::intellij_idea, "IntelliJ IDEA", std::move(idea));
std::filesystem::path clion = firstExisting({
under(program_files, "JetBrains/CLion 2026.1/bin/clion64.exe"),
under(program_files, "JetBrains/CLion 2025.3/bin/clion64.exe"),
under(program_files, "JetBrains/CLion 2025.2/bin/clion64.exe"),
under(program_files, "JetBrains/CLion 2025.1/bin/clion64.exe"),
under(program_files, "JetBrains/CLion 2024.3/bin/clion64.exe"),
under(local, "Programs/CLion/bin/clion64.exe"),
});
if (clion.empty())
clion = findExecutable(under(local, "JetBrains/Toolbox/apps"), "clion64.exe");
add(ExternalApplicationId::clion, "CLion", std::move(clion));
std::filesystem::path rider = firstExisting({
under(program_files, "JetBrains/JetBrains Rider 2026.1/bin/rider64.exe"),
under(program_files, "JetBrains/JetBrains Rider 2025.3/bin/rider64.exe"),
under(program_files, "JetBrains/JetBrains Rider 2025.2/bin/rider64.exe"),
under(program_files, "JetBrains/JetBrains Rider 2025.1/bin/rider64.exe"),
under(program_files, "JetBrains/JetBrains Rider 2024.3/bin/rider64.exe"),
under(local, "Programs/Rider/bin/rider64.exe"),
});
if (rider.empty())
rider = findExecutable(under(local, "JetBrains/Toolbox/apps"), "rider64.exe");
add(ExternalApplicationId::rider, "Rider", std::move(rider));
std::filesystem::path pycharm = firstExisting({
under(program_files, "JetBrains/PyCharm 2026.1/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm 2025.3/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm 2025.2/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm 2025.1/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm 2024.3/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm Community Edition 2026.1/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm Community Edition 2025.3/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm Community Edition 2025.2/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm Community Edition 2025.1/bin/pycharm64.exe"),
under(program_files, "JetBrains/PyCharm Community Edition 2024.3/bin/pycharm64.exe"),
under(local, "Programs/PyCharm Professional/bin/pycharm64.exe"),
under(local, "Programs/PyCharm Community/bin/pycharm64.exe"),
});
if (pycharm.empty())
pycharm = findExecutable(under(local, "JetBrains/Toolbox/apps"), "pycharm64.exe");
add(ExternalApplicationId::pycharm, "PyCharm", std::move(pycharm));
std::filesystem::path webstorm = firstExisting({
under(program_files, "JetBrains/WebStorm 2026.1/bin/webstorm64.exe"),
under(program_files, "JetBrains/WebStorm 2025.3/bin/webstorm64.exe"),
under(program_files, "JetBrains/WebStorm 2025.2/bin/webstorm64.exe"),
under(program_files, "JetBrains/WebStorm 2025.1/bin/webstorm64.exe"),
under(program_files, "JetBrains/WebStorm 2024.3/bin/webstorm64.exe"),
under(local, "Programs/WebStorm/bin/webstorm64.exe"),
});
if (webstorm.empty())
webstorm = findExecutable(under(local, "JetBrains/Toolbox/apps"), "webstorm64.exe");
add(ExternalApplicationId::webstorm, "WebStorm", std::move(webstorm));
std::filesystem::path phpstorm = firstExisting({
under(program_files, "JetBrains/PhpStorm 2026.1/bin/phpstorm64.exe"),
under(program_files, "JetBrains/PhpStorm 2025.3/bin/phpstorm64.exe"),
under(program_files, "JetBrains/PhpStorm 2025.2/bin/phpstorm64.exe"),
under(program_files, "JetBrains/PhpStorm 2025.1/bin/phpstorm64.exe"),
under(program_files, "JetBrains/PhpStorm 2024.3/bin/phpstorm64.exe"),
under(local, "Programs/PhpStorm/bin/phpstorm64.exe"),
});
if (phpstorm.empty())
phpstorm = findExecutable(under(local, "JetBrains/Toolbox/apps"), "phpstorm64.exe");
add(ExternalApplicationId::phpstorm, "PhpStorm", std::move(phpstorm));
std::filesystem::path rubymine = firstExisting({
under(program_files, "JetBrains/RubyMine 2026.1/bin/rubymine64.exe"),
under(program_files, "JetBrains/RubyMine 2025.3/bin/rubymine64.exe"),
under(program_files, "JetBrains/RubyMine 2025.2/bin/rubymine64.exe"),
under(program_files, "JetBrains/RubyMine 2025.1/bin/rubymine64.exe"),
under(program_files, "JetBrains/RubyMine 2024.3/bin/rubymine64.exe"),
under(local, "Programs/RubyMine/bin/rubymine64.exe"),
});
if (rubymine.empty())
rubymine = findExecutable(under(local, "JetBrains/Toolbox/apps"), "rubymine64.exe");
add(ExternalApplicationId::rubymine, "RubyMine", std::move(rubymine));
std::filesystem::path goland = firstExisting({
under(program_files, "JetBrains/GoLand 2026.1/bin/goland64.exe"),
under(program_files, "JetBrains/GoLand 2025.3/bin/goland64.exe"),
under(program_files, "JetBrains/GoLand 2025.2/bin/goland64.exe"),
under(program_files, "JetBrains/GoLand 2025.1/bin/goland64.exe"),
under(program_files, "JetBrains/GoLand 2024.3/bin/goland64.exe"),
under(local, "Programs/GoLand/bin/goland64.exe"),
});
if (goland.empty())
goland = findExecutable(under(local, "JetBrains/Toolbox/apps"), "goland64.exe");
add(ExternalApplicationId::goland, "GoLand", std::move(goland));
std::filesystem::path datagrip = firstExisting({
under(program_files, "JetBrains/DataGrip 2026.1/bin/datagrip64.exe"),
under(program_files, "JetBrains/DataGrip 2025.3/bin/datagrip64.exe"),
under(program_files, "JetBrains/DataGrip 2025.2/bin/datagrip64.exe"),
under(program_files, "JetBrains/DataGrip 2025.1/bin/datagrip64.exe"),
under(program_files, "JetBrains/DataGrip 2024.3/bin/datagrip64.exe"),
under(local, "Programs/DataGrip/bin/datagrip64.exe"),
});
if (datagrip.empty())
datagrip = findExecutable(under(local, "JetBrains/Toolbox/apps"), "datagrip64.exe");
add(ExternalApplicationId::datagrip, "DataGrip", std::move(datagrip));
add(ExternalApplicationId::jetbrains_fleet, "JetBrains Fleet", firstExisting({
under(local, "Programs/Fleet/Fleet.exe"),
under(local, "JetBrains/Fleet/bin/Fleet.exe"),
under(program_files, "JetBrains/Fleet/Fleet.exe"),
}));
// -------------------------------------------------------------------------
// Lightweight / general editors
// -------------------------------------------------------------------------
add(ExternalApplicationId::sublime_text, "Sublime Text", firstExisting({
under(program_files, "Sublime Text/sublime_text.exe"),
under(program_files, "Sublime Text 3/sublime_text.exe"),
under(program_files_x86, "Sublime Text/sublime_text.exe"),
under(local, "Programs/Sublime Text/sublime_text.exe"),
under(user_profile, "scoop/apps/sublime-text/current/sublime_text.exe"),
}));
add(ExternalApplicationId::notepad_plus_plus, "Notepad++", firstExisting({
under(program_files, "Notepad++/notepad++.exe"),
under(program_files_x86, "Notepad++/notepad++.exe"),
under(user_profile, "scoop/apps/notepadplusplus/current/notepad++.exe"),
under(chocolatey, "lib/notepadplusplus/tools/notepad++.exe"),
}));
add(ExternalApplicationId::atom, "Atom", firstExisting({
under(local, "atom/atom.exe"),
under(local, "Programs/Atom/atom.exe"),
under(program_files, "Atom/atom.exe"),
}));
add(ExternalApplicationId::pulsar, "Pulsar", firstExisting({
under(local, "Programs/Pulsar/Pulsar.exe"),
under(program_files, "Pulsar/Pulsar.exe"),
}));
add(ExternalApplicationId::brackets, "Brackets", firstExisting({
under(program_files, "Brackets/Brackets.exe"),
under(program_files_x86, "Brackets/Brackets.exe"),
}));
add(ExternalApplicationId::lapce, "Lapce", firstExisting({
under(local, "Programs/Lapce/Lapce.exe"),
under(program_files, "Lapce/Lapce.exe"),
under(user_profile, "scoop/apps/lapce/current/lapce.exe"),
}));
add(ExternalApplicationId::lite_xl, "Lite XL", firstExisting({
under(program_files, "Lite XL/lite-xl.exe"),
under(program_files_x86, "Lite XL/lite-xl.exe"),
under(user_profile, "scoop/apps/lite-xl/current/lite-xl.exe"),
}));
add(ExternalApplicationId::geany, "Geany", firstExisting({
under(program_files, "Geany/bin/geany.exe"),
under(program_files_x86, "Geany/bin/geany.exe"),
under(user_profile, "scoop/apps/geany/current/bin/geany.exe"),
}));
add(ExternalApplicationId::kate, "Kate", firstExisting({
under(program_files, "Kate/bin/kate.exe"),
under(program_files_x86, "Kate/bin/kate.exe"),
under(user_profile, "scoop/apps/kate/current/bin/kate.exe"),
}));
// -------------------------------------------------------------------------
// C/C++ / Java / general IDEs
// -------------------------------------------------------------------------
add(ExternalApplicationId::qt_creator, "Qt Creator", firstExisting({
under(program_files, "Qt/Tools/QtCreator/bin/qtcreator.exe"),
under(program_files_x86, "Qt/Tools/QtCreator/bin/qtcreator.exe"),
under(user_profile, "scoop/apps/qt-creator/current/bin/qtcreator.exe"),
}));
add(ExternalApplicationId::codeblocks, "Code::Blocks", firstExisting({
under(program_files, "CodeBlocks/codeblocks.exe"),
under(program_files_x86, "CodeBlocks/codeblocks.exe"),
}));
add(ExternalApplicationId::dev_cpp, "Dev-C++", firstExisting({
under(program_files, "Embarcadero/Dev-Cpp/devcpp.exe"),
under(program_files_x86, "Embarcadero/Dev-Cpp/devcpp.exe"),
under(program_files, "Dev-Cpp/devcpp.exe"),
under(program_files_x86, "Dev-Cpp/devcpp.exe"),
}));
add(ExternalApplicationId::eclipse, "Eclipse", firstExisting({
under(user_profile, "eclipse/java-latest-released/eclipse/eclipse.exe"),
under(program_files, "Eclipse Foundation/eclipse/eclipse.exe"),
under(program_files, "Eclipse/eclipse.exe"),
under(local, "Programs/Eclipse/eclipse.exe"),
}));
add(ExternalApplicationId::netbeans, "NetBeans", firstExisting({
under(program_files, "NetBeans-25/netbeans/bin/netbeans64.exe"),
under(program_files, "NetBeans-24/netbeans/bin/netbeans64.exe"),
under(program_files, "NetBeans-23/netbeans/bin/netbeans64.exe"),
under(program_files, "NetBeans/netbeans/bin/netbeans64.exe"),
under(program_files_x86, "NetBeans/netbeans/bin/netbeans.exe"),
}));
// -------------------------------------------------------------------------
// Terminal editors
// -------------------------------------------------------------------------
add(ExternalApplicationId::vim, "Vim", firstExisting({
under(program_files, "Vim/vim91/gvim.exe"),
under(program_files, "Vim/vim90/gvim.exe"),
under(program_files_x86, "Vim/vim91/gvim.exe"),
under(program_files_x86, "Vim/vim90/gvim.exe"),
under(user_profile, "scoop/apps/vim/current/gvim.exe"),
}));
add(ExternalApplicationId::neovim, "Neovim", firstExisting({
under(program_files, "Neovim/bin/nvim-qt.exe"),
under(program_files, "Neovim/bin/nvim.exe"),
under(local, "Programs/Neovim/bin/nvim-qt.exe"),
under(local, "Programs/Neovim/bin/nvim.exe"),
under(user_profile, "scoop/apps/neovim/current/bin/nvim.exe"),
}));
add(ExternalApplicationId::emacs, "Emacs", firstExisting({
under(program_files, "Emacs/x86_64/bin/runemacs.exe"),
under(program_files, "Emacs/bin/runemacs.exe"),
under(program_files_x86, "Emacs/bin/runemacs.exe"),
under(user_profile, "scoop/apps/emacs/current/bin/runemacs.exe"),
}));
add(ExternalApplicationId::helix, "Helix", firstExisting({
under(program_files, "helix/hx.exe"),
under(program_files_x86, "helix/hx.exe"),
under(user_profile, "scoop/apps/helix/current/hx.exe"),
}));
}
const ExternalApplication *ApplicationManager::application(ExternalApplicationId id) const
{
const auto found = std::find_if(applications_.begin(), applications_.end(),
[id](const ExternalApplication &application)
{
return application.id == id;
});
return found == applications_.end() ? nullptr : &*found;
}
ExternalApplicationId ApplicationManager::defaultApplication() const
{
const auto preferred = std::find_if(targets_.begin(), targets_.end(),
[](const LaunchTarget &target)
{
return target.info.available &&
target.info.id != ExternalApplicationId::file_explorer &&
target.info.id != ExternalApplicationId::terminal &&
target.info.id != ExternalApplicationId::git_bash &&
target.info.id != ExternalApplicationId::wsl;
});
if (preferred != targets_.end())
return preferred->info.id;
const auto available = std::find_if(targets_.begin(), targets_.end(),
[](const LaunchTarget &target)
{
return target.info.available;
});
return available == targets_.end()
? ExternalApplicationId::file_explorer
: available->info.id;
}
bool ApplicationManager::launch(ExternalApplicationId application,
const std::filesystem::path &repository,
std::string &error) const
{
const auto found = std::find_if(targets_.begin(), targets_.end(),
[application](const LaunchTarget &target)
{
return target.info.id == application;
});
if (found == targets_.end() || !found->info.available)
{
error = "The selected application is not installed";
return false;
}
std::vector<std::string> arguments = found->arguments;
const std::string repository_path = repository.string();
if (application == ExternalApplicationId::git_bash && !arguments.empty())
{
arguments.back() += repository_path;
}
else
{
arguments.push_back(repository_path);
}
const izo::ProcessResult result = izo::LaunchProcess({
found->info.executable,
std::move(arguments),
repository,
true,
});
if (!result)
{
error = result.errorMessage.empty() ? "Unable to launch application" : result.errorMessage;
return false;
}
error = "Opened repository in " + found->info.name;
return true;
}