#include <cstdlib>
#include "ext/imgui/imgui.h"
#include "UI/ImDebugger/ImDebugger.h"
#include "UI/ImDebugger/ImConsole.h"
#include "Core/LuaContext.h"
#include "Common/StringUtils.h"
ImConsole::ImConsole() {
memset(InputBuf, 0, sizeof(InputBuf));
HistoryPos = -1;
Commands.push_back("HELP");
Commands.push_back("HISTORY");
Commands.push_back("CLEAR");
AutoScroll = true;
ScrollToBottom = false;
}
ImConsole::~ImConsole() {
for (int i = 0; i < History.Size; i++)
ImGui::MemFree(History[i]);
}
static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; }
static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; }
static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); }
static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; }
static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) {
ImConsole* console = (ImConsole*)data->UserData;
return console->TextEditCallback(data);
}
void ImConsole::Draw(ImConfig &cfg) {
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Lua Console", &cfg.luaConsoleOpen)) {
ImGui::End();
return;
}
if (ImGui::SmallButton("Clear")) {
g_lua.Clear();
}
ImGui::SameLine();
bool copy_to_clipboard = ImGui::SmallButton("Copy");
ImGui::Separator();
if (ImGui::BeginPopup("Options")) {
ImGui::Checkbox("Auto-scroll", &AutoScroll);
ImGui::EndPopup();
}
if (ImGui::Button("Options"))
ImGui::OpenPopup("Options");
ImGui::SameLine();
Filter.Draw("Filter (\"incl,-excl\") (\"error\")", 180);
ImGui::Separator();
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar | ImGuiChildFlags_NavFlattened)) {
if (ImGui::BeginPopupContextWindow()) {
if (ImGui::Selectable("Clear"))
g_lua.Clear();
ImGui::EndPopup();
}
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1));
if (copy_to_clipboard)
ImGui::LogToClipboard();
for (const auto &item : g_lua.GetLines()) {
if (!Filter.PassFilter(item.line.c_str()))
continue;
ImVec4 color;
bool has_color = true;
switch (item.type) {
case LogLineType::Cmd: color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break;
case LogLineType::Error: color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); break;
case LogLineType::External: color = ImVec4(0.8f, 0.8f, 1.0f, 1.0f); break;
case LogLineType::Integer: color = ImVec4(1.0f, 1.0f, 0.8f, 1.0f); break;
case LogLineType::String: color = ImVec4(0.8f, 1.0f, 0.8f, 1.0f); break;
default:
has_color = false;
break;
}
if (has_color)
ImGui::PushStyleColor(ImGuiCol_Text, color);
switch (item.type) {
case LogLineType::Url:
if (ImGui::TextLink(item.line.c_str())) {
System_LaunchUrl(LaunchUrlType::BROWSER_URL, item.line.c_str());
}
break;
default:
ImGui::TextUnformatted(item.line.data(), item.line.data() + item.line.size());
break;
}
if (has_color)
ImGui::PopStyleColor();
}
if (copy_to_clipboard)
ImGui::LogFinish();
if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()))
ImGui::SetScrollHereY(1.0f);
ScrollToBottom = false;
ImGui::PopStyleVar();
}
ImGui::EndChild();
ImGui::Separator();
bool reclaim_focus = false;
ImGuiInputTextFlags input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory;
if (ImGui::InputText("Input", InputBuf, IM_ARRAYSIZE(InputBuf), input_text_flags, &TextEditCallbackStub, (void*)this)) {
char* s = InputBuf;
Strtrim(s);
if (s[0])
ExecCommand(s);
strcpy(s, "");
reclaim_focus = true;
}
ImGui::SetItemDefaultFocus();
if (reclaim_focus)
ImGui::SetKeyboardFocusHere(-1);
ImGui::End();
}
void ImConsole::ExecCommand(const char* command_line) {
HistoryPos = -1;
for (int i = History.Size - 1; i >= 0; i--)
if (Stricmp(History[i], command_line) == 0)
{
ImGui::MemFree(History[i]);
History.erase(History.begin() + i);
break;
}
History.push_back(Strdup(command_line));
g_lua.Print(LogLineType::Cmd, std::string(command_line));
if (Stricmp(command_line, "clear") == 0) {
g_lua.Clear();
} else if (Stricmp(command_line, "help") == 0) {
g_lua.Print("Available non-Lua commands:");
for (int i = 0; i < Commands.Size; i++)
g_lua.Print(StringFromFormat("- %s", Commands[i]));
g_lua.Print("For Lua help:");
g_lua.Print(LogLineType::Url, "https://www.lua.org/manual/5.3/");
} else if (Stricmp(command_line, "history") == 0) {
int first = History.Size - 10;
for (int i = first > 0 ? first : 0; i < History.Size; i++)
g_lua.Print(StringFromFormat("%3d: %s", i, History[i]));
} else {
g_lua.ExecuteConsoleCommand(command_line);
}
ScrollToBottom = true;
}
int ImConsole::TextEditCallback(ImGuiInputTextCallbackData* data) {
switch (data->EventFlag) {
case ImGuiInputTextFlags_CallbackCompletion:
{
const char* word_end = data->Buf + data->CursorPos;
const char* word_start = word_end;
while (word_start > data->Buf)
{
const char c = word_start[-1];
if (c == ' ' || c == '\t' || c == ',' || c == ';')
break;
word_start--;
}
ImVector<const char*> candidates;
for (int i = 0; i < Commands.Size; i++)
if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0)
candidates.push_back(Commands[i]);
if (candidates.Size == 0) {
g_lua.Print(StringFromFormat("No match for \"%.*s\"!", (int)(word_end - word_start), word_start));
} else if (candidates.Size == 1) {
data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));
data->InsertChars(data->CursorPos, candidates[0]);
data->InsertChars(data->CursorPos, " ");
} else {
int match_len = (int)(word_end - word_start);
for (;;) {
int c = 0;
bool all_candidates_matches = true;
for (int i = 0; i < candidates.Size && all_candidates_matches; i++)
if (i == 0)
c = toupper(candidates[i][match_len]);
else if (c == 0 || c != toupper(candidates[i][match_len]))
all_candidates_matches = false;
if (!all_candidates_matches)
break;
match_len++;
}
if (match_len > 0) {
data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start));
data->InsertChars(data->CursorPos, candidates[0], candidates[0] + match_len);
}
g_lua.Print("Possible matches:");
for (int i = 0; i < candidates.Size; i++) {
g_lua.Print(StringFromFormat("- %s", candidates[i]));
}
}
break;
}
case ImGuiInputTextFlags_CallbackHistory:
{
const int prev_history_pos = HistoryPos;
if (data->EventKey == ImGuiKey_UpArrow) {
if (HistoryPos == -1)
HistoryPos = History.Size - 1;
else if (HistoryPos > 0)
HistoryPos--;
} else if (data->EventKey == ImGuiKey_DownArrow) {
if (HistoryPos != -1)
if (++HistoryPos >= History.Size)
HistoryPos = -1;
}
if (prev_history_pos != HistoryPos) {
const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, history_str);
}
}
}
return 0;
}