Cogs.Core
ScriptInspector.cpp
1#include "Inspectors.h"
2#include "Context.h"
3#include "InspectorGuiHelper.h"
4
5#include "Systems/Core/ScriptSystem.h"
6
7#include "Scripting/ScriptingManager.h"
8
9#include "Utilities/Parsing.h"
10
11#include "Renderer/IRenderer.h"
12
13#include "Foundation/Reflection/TypeDatabase.h"
14
15#include "imgui.h"
16
17#include <algorithm>
18#include <string.h>
19
20namespace
21{
22 int editorCallback(ImGuiInputTextCallbackData* data)
23 {
24 auto engine = (Cogs::Core::ScriptingEngine *)data->UserData;
25 auto & editor = engine->editorContext;
26
27 auto & io = ImGui::GetIO();
28
29 if (editor.currentCompletions.size() > 1) {
30 ImGui::SetNextWindowPos(ImVec2(ImGui::GetItemRectMin().x + editor.completionOffset * 7.0f, ImGui::GetItemRectMax().y - 15.0f));
31 ImGui::SetNextWindowSize(ImVec2(200, 300));
32 ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGui::GetStyle().Colors[ImGuiCol_Border]);
33 ImGui::BeginTooltip();
34 for (size_t i = 0; i < editor.currentCompletions.size(); ++i) {
35 auto & completion = editor.currentCompletions[i];
36 const ImColor col = i == static_cast<size_t>(editor.completionIndex) ? ImColor(1.0f, 1.0f, 1.0f, 1.0f) : ImColor(0.7f, 0.7f, 0.7f, 1.0f);
37 ImGui::PushStyleColor(ImGuiCol_Text, col.Value);
38 ImGui::TextUnformatted(std::string(completion).c_str());
39 ImGui::PopStyleColor();
40
41 if (i == static_cast<size_t>(editor.completionIndex)) {
42 ImGui::SetScrollHereY(0.5f);
43 }
44 }
45 ImGui::EndTooltip();
46 ImGui::PopStyleColor();
47 }
48
49 if (data->EventFlag == ImGuiInputTextFlags_CallbackCompletion) {
50 if (editor.currentCompletions.empty()) {
51 editor.original = std::string(data->Buf, data->BufTextLen);
52
53 static thread_local Cogs::Core::TokenStream splits;
54 static thread_local Cogs::Core::TokenStream tokens;
55 static thread_local Cogs::Core::TokenStream completions;
56 splits.clear();
57 tokens.clear();
58 completions.clear();
59
60 Cogs::Core::split(editor.original, " =\t,:[](){};", splits);
61
62 if (splits.empty()) return 0;
63
64 Cogs::Core::split(splits.back(), ".", tokens);
65
66 if (editor.original.back() == '.') {
67 tokens.push_back(nullptr);
68 editor.completionOffset = editor.original.size();
69 } else {
70 editor.completionOffset = tokens.back().data() - editor.original.data();
71 }
72
73 engine->completions(tokens, completions);
74
75 std::sort(completions.begin(), completions.end());
76
77 editor.currentCompletions = completions;
78 editor.completionIndex = 0;
79
80 if (tokens.back().size()) {
81 editor.currentCompletions.push_back(tokens.back());
82 }
83 } else {
84 editor.completionIndex += io.KeyShift ? -1 : 1;
85 }
86
87 if (editor.currentCompletions.empty()) return 0;
88
89 if (editor.completionIndex >= (int)editor.currentCompletions.size()) editor.completionIndex = 0;
90 else if (editor.completionIndex < 0) editor.completionIndex = (int)editor.currentCompletions.size() - 1;
91
92 auto & suggestion = editor.currentCompletions[editor.completionIndex];
93 data->DeleteChars((int)editor.completionOffset, data->BufTextLen - (int)editor.completionOffset);
94 data->InsertChars((int)editor.completionOffset, suggestion.data(), suggestion.data() + suggestion.size());
95 data->CursorPos = data->BufTextLen;
96 data->BufDirty = true;
97 editor.suggested = std::string(data->Buf, data->BufTextLen);
98 } else if (!editor.currentCompletions.empty() && std::string_view(data->Buf, data->BufTextLen) != std::string_view(editor.suggested)) {
99 editor.currentCompletions.clear();
100 editor.completionIndex = 0;
101 }
102
103 // History
104
105 if (editor.commandHistory.empty()) return 0;
106
107 auto modifyHistory = [&](int offset) {
108 editor.historyIndex += offset;
109 editor.historyIndex = std::max(0, std::min(editor.historyIndex, (int)editor.commandHistory.size() - 1));
110
111 data->BufTextLen = snprintf(data->Buf, data->BufSize, "%s", editor.commandHistory[editor.historyIndex].c_str());
112 data->BufDirty = true;
113 data->CursorPos = data->BufTextLen;
114 };
115
116 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (io.KeyShift || data->CursorPos == 0) && !editor.movedUp) {
117 modifyHistory(-1);
118
119 editor.movedUp = true;
120
121 return 1;
122 } else if (editor.movedUp && !ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
123 editor.movedUp = false;
124 }
125
126 if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && io.KeyShift && !editor.movedDown) {
127 modifyHistory(+1);
128
129 editor.movedDown = true;
130
131 return 1;
132 } else if (editor.movedDown && !ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
133 editor.movedDown = false;
134 }
135
136 return 0;
137 }
138}
139
140void Cogs::Core::scriptConsole(Context * context, bool * show)
141{
142 if (*show) {
143 ImGui::SetNextWindowSize(ImVec2(600, 1000), ImGuiCond_Once);
144
145 guiBegin("Script Console", show);
146
147 auto & scriptingManager = context->scriptingManager;
148
149 for (auto & scriptEngine : scriptingManager->engines) {
150 auto & editor = scriptEngine->editorContext;
151
152 ImGui::Text("%s", scriptEngine->editorContext.name.c_str());
153
154 ImGui::BeginChild((editor.name + " History").c_str(), ImVec2(0, 200), false, ImGuiWindowFlags_HorizontalScrollbar);
155
156 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1));
157
158 for (auto & h : editor.history) {
159 ImColor col = ImColor(1.0f, 1.0f, 1.0f, 1.0f);
160 if (h.find("Error:") != std::string::npos) {
161 col = ImColor(1.0f, 0.4f, 0.4f, 1.0f);
162 } else if (h.find("> ") == 0) {
163 col = ImColor(0.7f, 0.7f, 0.7f, 1.0f);
164 }
165 ImGui::PushStyleColor(ImGuiCol_Text, col.Value);
166 ImGui::TextUnformatted(h.c_str());
167 ImGui::PopStyleColor();
168 }
169
170 if (editor.scrollToBottom) {
171 ImGui::SetScrollHereY();
172 editor.scrollToBottom = false;
173 }
174
175 ImGui::PopStyleVar();
176 ImGui::EndChild();
177
178 ImGui::PushItemWidth(-1);
179 if (ImGui::InputTextMultiline((editor.name + " Source").c_str(),
180 (char *)editor.buffer.data(),
181 editor.buffer.size(),
182 ImVec2(0, 0),
183 ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CtrlEnterForNewLine | ImGuiInputTextFlags_CallbackAlways | ImGuiInputTextFlags_CallbackCompletion,
184 editorCallback,
185 scriptEngine.get())) {
186
187 ImguiRenderer* guiRenderer = context->renderer->getGuiRenderer();
188 if (guiRenderer) {
189 guiRenderer->addTextToGlyphBuilder(editor.buffer.data());
190 }
191
192 auto command = std::string(editor.buffer.data());
193
194 if (command.size()) {
195 editor.history.emplace_back("> " + command + "\n");
196
197 editor.commandHistory.emplace_back(std::move(command));
198
199 std::string result(scriptEngine->eval(editor.buffer.data()));
200
201 editor.history.emplace_back(result + "\n");
202 editor.scrollToBottom = true;
203
204 editor.buffer.clear();
205 editor.buffer.resize(16384);
206
207 editor.historyIndex = (int)editor.commandHistory.size();
208 }
209
210 ImGui::SetKeyboardFocusHere(1);
211 }
212 }
213
214 guiEnd();
215 }
216}
217
218void Cogs::Core::scriptInspector(Context * /*context*/, bool * /*show*/)
219{
220 // Enumerate and show all active scripts.
221}