Add lua console

- Add an internal command logger to the lua kernel base, and expose
this.
- Redirect the internal lua `print` to the command logger.
- Add an external logger registration system to lua kernel, in
addition to the internal one.
- Add a lua console dialog which binds to a lua kernel base and
permits to review the contents of the logger and issue new commands,
and report errors.
- Add a hotkey binding to launch the lua console "`"
- Adds tab completion support to the lua console
This commit is contained in:
Chris Beck 2014-11-11 22:39:06 -05:00
parent 9fdc91e8a9
commit 2021a42d5b
20 changed files with 800 additions and 8 deletions

View file

@ -518,5 +518,8 @@
command="changelanguage"
key="l"
[/hotkey]
[hotkey]
command="global__lua__console"
key="`"
[/hotkey]
#undef IF_APPLE_CMD_ELSE_CTRL

View file

@ -0,0 +1,129 @@
#textdomain wesnoth-lib
###
### Definition of the window to show chat log.
###
[window]
id = "lua_interpreter"
description = "Lua interpreter console dialog."
[resolution]
definition = "default"
automatic_placement = "true"
vertical_placement = "center"
horizontal_placement = "center"
#maximum_width = 800
#maximum_height = 600
[tooltip]
id = "tooltip_large"
[/tooltip]
[helptip]
id = "tooltip_large"
[/helptip]
[grid]
[row] #header
grow_factor = 0
[column]
grow_factor = 7
border = "all"
border_size = 5
horizontal_alignment = "left"
[label]
definition = "title"
label = _ "Lua Console"
[/label]
[/column]
[/row]
[row] #choice
grow_factor="7"
[column]
grow_factor = 7
border = "all"
border_size = 5
horizontal_grow = "true"
#
# HACK:
# In order to reserve a minimum screen space for the
# central widget and avoid sudden window size changes when
# switching pages or entering text in the search box, we
# use two spacer widgets within a subgrid to restrict this
# cell's minimum size. It's important to keep the spacers'
# dimensions within certain limits so as to not cause
# dialog-spanning scrollbars to appear on 800x480.
#
{GUI_FORCE_WIDGET_MINIMUM_SIZE 600 400 (
[scroll_label]
id = "msg"
definition = "description"
label = ""
[/scroll_label]
)}
[/column]
[/row]
[row]
grow_factor=0
[column]
grow_factor = 7
border = "all"
border_size = 5
horizontal_grow = "true"
[text_box]
id = "text_entry"
definition = "default"
history = "lua_text_entry"
label = ""
[/text_box]
[/column]
[/row]
[row] #status
grow_factor = 0
[column]
horizontal_grow = "true"
[grid]
[row]
[column]
grow_factor = 0
border = "all"
border_size = 5
horizontal_alignment = "left"
[button]
id = "copy"
definition = "action_copy"
label = _ "clipboard^Copy"
# FIXME: tooltips cause weird interactions with map
# labels while running a GUI2 dialog, so let's
# not use a tooltip yet.
#tooltip = _ "Copy this log to clipboard"
[/button]
[/column]
[column]
grow_factor = 0
border = "all"
border_size = 5
horizontal_alignment = "right"
[button]
id = "cancel"
definition = "default"
label = _ "Close"
[/button]
[/column]
[/row]
[/grid]
[/column]
[/row]
[/grid]
[/resolution]
[/window]
# kate: indent-mode normal; encoding utf-8; space-indent on;

View file

