171 lines
4.4 KiB
C++
171 lines
4.4 KiB
C++
#include "ExternalEditor.h"
|
|
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
|
|
#if defined(__unix__) || defined(__APPLE__)
|
|
#include <unistd.h>
|
|
#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<char> 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<std::size_t>(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<std::string> installedEditors() {
|
|
std::vector<std::string> 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<char>(in)), std::istreambuf_iterator<char>());
|
|
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
|