#include "ExternalEditor.h" #include #include #include #include #include #include #if defined(__unix__) || defined(__APPLE__) #include #endif namespace fs = std::filesystem; namespace { #ifdef _WIN32 const char kPathSeparator = ';'; #else const char kPathSeparator = ':'; #endif bool isExecutableFile(const fs::path& path) { std::error_code error; if (!fs::is_regular_file(path, error)) { return false; } #if defined(__unix__) || defined(__APPLE__) return access(path.c_str(), X_OK) == 0; #else return true; #endif } bool findInPath(const std::string& program) { if (program.find('/') != std::string::npos || program.find('\\') != std::string::npos) { return isExecutableFile(program); } const char* pathVariable = std::getenv("PATH"); if (!pathVariable) { return false; } std::stringstream paths(pathVariable); std::string directory; while (std::getline(paths, directory, kPathSeparator)) { if (directory.empty()) { continue; } if (isExecutableFile(fs::path(directory) / program)) { return true; } #ifdef _WIN32 if (isExecutableFile(fs::path(directory) / (program + ".exe"))) { return true; } #endif } return false; } std::string firstWord(const std::string& command) { std::istringstream in(command); std::string word; in >> word; return word; } std::string quotePath(const std::string& path) { #ifdef _WIN32 return "\"" + path + "\""; #else std::string quoted = "'"; for (char c : path) { if (c == '\'') { quoted += "'\\''"; } else { quoted.push_back(c); } } return quoted + "'"; #endif } std::string makeTempFile(const std::string& content) { #if defined(__unix__) || defined(__APPLE__) std::string pattern = (fs::temp_directory_path() / "task-tracker-XXXXXX.txt").string(); std::vector buffer(pattern.begin(), pattern.end()); buffer.push_back('\0'); int fd = mkstemps(buffer.data(), 4); if (fd < 0) { throw std::runtime_error("Cannot create a temporary file for the editor."); } std::size_t written = 0; while (written < content.size()) { ssize_t n = ::write(fd, content.data() + written, content.size() - written); if (n <= 0) { ::close(fd); throw std::runtime_error("Cannot write the temporary file for the editor."); } written += static_cast(n); } ::close(fd); return std::string(buffer.data()); #else fs::path path = fs::temp_directory_path() / ("task-tracker-" + std::to_string(std::rand()) + ".txt"); std::ofstream out(path, std::ios::binary); out << content; if (!out) { throw std::runtime_error("Cannot write the temporary file for the editor."); } return path.string(); #endif } } // namespace namespace ExternalEditor { std::string defaultCommand() { for (const char* variable : {"VISUAL", "EDITOR"}) { const char* value = std::getenv(variable); if (value && *value) { return value; } } for (const char* candidate : {"nano", "vim", "vi"}) { if (findInPath(candidate)) { return candidate; } } return ""; } bool commandExists(const std::string& command) { const std::string program = firstWord(command); return !program.empty() && findInPath(program); } std::vector installedEditors() { std::vector found; for (const char* candidate : {"nano", "vim", "nvim", "vi", "emacs", "micro", "helix", "hx", "code", "kate", "gedit"}) { if (findInPath(candidate)) { found.push_back(candidate); } } return found; } std::string edit(const std::string& command, const std::string& initialText) { const std::string path = makeTempFile(initialText); const std::string commandLine = command + " " + quotePath(path); const int status = std::system(commandLine.c_str()); std::ifstream in(path, std::ios::binary); std::string content((std::istreambuf_iterator(in)), std::istreambuf_iterator()); in.close(); std::error_code error; fs::remove(path, error); if (status != 0) { throw std::runtime_error("The editor exited with an error; nothing was changed."); } return content; } } // namespace ExternalEditor