diff --git a/call_handlers/debugtools_commands.lua b/call_handlers/debugtools_commands.lua index 1a21135..512ea35 100644 --- a/call_handlers/debugtools_commands.lua +++ b/call_handlers/debugtools_commands.lua @@ -657,4 +657,50 @@ function Commands:set_enable_animation_text_command(session, response, bool) radiant.util.set_global_config('enable_effect_triggers', bool) end +function Commands:exec_script_server(session, response, script, entity) + return self:exec_script(session, response, script, entity) +end + +function Commands:exec_script_client(session, response, script, entity) + return self:exec_script(session, response, script, entity) +end + +function Commands:exec_script(session, response, script, entity) + local expr, error + expr, error = loadstring('return ' .. script) + if not expr then + -- Maybe it's a statement? + expr, error = loadstring(script) + end + if error then + response:reject('ERROR: ' .. error) + end + + -- Set the entity as a global variable. This is terrible, but unavoidable if we want to + -- let executed code set global vars. We could instead compile the expression as a + -- function with an entity argument, but if it's a statement, it won't be able to + -- normally set global vars, so no REPL state. + local saved_entity = rawget(_G, 'e') + rawset(_G, 'e', entity) + local success, result = pcall(expr) + rawset(_G, 'e', saved_entity) + + if success then + local output + if result == nil then + output = '' + elseif type(result) == 'string' then + output = string.gsub(string.format('%q', result), '\n', 'n') + elseif type(result) == 'table' then + output = radiant.util.table_tostring(result) + else + output = tostring(result) + end + response:resolve(output) + else + response:reject('ERROR: ' .. result) + end + return true +end + return Commands diff --git a/manifest.json b/manifest.json index ddd1afb..c1c72a7 100644 --- a/manifest.json +++ b/manifest.json @@ -25,7 +25,8 @@ "file(/ui/entity_editor/entity_editor.html)", "file(/ui/job_monitor/job_monitor.html)", "file(/ui/trace_monitor/trace_monitor.html)", - "file(/ui/item_dropper/item_dropper.html)" + "file(/ui/item_dropper/item_dropper.html)", + "file(/ui/lua_console/lua_console.html)" ], "js": [ "file(ui/debug_dock/debug_dock.js)", @@ -38,7 +39,8 @@ "file(/ui/entity_editor/entity_editor.js)", "file(/ui/job_monitor/job_monitor.js)", "file(/ui/trace_monitor/trace_monitor.js)", - "file(/ui/item_dropper/item_dropper.js)" + "file(/ui/item_dropper/item_dropper.js)", + "file(/ui/lua_console/lua_console.js)" ], "less": [ "file(/html/font-awesome-4.3.0/css/font-awesome.min.css)", @@ -51,7 +53,8 @@ "file(/ui/entity_editor/entity_editor.less)", "file(/ui/job_monitor/job_monitor.less)", "file(/ui/trace_monitor/trace_monitor.less)", - "file(/ui/item_dropper/item_dropper.less)" + "file(/ui/item_dropper/item_dropper.less)", + "file(/ui/lua_console/lua_console.less)" ] }, "functions": { @@ -266,6 +269,14 @@ "set_enable_animation_text_command": { "controller": "file(call_handlers/debugtools_commands.lua)", "endpoint": "server" + }, + "exec_script_server": { + "controller": "file(call_handlers/debugtools_commands.lua)", + "endpoint": "server" + }, + "exec_script_client": { + "controller": "file(call_handlers/debugtools_commands.lua)", + "endpoint": "client" } }, "default_locale": "en" diff --git a/ui/lua_console/images/icon.png b/ui/lua_console/images/icon.png new file mode 100644 index 0000000..e17a2e4 Binary files /dev/null and b/ui/lua_console/images/icon.png differ diff --git a/ui/lua_console/images/resizer.png b/ui/lua_console/images/resizer.png new file mode 100644 index 0000000..31fa05f Binary files /dev/null and b/ui/lua_console/images/resizer.png differ diff --git a/ui/lua_console/images/x-2x.png b/ui/lua_console/images/x-2x.png new file mode 100644 index 0000000..5900283 Binary files /dev/null and b/ui/lua_console/images/x-2x.png differ diff --git a/ui/lua_console/lua_console.html b/ui/lua_console/lua_console.html new file mode 100644 index 0000000..3e5b16d --- /dev/null +++ b/ui/lua_console/lua_console.html @@ -0,0 +1,34 @@ + + + + + diff --git a/ui/lua_console/lua_console.js b/ui/lua_console/lua_console.js new file mode 100644 index 0000000..5ff7f26 --- /dev/null +++ b/ui/lua_console/lua_console.js @@ -0,0 +1,188 @@ +$(document).on('stonehearthReady', function(){ + App.debugDock.addToDock(App.StonehearthLuaConsoleIcon); + radiant.call('radiant:get_config', 'mods.debugtools.enable_lua_console_hotkey') + .done(function (o) { + if (o['mods.debugtools.enable_lua_console_hotkey']) { + $(top).bind('keydown', function (e) { + if (e.keyCode == 192 && !e.originalEvent.repeat) { // Tilde/backtick + App.debugView.getView(App.StonehearthLuaConsoleIcon).$().click(); + e.preventDefault(); + e.stopPropagation(); + } + }); + } + }); +}); + +App.StonehearthLuaConsoleIcon = App.View.extend({ + templateName: 'luaConsoleIcon', + classNames: ['debugDockIcon'], + + didInsertElement: function() { + $('#luaConsoleIcon').tooltipster(); + this.$().click(function () { + var view = App.debugView.getView(App.StonehearthLuaConsoleView); + if (view) { + view.$().toggle(); + if (view.$().is(':visible')) { + view.focus(); + } + } else { + App.debugView.addView(App.StonehearthLuaConsoleView); + } + }); + } +}); + +App.StonehearthLuaConsoleView = App.View.extend({ + templateName: 'luaConsole', + uriProperty: 'model', + envContainer: Ember.ContainerView.extend(), + + didInsertElement: function() { + var self = this; + + this.$().draggable({ handle: '.header' }); + + this.$('.button').tooltipster({ + theme: 'tooltipster-shdt', + arrow: true, + }); + + this.$("#closeConsoleButton").click(function () { + self.$().hide(); + }); + + self.clientEnv = App.StonehearthLuaConsoleEnvironmentView.create({ env: 'client' }); + self.serverEnv = App.StonehearthLuaConsoleEnvironmentView.create({ env: 'server' }); + + self.get('envContainerInstance').pushObject(self.serverEnv); + self.get('envContainerInstance').pushObject(self.clientEnv); + + setTimeout(function () { self.switchEnv(true); }, 1); + }, + + switchEnv: function (isClient) { + var toShow = isClient ? this.clientEnv : this.serverEnv; + var toHide = isClient ? this.serverEnv : this.clientEnv; + + toHide.$().hide(); + toShow.$().show(); + toShow.focus(); + + var toSelect = isClient ? '#clientButton' : '#serverButton'; + var toDeselect = isClient ? '#serverButton' : '#clientButton'; + this.$(toSelect).addClass('selected'); + this.$(toDeselect).removeClass('selected'); + }, + + focus: function () { + if (this.clientEnv.$().is(':visible')) { + this.clientEnv.focus(); + } else { + this.serverEnv.focus(); + } + }, + + actions: { + switchToServer: function () { + this.switchEnv(false); + }, + + switchToClient: function () { + this.switchEnv(true); + } + } +}); + +App.StonehearthLuaConsoleEnvironmentView = App.View.extend({ + templateName: 'stonehearthLuaConsoleEnvironment', + uriProperty: 'model', + env: null, + + init: function () { + this._super(); + this._history = []; + this._curHistoryIdx = 0; + this._curCommand = ''; + }, + + didInsertElement: function() { + var self = this; + + self.$('#input').keydown(function(e) { + if (e.keyCode == 13 && !e.originalEvent.repeat) { // Enter + var command = $(this).val(); + if (!command) return; + self._history.push(command); + $(this).val(''); + self._curHistoryIdx = self._history.length - 1; + + var outputArea = self.$('.output'); + outputArea.append($('
').text('> ' + command)); + + var resultContainer = $('
').text('...'); + outputArea.append(resultContainer); + + var currentEntity = App.stonehearthClient.getSubSelectedEntity(); + if (!currentEntity) { + currentEntity = App.stonehearthClient.getSelectedEntity(); + } + radiant.call('debugtools:exec_script_' + self.env, command, currentEntity) + .done(function (o) { + resultContainer.removeClass('progress').text(o.result); + outputArea.scrollTop(outputArea[0].scrollHeight); + }) + .fail(function (o) { + if (typeof o.error == 'string') { + // Client failures don't decode. We should fix it at the root, but this hack here is fine for now. + o = JSON.parse(o.error) + } + resultContainer.removeClass('progress').addClass('error').text(o.result); + outputArea.scrollTop(outputArea[0].scrollHeight); + }); + + outputArea.scrollTop(outputArea[0].scrollHeight); + } else if (e.keyCode == 27) { // Esc + App.debugView.getView(App.StonehearthLuaConsoleIcon).$().click(); + e.preventDefault(); + e.stopPropagation(); + } + }); + + this.$('#input').keydown(function (e) { + if (e.keyCode == 38) { // UpArrow + if (self._curHistoryIdx == self._history.length - 1) { + // Remember our current command, if any. + if ($(this).val() != '') { + self._curCommand = $(this).val(); + } + } + + $(this).val(self._history[self._curHistoryIdx]); + + self._curHistoryIdx--; + if (self._curHistoryIdx < 0) { + self._curHistoryIdx = 0; + } + e.preventDefault(); + } else if (e.keyCode == 40) { // DownArrow + if (self._curHistoryIdx == self._history.length - 1) { + if (self._curCommand != '') { + $(this).val(self._curCommand); + self._curCommand = ''; + } + return; + } + self._curHistoryIdx++; + $(this).val(self._history[self._curHistoryIdx]); + e.preventDefault(); + } + }); + }, + + focus: function () { + this.$('#input').focus(); + }, +}); + diff --git a/ui/lua_console/lua_console.less b/ui/lua_console/lua_console.less new file mode 100644 index 0000000..acb08a3 --- /dev/null +++ b/ui/lua_console/lua_console.less @@ -0,0 +1,146 @@ +#luaConsoleIcon { + .debugDockIcon; + background: url('./images/icon.png'); +} + +#luaConsole { + .debug; + position: absolute; + top: 9px; + left: 655px; + font-family: @debugFontFamily; + padding: 0; + font-size: 14px; + + .header { + margin-bottom: 1px; + font-size: 125%; + height: 24px; + line-height: 28px; + + #toolbar { + display: inline-block; + margin-left: 100px; + font-size: 90%; + position: relative; + top: -1px; + } + + #closeConsoleButton { + float: right; + width: 16px; + height: 16px; + padding: 5px; + background: url(images/x-2x.png) no-repeat center; + opacity: .5; + } + } + + .button { + position: relative; + color: #fff; + margin-left: 30px; + font-weight: bold; + + &:hover { + color: #fc0; + } + } + + .selected { + color: #fc0; + } + + #close { + position: absolute; + top: 8px; + right: 4px; + } + + .title { + font-size: 16px; + color: white; + } + .subtitle { + font-size: 12px; + color: white; + } + + .menu { + li { + margin-left: 0px; + padding-left: 0px; + list-style-type: none; + i { + color: #fc0; + } + &:hover { + color: #fc0; + } + } + } +} + +.tooltipster-shdt { + .tooltipster-content { + .debug; + } +} + +#stonehearthLuaConsoleEnvironment { + .output { + -webkit-user-select: text; + height: 250px; + width: 1000px; + overflow: auto; + resize: both; + + #title { + padding: 6px; + background: #3d3f36; + } + + .cmdIn { + font-weight: bold; + } + + .cmdOut { + font-weight: bold; + margin-bottom: 4px; + color: #01b201; + margin-left: 2ch; + white-space: pre-wrap; + &.progress { + color: yellow; + } + &.error { + color: #cc0000; + } + } + + &::-webkit-resizer { + background: rgb(39, 40, 34) url(images/resizer.png) bottom right no-repeat; + } + } + + .input { + white-space: nowrap; + overflow-x: hidden; + &:before { + content: '> '; + display: inline; + background: #000; + color: #ffc000; + padding: 2px 0px; + } + input { + width: 100%; + border: 0px; + background: #000; + color: #ffc000; + padding: 2px 0px; + font-size: inherit; + font-family: inherit; + } + } +}