@ -877,6 +877,7 @@ set(wesnoth-main_SRC
gui/dialogs/lobby/lobby_info.cpp
gui/dialogs/lobby_main.cpp
gui/dialogs/lobby_player_info.cpp
gui/dialogs/lua_interpreter.cpp
gui/dialogs/message.cpp
gui/dialogs/mp_alerts_options.cpp
gui/dialogs/mp_change_control.cpp

View file

@ -411,6 +411,7 @@ wesnoth_sources = Split("""
gui/dialogs/lobby/lobby_info.cpp
gui/dialogs/lobby_main.cpp
gui/dialogs/lobby_player_info.cpp
gui/dialogs/lua_interpreter.cpp
gui/dialogs/message.cpp
gui/dialogs/mp_alerts_options.cpp
gui/dialogs/mp_cmd_wrapper.cpp

View file

@ -0,0 +1,309 @@
/*
Copyright (C) 2011 - 2014 by Yurii Chernyi <terraninfo@terraninfo.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/dialogs/lua_interpreter.hpp"
#include "gui/auxiliary/find_widget.tpp"
#include "gui/dialogs/helper.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/settings.hpp"
#include "gui/widgets/text_box.hpp"
#include "gui/widgets/window.hpp"
#include "desktop/clipboard.hpp"
#include "game_errors.hpp"
#include "gettext.hpp"
#include "resources.hpp" //for help fetching lua kernel pointers
#include "scripting/application_lua_kernel.hpp" //needed for the WHICH_KERNEL version of display
#include "scripting/game_lua_kernel.hpp" //needed for the WHICH_KERNEL version of display
#include "scripting/lua_kernel_base.hpp"
#include "serialization/string_utils.hpp"
#include "serialization/unicode.hpp"
#include "log.hpp"
#include "text.hpp"
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
static lg::log_domain log_lua_int("lua/interpreter");
#define DBG_LUA LOG_STREAM(debug, log_lua_int)
#define LOG_LUA LOG_STREAM(info, log_lua_int)
#define WRN_LUA LOG_STREAM(warn, log_lua_int)
#define ERR_LUA LOG_STREAM(err, log_lua_int)
namespace gui2
{
/*WIKI
* @page = GUIWindowDefinitionWML
* @order = 3_chat_log
*
* == Settings manager ==
*
* This shows the settings manager
*
*/
REGISTER_DIALOG(lua_interpreter)
void tlua_interpreter::display(CVideo& video, lua_kernel_base * lk) {
if (!lk) {
ERR_LUA << "Tried to open console with a null lua kernel pointer.\n";
return;
}
tlua_interpreter(*lk).show(video);
}
void tlua_interpreter::display(CVideo& video, tlua_interpreter::WHICH_KERNEL which) {
if (which == tlua_interpreter::APP) {
display(video, resources::app_lua_kernel);
} else if (which == tlua_interpreter::GAME) {
display(video, resources::lua_kernel);
}
}
twindow* tlua_interpreter::build_window(CVideo& video)
{
return build(video, window_id());
}
void tlua_interpreter::pre_show(CVideo& /*video*/, twindow& window)
{
LOG_LUA << "Entering tlua_interpreter::view::pre_show" << std::endl;
bind(window);
update_contents();
//window.invalidate_layout(); // workaround for assertion failure
LOG_LUA << "Exiting tlua_interpreter::view::pre_show" << std::endl;
}
void tlua_interpreter::bind(twindow& window)
{
LOG_LUA << "Entering tlua_interpreter::bind" << std::endl;
msg_label = &find_widget<tscroll_label>(&window, "msg", false);
msg_label->set_use_markup(true);
msg_label->set_vertical_scrollbar_mode(tscrollbar_container::always_visible);
msg_label->set_label("");
text_entry = &find_widget<ttext_box>(&window, "text_entry", false);
//text_entry->set_text_changed_callback(
// boost::bind(&view::filter, this, boost::ref(window)));
window.keyboard_capture(text_entry);
window.set_click_dismiss(false);
window.set_enter_disabled(true);
connect_signal_pre_key_press(
*text_entry,
boost::bind(&tlua_interpreter::input_keypress_callback,
this,
_3,
_4,
_5,
boost::ref(window)));
copy_button = &find_widget<tbutton>(&window, "copy", false);
connect_signal_mouse_left_click(
*copy_button,
boost::bind(&tlua_interpreter::handle_copy_button_clicked,
this,
boost::ref(window)));
if (!desktop::clipboard::available()) {
copy_button->set_active(false);
copy_button->set_tooltip(_("Clipboard support not found, contact your packager."));
}
LOG_LUA << "Exiting tlua_interpreter::bind" << std::endl;
}
class tlua_interpreter::model {
private:
lua_kernel_base & L_;
std::stringstream log_;
public:
model (lua_kernel_base & lk)
: L_(lk)
, log_()
{
DBG_LUA << "constructing a tlua_interpreter::model\n";
//DBG_LUA << "incoming:\n" << lk.get_log().rdbuf() << "\n.\n";
log_ << lk.get_log().str() << std::flush;
L_.set_external_log(&log_); //register our log to get commands and output from the lua interpreter
//DBG_LUA << "recieved:\n" << log_.str() << "\n.\n";
DBG_LUA << "finished constructing a tlua_interpreter::model\n";
}
~model()
{
DBG_LUA << "destroying a tlua_interpreter::model\n";
L_.set_external_log(NULL); //deregister our log since it's about to be destroyed
}
bool execute(const std::string & cmd); // no throw of lua_error. should handle formatting any syntax errors to the interpreter log.
void add_dialog_message(const std::string & msg); //formats a message from the dialog, puts in interpreter log but not lua's log
std::string get_log() const { return log_.str(); }
std::vector<std::string> get_globals() { return L_.get_global_var_names(); }
std::vector<std::string> get_attribute_names(const std::string & s) { return L_.get_attribute_names(s); }
};
bool tlua_interpreter::model::execute (const std::string & cmd)
{
LOG_LUA << "tlua_interpreter::model::execute...\n";
try {
L_.throwing_run(cmd.c_str());
return true;
} catch (game::lua_error & e) {
add_dialog_message(std::string(e.what()));
return false;
}
}
void tlua_interpreter::model::add_dialog_message(const std::string & msg) {
log_ << "<span color='#8888FF'>" << font::escape_text(msg) << "</span>\n";
}
tlua_interpreter::tlua_interpreter(lua_kernel_base & lk)
: model_(new tlua_interpreter::model(lk))
, msg_label(NULL)
, copy_button(NULL)
, text_entry(NULL)
{
LOG_LUA << "entering tlua_interpreter ctor...\n";
LOG_LUA << "finished tlua_interpreter ctor...\n";
}
void tlua_interpreter::update_contents()
{
LOG_LUA << "tlua_interpreter update_contents...\n";
if (msg_label) {
msg_label->set_label(model_->get_log());
msg_label->scroll_vertical_scrollbar(tscrollbar_::END);
}
LOG_LUA << "tlua_interpreter update_contents finished\n";
}
void tlua_interpreter::handle_copy_button_clicked(twindow & /*window*/)
{
desktop::clipboard::copy_to_clipboard(model_->get_log(), false);
}
void tlua_interpreter::input_keypress_callback(bool& handled,
bool& halt,
const SDLKey key,
twindow& /*window*/)
{
LOG_LUA << "keypress_callback\n";
if(key == SDLK_RETURN || key == SDLK_KP_ENTER) {
LOG_LUA << "executing...\n";
model_->execute(text_entry->get_value());
update_contents();
text_entry->set_value("");
handled = true;
halt = true;
LOG_LUA << "finished executing\n";
} else if(key == SDLK_TAB) {
std::string text = text_entry->get_value();
std::string prefix;
size_t idx = text.find_last_of(" (");
if (idx != std::string::npos) {
prefix = text.substr(0, idx+1);
text = text.substr(idx+1);
}
std::vector<std::string> matches;
if (text.find('.') == std::string::npos) {
matches = model_->get_globals();
matches.push_back("and");
matches.push_back("break");
matches.push_back("else");
matches.push_back("elseif");
matches.push_back("end");
matches.push_back("false");
matches.push_back("for");
matches.push_back("function");
matches.push_back("local");
matches.push_back("nil");
matches.push_back("not");
matches.push_back("repeat");
matches.push_back("return");
matches.push_back("then");
matches.push_back("true");
matches.push_back("until");
matches.push_back("while");
} else {
matches = model_->get_attribute_names(text);
}
//bool line_start = utils::word_completion(text, matches);
if (text.size() > 0) { // this if is to avoid wierd behavior in word_completion, where it thinks nothing matches the empty string
utils::word_completion(text, matches);
}
if(matches.empty()) {
handled = true; //there's no point in putting tabs in the command line
halt = true;
return;
}
//if(matches.size() == 1) {
//text.append(" "); //line_start ? ": " : " ");
//} else {
if (matches.size() > 1) {
//std::string completion_list = utils::join(matches, " ");
const size_t wrap_limit = 80;
std::string buffer;
for (size_t idx = 0; idx < matches.size(); ++idx) {
if (buffer.size() + 1 + matches.at(idx).size() > wrap_limit) {
model_->add_dialog_message(buffer);
buffer = matches.at(idx);
} else {
if (buffer.size()) {
buffer += (" " + matches.at(idx));
} else {
buffer = matches.at(idx);
}
}
}
model_->add_dialog_message(buffer);
update_contents();
}
text_entry->set_value(prefix + text);
handled = true;
halt = true;
}
}
} // end of namespace gui2

View file

@ -0,0 +1,70 @@
/*
Copyright (C) 2014 by Chris Beck <render787@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef GUI_DIALOGS_LUA_INT_HPP_INCLUDED
#define GUI_DIALOGS_LUA_INT_HPP_INCLUDED
#include "gui/dialogs/dialog.hpp"
#include "gui/widgets/button.hpp"
#include "gui/widgets/scroll_label.hpp"
#include "gui/widgets/text_box.hpp"
#include <boost/scoped_ptr.hpp>
class lua_kernel_base;
namespace gui2
{
class tlua_interpreter : public tdialog
{
public:
class model;
tlua_interpreter(lua_kernel_base & lk);
/** Inherited from tdialog. */
twindow* build_window(CVideo& video);
/** Inherited from tdialog. */
void pre_show(CVideo& video, twindow& window);
enum WHICH_KERNEL { APP, GAME };
static void display(CVideo& video, lua_kernel_base * lk);
static void display(CVideo& video, WHICH_KERNEL which);
private:
boost::scoped_ptr<model> model_;
/** Inherited from tdialog, implemented by REGISTER_DIALOG. */
virtual const std::string& window_id() const;
void bind(twindow& window);
void update_contents();
void handle_copy_button_clicked(twindow & window);
void input_keypress_callback(bool& handled,
bool& halt,
const SDLKey key,
twindow& window);
tscroll_label* msg_label;
tbutton* copy_button;
ttext_box* text_entry;
};
}
#endif /* ! GUI_DIALOGS_CHAT_LOG_HPP_INCLUDED */

View file

@ -26,6 +26,7 @@
#include "gui/auxiliary/tips.hpp"
#include "gui/dialogs/debug_clock.hpp"
#include "gui/dialogs/language_selection.hpp"
#include "gui/dialogs/lua_interpreter.hpp"
//#define DEBUG_TOOLTIP
#ifdef DEBUG_TOOLTIP
#include "gui/dialogs/tip.hpp"
@ -47,6 +48,9 @@ static lg::log_domain log_config("config");
#define ERR_CF LOG_STREAM(err, log_config)
#define WRN_CF LOG_STREAM(warn, log_config)
static lg::log_domain log_general("general");
#define ERR_GEN LOG_STREAM(err, log_general)
namespace gui2
{
@ -188,6 +192,12 @@ static bool fullscreen(CVideo& video)
return true;
}
static bool launch_lua_console(twindow & window)
{
gui2::tlua_interpreter::display(window.video(), gui2::tlua_interpreter::APP);
return true;
}
void ttitle_screen::post_build(CVideo& video, twindow& window)
{
/** @todo Should become a title screen hotkey. */
@ -258,6 +268,10 @@ void ttitle_screen::post_build(CVideo& video, twindow& window)
window.register_hotkey(hotkey::HOTKEY_QUIT_GAME,
boost::bind(&hotkey, boost::ref(window), QUIT_GAME));
window.register_hotkey(
hotkey::LUA_CONSOLE,
boost::bind(&launch_lua_console, boost::ref(window)));
}
#ifdef DEBUG_TOOLTIP

View file

@ -130,7 +130,7 @@ void tscrollbar_::set_item_position(const unsigned item_position)
? item_count_ - visible_items_
: item_position;
item_position_ = (item_position_ + step_size_ - 1) / step_size_;
item_position_ = ((step_size_ * item_position_) + step_size_ - 1) / step_size_; // round to nearest step size
if(all_items_visible()) {
item_position_ = 0;

View file

@ -17,6 +17,7 @@
#include "boost/foreach.hpp"
#include "gui/dialogs/lua_interpreter.hpp"
#include "gui/dialogs/message.hpp"
#include "gui/dialogs/screenshot_notification.hpp"
#include "gui/dialogs/transient_message.hpp"
@ -32,6 +33,7 @@
static lg::log_domain log_config("config");
#define ERR_G LOG_STREAM(err, lg::general)
#define WRN_G LOG_STREAM(warn, lg::general)
#define LOG_G LOG_STREAM(info, lg::general)
#define DBG_G LOG_STREAM(debug, lg::general)
#define ERR_CF LOG_STREAM(err, log_config)
@ -688,6 +690,13 @@ void execute_command(display& disp, const hotkey_command& command, command_execu
}
break;
}
case LUA_CONSOLE: {
if (!disp.in_game()) {
WRN_G << "caution: attempting to interface console with game lua kernel when we are not in game...\n";
}
gui2::tlua_interpreter::display(disp.video(), gui2::tlua_interpreter::GAME);
break;
}
default:
DBG_G << "command_executor: unknown command number " << command.id << ", ignoring.\n";
break;

View file

@ -259,6 +259,8 @@ hotkey::hotkey_command_temp hotkey_list_[] = {
{ hotkey::GLOBAL__HELPTIP, "global__helptip", N_("Show Helptip"), false, scope_game | scope_editor | scope_main, "" },
{ hotkey::LUA_CONSOLE, "global__lua__console", N_("Show Lua Console"), false, scope_game | scope_editor | scope_main, ""},
//This list item must stay at the end since it is used as terminator for iterating.
{ hotkey::HOTKEY_NULL, "null", N_("Unrecognized Command"), true, hotkey::SCOPE_COUNT, "" }
};

View file

@ -96,6 +96,7 @@ enum HOTKEY_COMMAND {
TITLE_SCREEN__EDITOR,
TITLE_SCREEN__CREDITS,
GLOBAL__HELPTIP,
LUA_CONSOLE,
HOTKEY_WML,

View file

@ -793,6 +793,7 @@ bool play_controller::can_execute_command(const hotkey::hotkey_command& cmd, int
case hotkey::HOTKEY_MINIMAP_DRAW_VILLAGES:
case hotkey::HOTKEY_NULL:
case hotkey::HOTKEY_SAVE_REPLAY:
case hotkey::LUA_CONSOLE:
return true;
// Commands that have some preconditions:

View file

@ -54,6 +54,9 @@ application_lua_kernel::application_lua_kernel()
bool application_lua_kernel::initialize(game_launcher * gl)
{
cmd_log_ << "Adding game_launcher...\n";
//if (resources::app_lua_kernel && resources::app_lua_kernel != this) {
// throw "you appear to have multiple application lua kernels, this is bad";
//}

View file

@ -26,6 +26,8 @@ public:
application_lua_kernel();
bool initialize(game_launcher* gl);
virtual std::string my_name() { return "Application Lua Kernel"; }
static int intf_set_script(lua_State * L); /* Registers a lua function as the current script */
void call_script(const config & cfg); /* Call the current script, with config passed as argument */
};

View file

@ -2877,6 +2877,8 @@ LuaKernel::LuaKernel(const config &cfg)
{
lua_State *L = mState;
cmd_log_ << "Registering game-specific wesnoth lib functions...\n";
// Put some callback functions in the scripting environment.
static luaL_Reg const callbacks[] = {
{ "add_known_unit", &intf_add_known_unit },
@ -2954,6 +2956,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_setglobal(L, "wesnoth");
// Create the getside metatable.
cmd_log_ << "Adding getside metatable...\n";
lua_pushlightuserdata(L
, getsideKey);
lua_createtable(L, 0, 3);
@ -2966,6 +2970,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the gettype metatable.
cmd_log_ << "Adding gettype metatable...\n";
lua_pushlightuserdata(L
, gettypeKey);
lua_createtable(L, 0, 2);
@ -2976,6 +2982,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
//Create the getrace metatable
cmd_log_ << "Adding getrace metatable...\n";
lua_pushlightuserdata(L
, getraceKey);
lua_createtable(L, 0, 2);
@ -2986,6 +2994,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the getunit metatable.
cmd_log_ << "Adding getunit metatable...\n";
lua_pushlightuserdata(L
, getunitKey);
lua_createtable(L, 0, 5);
@ -3002,6 +3012,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the unit status metatable.
cmd_log_ << "Adding unit status metatable...\n";
lua_pushlightuserdata(L
, ustatusKey);
lua_createtable(L, 0, 3);
@ -3014,6 +3026,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the unit variables metatable.
cmd_log_ << "Adding unit variables metatable...\n";
lua_pushlightuserdata(L
, unitvarKey);
lua_createtable(L, 0, 3);
@ -3026,6 +3040,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the vconfig metatable.
cmd_log_ << "Adding vconfig metatable...\n";
lua_pushlightuserdata(L
, vconfigKey);
lua_createtable(L, 0, 4);
@ -3040,9 +3056,13 @@ LuaKernel::LuaKernel(const config &cfg)
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the ai elements table.
cmd_log_ << "Adding ai elements table...\n";
ai::lua_ai_context::init(L);
// Create the game_config variable with its metatable.
cmd_log_ << "Adding game_config table...\n";
lua_getglobal(L, "wesnoth");
lua_newuserdata(L, 0);
lua_createtable(L, 0, 3);
@ -3057,6 +3077,8 @@ LuaKernel::LuaKernel(const config &cfg)
lua_pop(L, 1);
// Create the current variable with its metatable.
cmd_log_ << "Adding wesnoth current table...\n";
lua_getglobal(L, "wesnoth");
lua_newuserdata(L, 0);
lua_createtable(L, 0, 2);
@ -3069,18 +3091,24 @@ LuaKernel::LuaKernel(const config &cfg)
lua_pop(L, 1);
// Create the wml_actions table.
cmd_log_ << "Adding wml_actions table...\n";
lua_getglobal(L, "wesnoth");
lua_newtable(L);
lua_setfield(L, -2, "wml_actions");
lua_pop(L, 1);
// Create the game_events table.
cmd_log_ << "Adding game_events table...\n";
lua_getglobal(L, "wesnoth");
lua_newtable(L);
lua_setfield(L, -2, "game_events");
lua_pop(L, 1);
// Create the theme_items table.
cmd_log_ << "Adding theme_items table...\n";
lua_getglobal(L, "wesnoth");
lua_newtable(L);
lua_createtable(L, 0, 2);
@ -3122,6 +3150,8 @@ void LuaKernel::initialize()
lua_pop(L, 2);
// Create the unit_types table.
cmd_log_ << "Adding unit_types table...\n";
lua_getglobal(L, "wesnoth");
lua_pushlightuserdata(L
, gettypeKey);
@ -3140,6 +3170,8 @@ void LuaKernel::initialize()
lua_pop(L, 2);
//Create the races table.
cmd_log_ << "Adding races table...\n";
lua_getglobal(L, "wesnoth");
lua_pushlightuserdata(L
, getraceKey);
@ -3160,6 +3192,8 @@ void LuaKernel::initialize()
lua_pop(L, 2);
// Execute the preload scripts.
cmd_log_ << "Running preload scripts...\n";
game_config::load_config(preload_config);
BOOST_FOREACH(const config &cfg, preload_scripts) {
run(cfg["code"].str().c_str());

View file

@ -37,6 +37,9 @@ class LuaKernel : public lua_kernel_base
public:
LuaKernel(const config &);
virtual std::string my_name() { return "Game Lua Kernel"; }
void initialize();
void save_game(config &);
void load_game();

View file

@ -40,6 +40,7 @@
#include <boost/scoped_ptr.hpp>
static lg::log_domain log_scripting_lua("scripting/lua");
#define DBG_LUA LOG_STREAM(debug, log_scripting_lua)
#define LOG_LUA LOG_STREAM(info, log_scripting_lua)
#define WRN_LUA LOG_STREAM(warn, log_scripting_lua)
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
@ -220,15 +221,87 @@ static int intf_compare_versions(lua_State* L)
return 1;
}
/**
* Replacement print function -- instead of printing to std::cout, print to the command log.
* Intended to be bound to this' command_log at registration time.
*/
int lua_kernel_base::intf_print(lua_State* L)
{
DBG_LUA << "intf_print called:\n";
size_t nargs = lua_gettop(L);
for (size_t i = 2; i <= nargs; ++i) { // ignore first argument, since it's the userdata from boost cfunc binding
cmd_log_ << lua_tostring(L,i);
DBG_LUA << "'" << lua_tostring(L,i) << "'\n";
}
cmd_log_ << "\n";
DBG_LUA << "\n";
lua_pop(L, nargs - 1);
return 0;
}
// Make the possibility to push the boost::bind 'ed intf_print onto the lua stack, using a dispatcher to get around the C++ / boost::function aspect of this
char const * boost_cfunc = "Boost_C_Function";
typedef boost::function<int(lua_State*)> lua_cfunc;
static int intf_boost_cfunc_dispatcher ( lua_State* L )
{
lua_cfunc *f = static_cast<lua_cfunc *> (lua_touserdata(L, 1));
int result = (*f)(L);
lua_remove(L,1);
return result;
}
static int intf_boost_cfunc_cleanup ( lua_State* L )
{
lua_cfunc * d = static_cast< lua_cfunc *> (luaL_testudata(L, 1, boost_cfunc));
if (d == NULL) {
ERR_LUA << "boost_cfunc_cleanup called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl;
ERR_LUA << "This may indicate a memory leak, please report at bugs.wesnoth.org" << std::endl;
} else {
d->~lua_cfunc();
}
return 0;
}
static void register_boost_cfunc_metatable ( lua_State* L )
{
luaL_newmetatable(L, boost_cfunc);
lua_pushcfunction(L, intf_boost_cfunc_dispatcher);
lua_setfield(L, -2, "__call");
lua_pushcfunction(L, intf_boost_cfunc_cleanup);
lua_setfield(L, -2, "__gc");
lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table
lua_setfield(L, -2, "__index");
lua_pop(L, 1);
}
static void push_boost_cfunc( lua_State* L, const lua_cfunc & f )
{
void * p = lua_newuserdata(L, sizeof(lua_cfunc));
luaL_setmetatable(L, boost_cfunc);
new (p) lua_cfunc(f);
}
// End Callback implementations
lua_kernel_base::lua_kernel_base()
: mState(luaL_newstate())
, cmd_log_()
{
lua_State *L = mState;
cmd_log_ << "Initializing " << my_name() << "...\n";
// Open safe libraries.
// Debug and OS are not, but most of their functions will be disabled below.
cmd_log_ << "Adding standard libs...\n";
static const luaL_Reg safe_libs[] = {
{ "", luaopen_base },
{ "table", luaopen_table },
@ -276,6 +349,8 @@ lua_kernel_base::lua_kernel_base()
lua_setglobal(L, "loadfile");
// Store the error handler.
cmd_log_ << "Adding error handler...\n";
lua_pushlightuserdata(L
, executeKey);
lua_getglobal(L, "debug");
@ -285,6 +360,8 @@ lua_kernel_base::lua_kernel_base()
lua_pop(L, 1);
// Create the gettext metatable.
cmd_log_ << "Adding gettext metatable...\n";
lua_pushlightuserdata(L
, gettextKey);
lua_createtable(L, 0, 2);
@ -295,6 +372,8 @@ lua_kernel_base::lua_kernel_base()
lua_rawset(L, LUA_REGISTRYINDEX);
// Create the tstring metatable.
cmd_log_ << "Adding tstring metatable...\n";
lua_pushlightuserdata(L
, tstringKey);
lua_createtable(L, 0, 4);
@ -311,6 +390,7 @@ lua_kernel_base::lua_kernel_base()
lua_settop(L, 0);
// Add some callback from the wesnoth lib
cmd_log_ << "Registering basic wesnoth API...\n";
static luaL_Reg const callbacks[] = {
{ "compare_versions", &intf_compare_versions },
@ -325,7 +405,6 @@ lua_kernel_base::lua_kernel_base()
{ "set_dialog_markup", &intf_set_dialog_markup },
{ "set_dialog_value", &intf_set_dialog_value },
{ "show_dialog", &intf_show_dialog },
{ NULL, NULL }
};
@ -336,6 +415,18 @@ lua_kernel_base::lua_kernel_base()
luaL_setfuncs(L, callbacks, 0);
lua_setglobal(L, "wesnoth");
// Define the Boost_C_Function metatable ( so we can override print to point to a C++ member function )
cmd_log_ << "Adding boost cfunc proxy...\n";
register_boost_cfunc_metatable(L);
// Override the print function
cmd_log_ << "Redirecting print function...\n";
lua_cfunc my_print = boost::bind(&lua_kernel_base::intf_print, this, _1);
push_boost_cfunc(L, my_print);
lua_setglobal(L, "print");
// Create the package table.
lua_getglobal(L, "wesnoth");
lua_newtable(L);
@ -444,18 +535,27 @@ bool lua_kernel_base::load_string(char const * prog, error_handler e_h)
return true;
}
// Call load_string and protected call. Make them throw exceptions, and if we catch one, reformat it with signature for this function and log it.
// Call load_string and protected call. Make them throw exceptions.
//
void lua_kernel_base::throwing_run(const char * prog) {
cmd_log_ << "$ " << prog << "\n";
error_handler eh = boost::bind(&lua_kernel_base::throw_exception, this, _1, _2 );
load_string(prog, eh);
protected_call(0, 0, eh);
}
// Do a throwing run, but if we catch a lua_error, reformat it with signature for this function and log it.
void lua_kernel_base::run(const char * prog) {
try {
error_handler eh = boost::bind(&lua_kernel_base::throw_exception, this, _1, _2 );
load_string(prog, eh);
protected_call(0, 0, eh);
throwing_run(prog);
} catch (game::lua_error & e) {
cmd_log_ << e.what() << "\n";
lua_kernel_base::log_error(e.what(), "In function lua_kernel::run()");
}
}
/**
* Loads the "package" package into the Lua environment.
* This action is inherently unsafe, as Lua scripts will now be able to
@ -469,3 +569,67 @@ void lua_kernel_base::load_package()
lua_pushstring(L, "package");
lua_call(L, 1, 0);
}
/**
* Gets all the global variable names in the Lua environment. This is useful for tab completion.
*/
std::vector<std::string> lua_kernel_base::get_global_var_names()
{
std::vector<std::string> ret;
lua_State *L = mState;
int idx = lua_gettop(L);
lua_getglobal(L, "_G");
lua_pushnil(L);
while (lua_next(L, idx+1) != 0) {
if (lua_isstring(L, -2)) {
ret.push_back(lua_tostring(L,-2));
}
lua_pop(L,1);
}
lua_settop(L, idx);
return ret;
}
/**
* Gets all attribute names of an extended variable name. This is useful for tab completion.
*/
std::vector<std::string> lua_kernel_base::get_attribute_names(const std::string & input)
{
std::vector<std::string> ret;
std::string var_path = input; // it's convenient to make a copy, even if it's a little slower
lua_State *L = mState;
int base = lua_gettop(L);
lua_getglobal(L, "_G");
size_t idx = var_path.find('.');
size_t last_dot = 0;
while (idx != std::string::npos ) {
last_dot += idx + 1; // Since idx was not npos, add it to the "last_dot" idx, so that last_dot keeps track of indices in input string
lua_pushstring(L, var_path.substr(0, idx).c_str()); //push the part of the path up to the period
lua_rawget(L, -2);
if (!lua_istable(L,-1) && !lua_isuserdata(L,-1)) {
return ret; //if we didn't get a table or userdata we can't proceed
}
var_path = var_path.substr(idx+1); // chop off the part of the path we just dereferenced
idx = var_path.find('.'); // find the next .
}
std::string prefix = input.substr(0, last_dot);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
if (lua_isstring(L, -2)) {
ret.push_back(prefix + lua_tostring(L,-2));
}
lua_pop(L,1);
}
lua_settop(L, base);
return ret;
}

View file

@ -15,7 +15,10 @@
#ifndef SCRIPTING_LUA_KERNEL_BASE_HPP
#define SCRIPTING_LUA_KERNEL_BASE_HPP
#include <string> // for string
#include <ostream>
#include <sstream>
#include <string>
#include <vector>
#include "utils/boost_function_guarded.hpp"
struct lua_State;
@ -28,8 +31,20 @@ public:
/** Runs a plain script. Doesn't throw lua_error.*/
void run(char const *prog);
/** Runs a plain script, but reports errors by throwing lua_error.*/
void throwing_run(char const * prog);
void load_package();
std::vector<std::string> get_global_var_names();
std::vector<std::string> get_attribute_names(const std::string & var_path);
virtual std::string my_name() { return "Basic Lua Kernel"; }
const std::stringstream & get_log() { cmd_log_.log_ << std::flush; return cmd_log_.log_; }
void clear_log() { cmd_log_.log_.str(""); cmd_log_.log_.clear(); }
void set_external_log( std::ostream * lg ) { cmd_log_.external_log_ = lg; }
virtual void log_error(char const* msg, char const* context = "Lua error");
virtual void throw_exception(char const* msg, char const* context = "Lua error"); //throws game::lua_error
@ -38,6 +53,33 @@ public:
protected:
lua_State *mState;
struct command_log {
std::stringstream log_;
std::ostream * external_log_;
command_log()
: log_()
, external_log_(NULL)
{}
inline command_log & operator<< (const std::string & str) {
log_ << str;
if (external_log_) {
(*external_log_) << str;
}
return *this;
}
inline command_log & operator<< (char const* str) {
return *this << std::string(str);
}
};
command_log cmd_log_;
// Print text to the command log for this lua kernel. Used as a replacement impl for lua print.
int intf_print(lua_State * L);
// Execute a protected call. Error handler is called in case of an error, using syntax for log_error and throw_exception above. Returns true if successful.
bool protected_call(int nArgs, int nRets, error_handler);
// Load a string onto the stack as a function. Returns true if successful, error handler is called if not.

View file

@ -91,6 +91,8 @@ mapgen_lua_kernel::mapgen_lua_kernel()
lua_State *L = mState;
// Add mersenne twister rng wrapper
cmd_log_ << "Adding mt19937 metatable...\n";
luaL_newmetatable(L, Rng);
static luaL_Reg const callbacks[] = {

View file

@ -25,6 +25,8 @@ class mapgen_lua_kernel : public lua_kernel_base {
public:
mapgen_lua_kernel();
virtual std::string my_name() { return "Mapgen Lua Kernel"; }
std::string create_map(const char * prog, const config & generator); // throws game::lua_error
config create_scenario(const char * prog, const config & generator); // throws game::lua_error