Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 173 additions & 5 deletions src/lua_console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "imgui.h"
#include "modes.h"
#include "occt_view.h"
#include "shp.h"
#include "sketch.h"

extern "C"
Expand All @@ -18,7 +19,8 @@ extern "C"

namespace
{
const char* k_registry_gui = "ezycad_gui";
const char* k_registry_gui = "ezycad_gui";
const char* k_shp_metatable = "EzyCad_Shp";

GUI* get_gui(lua_State* L)
{
Expand All @@ -28,6 +30,18 @@ GUI* get_gui(lua_State* L)
return gui;
}

void push_shp(lua_State* L, const Shp_ptr& shp)
{
void* block = lua_newuserdata(L, sizeof(Shp_ptr));
new (block) Shp_ptr(shp);
luaL_setmetatable(L, k_shp_metatable);
}

Shp_ptr* to_shp(lua_State* L, int index)
{
return static_cast<Shp_ptr*>(luaL_testudata(L, index, k_shp_metatable));
}

// ezy.log(msg) -> append to console and log window
int l_ezy_log(lua_State* L)
{
Expand Down Expand Up @@ -149,6 +163,77 @@ int l_view_add_sphere(lua_State* L)
return 0;
}

// view.get_shape(index) -> Shp userdata or nil (1-based index)
int l_view_get_shape(lua_State* L)
{
GUI* gui = get_gui(L);
Occt_view* view = gui ? gui->get_view() : nullptr;
if (!view)
{
lua_pushnil(L);
return 1;
}
lua_Integer idx = luaL_checkinteger(L, 1);
if (idx < 1)
{
lua_pushnil(L);
return 1;
}
std::list<Shp_ptr>& shapes = view->get_shapes();
auto it = shapes.begin();
for (lua_Integer i = 1; i < idx && it != shapes.end(); ++i, ++it)
;
if (it == shapes.end())
{
lua_pushnil(L);
return 1;
}
push_shp(L, *it);
return 1;
}

// Shp userdata __gc
int l_shp_gc(lua_State* L)
{
Shp_ptr* p = to_shp(L, 1);
if (p)
p->~Shp_ptr();
return 0;
}

// Shp:name() -> string
int l_shp_name(lua_State* L)
{
Shp_ptr* p = static_cast<Shp_ptr*>(luaL_checkudata(L, 1, k_shp_metatable));
lua_pushstring(L, (*p)->get_name().c_str());
return 1;
}

// Shp:set_name(s)
int l_shp_set_name(lua_State* L)
{
Shp_ptr* p = static_cast<Shp_ptr*>(luaL_checkudata(L, 1, k_shp_metatable));
const char* s = luaL_checkstring(L, 2);
(*p)->set_name(s);
return 0;
}

// Shp:visible() -> boolean
int l_shp_visible(lua_State* L)
{
Shp_ptr* p = static_cast<Shp_ptr*>(luaL_checkudata(L, 1, k_shp_metatable));
lua_pushboolean(L, (*p)->get_visible());
return 1;
}

// Shp:set_visible(b)
int l_shp_set_visible(lua_State* L)
{
Shp_ptr* p = static_cast<Shp_ptr*>(luaL_checkudata(L, 1, k_shp_metatable));
(*p)->set_visible(lua_toboolean(L, 2));
return 0;
}

// ezy.help() / help() - print available commands
int l_ezy_help(lua_State* L)
{
Expand All @@ -169,7 +254,13 @@ int l_ezy_help(lua_State* L)
" view.shape_count() - number of shapes\n"
" view.curr_sketch_name() - current sketch name\n"
" view.add_box(ox,oy,oz,w,l,h) - add box\n"
" view.add_sphere(ox,oy,oz,r) - add sphere";
" view.add_sphere(ox,oy,oz,r) - add sphere\n"
" view.get_shape(i) - get shape by 1-based index (returns Shp or nil)\n"
"Shp (shape object):\n"
" s:name() - get shape name\n"
" s:set_name(s) - set shape name\n"
" s:visible() - get visibility\n"
" s:set_visible(b) - set visibility";
con->append_line_from_lua(help_text);
return 0;
}
Expand Down Expand Up @@ -214,6 +305,22 @@ void Lua_console::register_bindings()
if (!m_L || !m_gui)
return;

// Shp userdata metatable (in registry for luaL_setmetatable / luaL_checkudata)
luaL_newmetatable(m_L, k_shp_metatable);
lua_pushcfunction(m_L, l_shp_gc);
lua_setfield(m_L, -2, "__gc");
lua_newtable(m_L); // __index
lua_pushcfunction(m_L, l_shp_name);
lua_setfield(m_L, -2, "name");
lua_pushcfunction(m_L, l_shp_set_name);
lua_setfield(m_L, -2, "set_name");
lua_pushcfunction(m_L, l_shp_visible);
lua_setfield(m_L, -2, "visible");
lua_pushcfunction(m_L, l_shp_set_visible);
lua_setfield(m_L, -2, "set_visible");
lua_setfield(m_L, -2, "__index");
lua_pop(m_L, 1);

// ezy table
lua_newtable(m_L);
lua_pushlightuserdata(m_L, this);
Expand Down Expand Up @@ -247,6 +354,8 @@ void Lua_console::register_bindings()
lua_setfield(m_L, -2, "add_box");
lua_pushcfunction(m_L, l_view_add_sphere);
lua_setfield(m_L, -2, "add_sphere");
lua_pushcfunction(m_L, l_view_get_shape);
lua_setfield(m_L, -2, "get_shape");
lua_setglobal(m_L, "view");

// Override print to use ezy.log
Expand All @@ -260,6 +369,15 @@ void Lua_console::execute(const std::string& code)
{
if (!m_L || code.empty())
return;

// Track command history (avoid consecutive duplicates).
if (!code.empty())
{
if (m_cmd_history.empty() || m_cmd_history.back() != code)
m_cmd_history.push_back(code);
m_cmd_history_pos = -1;
}

append_line("> " + code);
if (luaL_loadstring(m_L, code.c_str()) != LUA_OK)
{
Expand Down Expand Up @@ -289,6 +407,48 @@ void Lua_console::execute(const std::string& code)
}
}

int Lua_console::text_edit_callback(ImGuiInputTextCallbackData* data)
{
auto* console = static_cast<Lua_console*>(data->UserData);
if (!console)
return 0;

if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory && !console->m_cmd_history.empty())
{
const int history_size = static_cast<int>(console->m_cmd_history.size());

if (data->EventKey == ImGuiKey_UpArrow)
{
if (console->m_cmd_history_pos == -1)
console->m_cmd_history_pos = history_size - 1;
else if (console->m_cmd_history_pos > 0)
--console->m_cmd_history_pos;
}
else if (data->EventKey == ImGuiKey_DownArrow)
{
if (console->m_cmd_history_pos != -1)
{
if (console->m_cmd_history_pos + 1 < history_size)
++console->m_cmd_history_pos;
else
console->m_cmd_history_pos = -1;
}
}

// Apply history entry (or clear when pos == -1).
const char* new_buf = "";
if (console->m_cmd_history_pos != -1)
new_buf = console->m_cmd_history[console->m_cmd_history_pos].c_str();

data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, new_buf);
data->SelectionStart = 0;
data->SelectionEnd = data->BufTextLen;
}

