#include "managers/application_manager.h" #include #include #include #include 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 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 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 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; }