25#include <imgui_internal.h>
26#include <imgui_stdlib.h>
32using namespace std::literals;
34static constexpr std::string_view PROMPT_NEW =
"> ";
35static constexpr std::string_view PROMPT_CONT =
"| ";
36static constexpr std::string_view PROMPT_BUSY =
"*busy*";
41 manager.getReactor().getCommandController(),
"console",
42 "turns console display on/off", false,
Setting::Save::NO)
51 consoleSetting.
attach(*
this);
55 print(std::string(fullVersion.size(),
'-'));
57 "General information about openMSX is available at http://openmsx.org.\n"
59 "Type 'help' to see a list of available commands.\n"
60 "Or read the Console Command Reference in the manual.\n"
66 consoleSetting.
detach(*
this);
79void ImGuiConsole::print(std::string_view text,
imColor color)
82 auto pos = text.find(
'\n');
83 newLineConsole(
ConsoleLine(std::string(text.substr(0, pos)), color));
84 if (pos == std::string_view::npos)
break;
85 text.remove_prefix(pos + 1);
86 }
while (!text.empty());
89void ImGuiConsole::newLineConsole(ConsoleLine line)
91 auto addLine = [&](ConsoleLine&& l) {
92 if (lines.full()) lines.pop_front();
93 lines.push_back(std::move(l));
98 auto rest = line.splitAtColumn(columns);
99 addLine(std::move(line));
100 line = std::move(rest);
101 }
while (!line.str().empty());
103 addLine(std::move(line));
106 scrollToBottom =
true;
109static void drawLine(
const ConsoleLine& line)
111 auto n = line.numChunks();
112 for (
auto i :
xrange(n)) {
114 ImGui::TextUnformatted(line.chunkText(i));
115 if (i != (n - 1)) ImGui::SameLine(0.0f, 0.0f);
122 bool reclaimFocus =
show && !wasShown;
126 ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
131 const auto& style = ImGui::GetStyle();
132 const float footerHeightToReserve = style.ItemSpacing.y +
133 ImGui::GetFrameHeightWithSpacing();
135 bool scrollUp = ImGui::Shortcut(ImGuiKey_PageUp);
136 bool scrollDown = ImGui::Shortcut(ImGuiKey_PageDown);
137 im::Child(
"ScrollingRegion", ImVec2(0, -footerHeightToReserve), 0,
138 ImGuiWindowFlags_HorizontalScrollbar, [&]{
140 if (ImGui::Selectable(
"Clear")) {
143 ImGui::Checkbox(
"Wrap (new) output", &wrap);
146 im::StyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1), [&]{
154 if (scrollToBottom || (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
155 scrollToBottom =
false;
156 ImGui::SetScrollHereY(1.0f);
159 auto scrollDelta = ImGui::GetWindowHeight() * 0.5f;
161 ImGui::SetScrollY(std::max(ImGui::GetScrollY() - scrollDelta, 0.0f));
164 ImGui::SetScrollY(std::min(ImGui::GetScrollY() + scrollDelta, ImGui::GetScrollMaxY()));
168 auto width = ImGui::GetContentRegionAvail().x;
170 columns = narrow_cast<unsigned>(width / charWidth);
175 ImGui::AlignTextToFramePadding();
177 ImGui::SameLine(0.0f, 0.0f);
179 ImGui::SetNextItemWidth(-FLT_MIN);
181 auto cursorScrnPos = ImGui::GetCursorScreenPos();
182 auto itemWidth = ImGui::CalcItemWidth();
184 ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue |
185 ImGuiInputTextFlags_EscapeClearsAll |
186 ImGuiInputTextFlags_CallbackEdit |
187 ImGuiInputTextFlags_CallbackCompletion |
188 ImGuiInputTextFlags_CallbackHistory;
191 enter = ImGui::InputTextWithHint(
"##Input",
"enter command", &inputBuf, flags, &textEditCallbackStub,
this);
193 if (enter && (prompt != PROMPT_BUSY)) {
196 cmdLine.
addLine(coloredInputBuf);
197 newLineConsole(std::move(cmdLine));
200 strAppend(commandBuffer, inputBuf,
'\n');
202 putHistory(std::move(inputBuf));
205 coloredInputBuf.
clear();
207 historyBackupLine.clear();
210 if (commandController.isComplete(commandBuffer)) {
215 prompt = PROMPT_BUSY;
219 if (
const auto& s = result.
getString(); !s.empty()) {
224 [
this](
const std::string& error) {
228 commandBuffer.clear();
230 prompt = PROMPT_CONT;
234 ImGui::SetItemDefaultFocus();
237 (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows) &&
238 !ImGui::IsPopupOpen(
nullptr, ImGuiPopupFlags_AnyPopupId) &&
239 !ImGui::IsAnyItemActive() && !ImGui::IsMouseClicked(0) && !ImGui::IsMouseClicked(1))) {
240 ImGui::SetKeyboardFocusHere(-1);
252 const auto* font = ImGui::GetFont();
253 auto fontSize = ImGui::GetFontSize();
254 gl::vec2 frameSize(itemWidth, fontSize + style.FramePadding.y * 2.0f);
256 gl::vec2 bottomRight = topLeft + frameSize;
258 ImVec4 clipRect =
gl::vec4(topLeft, bottomRight);
259 auto* drawList = ImGui::GetWindowDrawList();
260 auto charWidth = ImGui::GetFont()->GetCharAdvance(
'A');
261 if (ImGui::IsItemActive()) {
262 auto id = ImGui::GetID(
"##Input");
263 if (
const auto* state = ImGui::GetInputTextState(
id)) {
265 drawPos.x -= state->ScrollX;
267 bool cursorIsVisible = (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
268 if (cursorIsVisible) {
270 gl::vec2 cursorOffset(
float(state->GetCursorPos()) * charWidth, 0.0f);
271 gl::vec2 cursorScreenPos = ImTrunc(drawPos + cursorOffset);
272 ImRect cursorScreenRect(cursorScreenPos.x, cursorScreenPos.y - 0.5f, cursorScreenPos.x + 1.0f, cursorScreenPos.y + fontSize - 1.5f);
273 if (cursorScreenRect.Overlaps(clipRect)) {
280 auto text = coloredInputBuf.
chunkText(i);
282 const char*
begin = text.data();
283 const char*
end =
begin + text.size();
284 drawList->AddText(font, fontSize, drawPos, rgba,
begin,
end, 0.0f, &clipRect);
291int ImGuiConsole::textEditCallbackStub(ImGuiInputTextCallbackData* data)
293 auto* console =
static_cast<ImGuiConsole*
>(data->UserData);
294 return console->textEditCallback(data);
297int ImGuiConsole::textEditCallback(ImGuiInputTextCallbackData* data)
299 switch (data->EventFlag) {
300 case ImGuiInputTextFlags_CallbackCompletion: {
301 std::string_view oldLine{data->Buf, narrow<size_t>(data->BufTextLen)};
306 std::string newFront = commandController.
tabCompletion(front);
307 historyBackupLine =
strCat(std::move(newFront), back);
310 data->DeleteChars(0, data->BufTextLen);
311 data->InsertChars(0, historyBackupLine.c_str());
313 colorize(historyBackupLine);
316 case ImGuiInputTextFlags_CallbackHistory: {
318 if (data->EventKey == ImGuiKey_UpArrow) {
319 while (!
match && (historyPos < narrow<int>(history.
size() - 1))) {
321 match = history[historyPos].starts_with(historyBackupLine);
323 }
else if ((data->EventKey == ImGuiKey_DownArrow) && (historyPos != -1)) {
325 if (--historyPos == -1)
break;
326 match = history[historyPos].starts_with(historyBackupLine);
329 if (
match || (historyPos == -1)) {
330 const auto& historyStr = (historyPos >= 0) ? history[historyPos] : historyBackupLine;
331 data->DeleteChars(0, data->BufTextLen);
332 data->InsertChars(0, historyStr.c_str());
333 colorize(std::string_view{data->Buf, narrow<size_t>(data->BufTextLen)});
337 case ImGuiInputTextFlags_CallbackEdit: {
338 historyBackupLine.assign(data->Buf, narrow<size_t>(data->BufTextLen));
340 colorize(historyBackupLine);
347void ImGuiConsole::colorize(std::string_view line)
351 assert(colors.size() == line.size());
353 coloredInputBuf.
clear();
355 while (pos != colors.size()) {
356 char col = colors[pos];
358 while ((pos != colors.size()) && (colors[pos] == col)) {
364 case 'E':
return ERROR;
368 case 'p':
return PROC;
370 default:
return TEXT;
373 coloredInputBuf.
addChunk(line.substr(pos2, pos - pos2), color);
377void ImGuiConsole::putHistory(std::string command)
379 if (command.empty())
return;
380 if (!history.
empty() && (history.
front() == command)) {
387void ImGuiConsole::saveHistory()
390 std::ofstream outputFile;
394 throw FileException(
"Error while saving the console history.");
397 outputFile << s <<
'\n';
399 }
catch (FileException& e) {
404void ImGuiConsole::loadHistory()
407 std::ifstream inputFile(
411 getline(inputFile, line);
414 }
catch (FileException&) {
419void ImGuiConsole::output(std::string_view text)
424unsigned ImGuiConsole::getOutputColumns()
const
429void ImGuiConsole::update(
const Setting& )
noexcept
431 show = consoleSetting.getBoolean();
449 ImGui::SetWindowFocus(
nullptr);
const std::string & getColors() const
Ouput: a string of equal length of the input command where each character indicates the type of the c...
void printWarning(std::string_view message)
static void setOutput(InterpreterOutput *output_)
This class represents a single text line in the console.
void addLine(const ConsoleLine &ln)
Append another line (possibly containing multiple chunks).
std::string_view chunkText(size_t i) const
Get the text for the i-th chunk.
imColor chunkColor(size_t i) const
Get the color for the i-th chunk.
void addChunk(std::string_view text, imColor color=imColor::TEXT)
Append a chunk with a (different) color.
void clear()
Reinitialize to an empty line.
size_t numChunks() const
Get the number of different chunks.
std::string tabCompletion(std::string_view command)
Complete the given command.
void paint(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) override
ImGuiConsole(ImGuiManager &manager)
void loadLine(std::string_view name, zstring_view value) override
Interpreter & getInterpreter()
void executeDelayed(std::function< void()> action)
void setOutput(InterpreterOutput *output_)
TclParser parse(std::string_view command)
GlobalCommandController & getGlobalCommandController()
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
zstring_view getString() const
static std::string full()
static uint32_t getMainWindowId()
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
auto CalcTextSize(std::string_view str)
void TextUnformatted(const std::string &str)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void StyleVar(ImGuiStyleVar idx, float val, std::invocable<> auto next)
void PopupContextWindow(const char *str_id, ImGuiPopupFlags popup_flags, std::invocable<> auto next)
void StyleColor(bool active, Args &&...args)
void Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
void openOfStream(std::ofstream &stream, zstring_view filename)
Open an ofstream in a platform-independent manner.
This file implemented 3 utility functions:
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
std::optional< bool > match(const BooleanInput &binding, const Event &event, function_ref< int(JoystickId)> getJoyDeadZone)
ImU32 getColor(imColor col)
const FileContext & userFileContext()
std::string_view substr(std::string_view utf8, std::string_view::size_type first=0, std::string_view::size_type len=std::string_view::npos)
auto distance(octet_iterator first, octet_iterator last)
constexpr auto reverse(Range &&range)
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)
constexpr auto begin(const zstring_view &x)
constexpr auto end(const zstring_view &x)