return 0;
}

void Lua_console::render(bool* p_open)
{
if (!ImGui::Begin("Lua Console", p_open, ImGuiWindowFlags_None))
Expand All @@ -314,8 +474,9 @@ void Lua_console::render(bool* p_open)
display_text += '\0';

ImGui::BeginChild("Lua_consoleScroll", ImVec2(0, height), true, ImGuiWindowFlags_HorizontalScrollbar);
ImGui::InputTextMultiline("##lua_console_output", display_text.data(), display_text.size(),
ImVec2(-1, -1), ImGuiInputTextFlags_ReadOnly);
// Draw text in the child so the child window scrolls (InputTextMultiline has its own internal scroll).
const char* text_end = display_text.size() > 0 ? display_text.data() + display_text.size() - 1 : display_text.data();
ImGui::TextUnformatted(display_text.data(), text_end);
if (m_scroll_to_bottom)
{
ImGui::SetScrollHereY(1.0f);
Expand All @@ -325,7 +486,14 @@ void Lua_console::render(bool* p_open)
}

ImGui::SetNextItemWidth(-1);
bool run = ImGui::InputTextWithHint("##lua_input", "Enter Lua code (e.g. ezy.log('hi'))", m_input_buf, k_input_buf_size, ImGuiInputTextFlags_EnterReturnsTrue);
bool run = ImGui::InputTextWithHint("##lua_input",
"Enter Lua code (e.g. ezy.log('hi'))",
m_input_buf,
k_input_buf_size,
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackHistory,
&Lua_console::text_edit_callback,
this);

if (run)
{
execute(m_input_buf);
Expand Down
7 changes: 5 additions & 2 deletions src/lua_console.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Lua_console
~Lua_console();

void render(bool* p_open = nullptr);
static int text_edit_callback(struct ImGuiInputTextCallbackData* data);

/// Called from Lua ezy.log(); appends to console history.
void append_line_from_lua(const std::string& line);
Expand All @@ -28,8 +29,10 @@ class Lua_console
GUI* m_gui = nullptr;
lua_State* m_L = nullptr;

std::vector<std::string> m_history; // output lines (result or error)
static constexpr int k_input_buf_size = 1024;
std::vector<std::string> m_history; // output lines (result or error)
std::vector<std::string> m_cmd_history; // entered Lua commands
int m_cmd_history_pos = -1; // -1 when not browsing
static constexpr int k_input_buf_size = 1024;
char m_input_buf[k_input_buf_size] {};
bool m_scroll_to_bottom = false;
};