Add source code.

This commit is contained in:
alejo committed 2026-10-17 03:29:11 -03:00
1 parent dab19ace02
commit 0b8a7b3cdc
31 files changed
+2809

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
#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