Files
whetstone_DSL/editor/src/TerminalPanel.h

235 lines
7.5 KiB
C++

#pragma once
#include "imgui.h"
#include <string>
#include <vector>
#include <sstream>
#include <cstdio>
class TerminalPanel {
public:
static std::string buildCommandLine(const std::string& cwd, const std::string& command) {
std::string workingDir = cwd.empty() ? "." : cwd;
#ifdef _WIN32
return "cmd /C \"cd /d \\\"" + escapeForCmd(workingDir) + "\\\" && " +
command + " 2>&1\"";
#else
return "sh -c 'cd \"" + escapeForSh(workingDir) + "\" && " +
command + " 2>&1'";
#endif
}
void render(const std::string& cwd, ImFont* monoFont) {
ImGui::PushFont(monoFont ? monoFont : ImGui::GetFont());
ImGui::PushItemWidth(-80.0f);
bool runNow = inputText("##terminalCmd", commandBuf_,
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button("Run") || runNow) {
runCommand(cwd);
}
ImGui::SameLine();
if (ImGui::Button("Clear")) {
output_.clear();
}
ImGui::Separator();
ImGui::BeginChild("##terminalOutput", ImVec2(0, 0), false);
if (output_.empty()) {
ImGui::TextDisabled("(no output)");
} else {
renderAnsiText(output_);
if (autoScroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 20.0f) {
ImGui::SetScrollHereY(1.0f);
}
}
ImGui::EndChild();
ImGui::PopFont();
}
void append(const std::string& text) {
output_ += text;
}
int runAndAppend(const std::string& cwd, const std::string& command) {
std::string out;
int code = runProcess(cwd, command, out);
output_ += "> " + command + "\n";
output_ += out;
return code;
}
int runAndCapture(const std::string& cwd, const std::string& command, std::string& out) {
out.clear();
int code = runProcess(cwd, command, out);
output_ += "> " + command + "\n";
output_ += out;
return code;
}
const std::string& getOutput() const {
return output_;
}
private:
std::string commandBuf_;
std::string output_;
bool autoScroll_ = true;
struct InputTextCallbackData {
std::string* str;
};
static int inputTextCallback(ImGuiInputTextCallbackData* data) {
auto* userData = static_cast<InputTextCallbackData*>(data->UserData);
if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) {
userData->str->resize(data->BufTextLen);
data->Buf = userData->str->data();
}
return 0;
}
static bool inputText(const char* label, std::string& str, ImGuiInputTextFlags flags) {
flags |= ImGuiInputTextFlags_CallbackResize;
InputTextCallbackData cbData{&str};
if (str.capacity() < str.size() + 64) str.reserve(str.size() + 256);
return ImGui::InputText(label, str.data(), str.capacity() + 1,
flags, inputTextCallback, &cbData);
}
static std::string escapeForCmd(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '"') out += "\\\"";
else out += c;
}
return out;
}
static std::string escapeForSh(const std::string& s) {
std::string out;
out.reserve(s.size());
for (char c : s) {
if (c == '\'') out += "'\\''";
else out += c;
}
return out;
}
void runCommand(const std::string& cwd) {
if (commandBuf_.empty()) return;
std::string out;
int code = runProcess(cwd, commandBuf_, out);
output_ += "> " + commandBuf_ + "\n";
output_ += out;
output_ += "[exit code: " + std::to_string(code) + "]\n";
}
static int runProcess(const std::string& cwd, const std::string& command, std::string& out) {
std::string fullCmd = buildCommandLine(cwd, command);
#ifdef _WIN32
FILE* pipe = _popen(fullCmd.c_str(), "r");
#else
FILE* pipe = popen(fullCmd.c_str(), "r");
#endif
if (!pipe) {
out += "[terminal] Failed to launch command.\n";
return -1;
}
char buffer[4096];
while (!feof(pipe)) {
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
out += buffer;
}
}
#ifdef _WIN32
int code = _pclose(pipe);
#else
int code = pclose(pipe);
#endif
return code;
}
static ImVec4 ansiColor(int code, const ImVec4& base) {
switch (code) {
case 30: return ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
case 31: return ImVec4(0.85f, 0.2f, 0.2f, 1.0f);
case 32: return ImVec4(0.2f, 0.75f, 0.3f, 1.0f);
case 33: return ImVec4(0.85f, 0.65f, 0.2f, 1.0f);
case 34: return ImVec4(0.35f, 0.55f, 0.95f, 1.0f);
case 35: return ImVec4(0.8f, 0.35f, 0.8f, 1.0f);
case 36: return ImVec4(0.2f, 0.75f, 0.75f, 1.0f);
case 37: return ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
case 90: return ImVec4(0.45f, 0.45f, 0.45f, 1.0f);
case 91: return ImVec4(1.0f, 0.4f, 0.4f, 1.0f);
case 92: return ImVec4(0.4f, 1.0f, 0.4f, 1.0f);
case 93: return ImVec4(1.0f, 0.9f, 0.4f, 1.0f);
case 94: return ImVec4(0.5f, 0.7f, 1.0f, 1.0f);
case 95: return ImVec4(1.0f, 0.6f, 1.0f, 1.0f);
case 96: return ImVec4(0.5f, 1.0f, 1.0f, 1.0f);
case 97: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
case 39: return base;
default: return base;
}
}
static void renderAnsiText(const std::string& text) {
ImVec4 baseColor = ImGui::GetStyleColorVec4(ImGuiCol_Text);
ImVec4 currentColor = baseColor;
std::string buffer;
bool lineStart = true;
auto emit = [&](const std::string& chunk) {
if (chunk.empty()) return;
if (!lineStart) ImGui::SameLine(0.0f, 0.0f);
ImGui::TextColored(currentColor, "%s", chunk.c_str());
lineStart = false;
};
for (size_t i = 0; i < text.size(); ) {
if (text[i] == '\x1b' && i + 1 < text.size() && text[i + 1] == '[') {
emit(buffer);
buffer.clear();
i += 2;
std::string codeStr;
while (i < text.size() && text[i] != 'm') {
codeStr += text[i++];
}
if (i < text.size() && text[i] == 'm') i++;
if (codeStr.empty()) {
currentColor = baseColor;
continue;
}
std::stringstream ss(codeStr);
std::string part;
while (std::getline(ss, part, ';')) {
int code = 0;
try { code = std::stoi(part); } catch (...) { code = 0; }
if (code == 0) {
currentColor = baseColor;
} else {
currentColor = ansiColor(code, currentColor);
}
}
continue;
}
if (text[i] == '\n') {
emit(buffer);
buffer.clear();
ImGui::NewLine();
lineStart = true;
++i;
continue;
}
buffer.push_back(text[i++]);
}
emit(buffer);
}
};